- •ЗАМЕЧАНИЕ <...> в описании правил записи (формата) конструкций языка означают понятие языка, в программе не записываются.
- •Пример
- •Формат
- •Значение
- •Содержание
- •Формат
- •if ( <выражение> )
- •Форма представления одного данного
- •Ввод двумерного массива
- •Вопрос? А как же передать результаты работы в вызывающую процедуру, если реально можно передать только одно значение?
- •Вызывающая
- •Вызываемая
- •ВЫЧИСЛЕНИЕ КВАДРАТИЧНОЙ ФОРМЫ
Заключительный пример
#include <stdio.h> #define SIZE 4
ВЫЧИСЛЕНИЕ КВАДРАТИЧНОЙ ФОРМЫ
void main(void) |
{ |
|
float u[SIZE], |
/* Входной вектор */ |
|
b[SIZE][SIZE], |
/* Входная матрица */ |
|
v[SIZE], |
/* Вектор b*u */ |
|
z, |
/* Результат */ |
|
scalar(); |
/* Скалярное произведение векторов */ |
|
int i, j; |
|
|
printf ( "Исходный вектор: \n" ); for ( i=0; i<SIZE; i++ ) { scanf ( "%f", &u[i] );}
printf ( "Исходная матрица: \n" ); for ( i=0; i<SIZE; i++ ) {
for ( j=0; j<SIZE; j++ ) { scanf ( "%f", &b[i][j] ); } } matrix(b, u, v); z=scalar(v, u);
printf ( " \n \n \n Квадратичная форма равна %.5g \n", z);
}/* End main */
/* Умножение матрицы на вектор */
void matrix ( float a[][SIZE], float x[], float y[] ) { int i, j;
for ( i=0; i<SIZE; i++ ) { for ( y[i]=j=0; j<SIZE; j++ ) { y[i] += a[i][j]*x[j];
}
}
}/* End matrix */
/* Скалярное произведение векторов */
float scalar (float x[], float y[] ) |
{ |
|
int i; |
|
|
for ( z = i = 0; i<SIZE; i++ ) { |
|
|
z += x[i]*y[i]; |
|
|
} |
|
|
return z; |
|
|
} |
/* End scalar */ |
|
51
Глава 9 СИМВОЛЬНАЯ ИНФОРМАЦИЯ И СТРОКИ
Для представления текстовой информации в языке СИ используются символы (константы), символьные переменные и строки (строковые константы), для которых в языке СИ не введено отдельного типа в отличие от некоторых других языков программирования.
Символьные константы используются для изображения отдельных знаков, имеющих индивидуальные внутренние коды. Каждая символьная константа – это лексема (элемент языка СИ), которая состоит из изображения символа и ограничивающих апост-
рофов: ‘A’, ‘a’,’B’,’8’,’+’,’;’.
Внутри апострофов можно записать любой символ, изображаемый на экране или принтере в текстовом режиме а также управляющие символы.
Для символьных данных (переменных) введен базовый тип char. Описание символьных переменных имеет вид:
char список_имен_переменных;
Пример: char a, z;
9.1Ввод - вывод символьных данных
Для ввода и вывода символьных значений в форматных строках библиотечных функций printf( ) и scanf( ) используется спецификация преобразования % c.
Пример:
Ввести предложение, слова в котором разделены пробелами и в конце которого стоит точка. Удалить повторяющиеся пробелы между отдельными словами (оставить по одному пробелу), вывести отредактированное предложение на экран.
/* удаление повторяющихся пробелов */
#include<stdio.h> void main()
{
char z,s; /*z–текущий вводимый символ*/ /*s–предыдущий введенный символ*/
printf(“\nНапишите предложение с точкой в конце: \n”); for ( z=s=’ ’;z!=’.’;s=z ) {
scanf(“%c”,&z); // z=getch(); if ( z ! =’ ‘ || s !=’ ‘)
printf(“%c”,z); } /*конец цикла обработки*/
}/*конец программы*/
В программе две символьные переменные: z – для чтения очередного символа и s – для хранения предыдущего. В заголовке цикла переменные s и z получают значение “пробел”. Очередной символ вводится как значение переменной z, и пара z, s анализируется. Если хотя бы один из символов значений пары отличен от пробела, то значение z печатается. В заголовке цикла z сравнивается с символом “точка” и при несовпадении запоминается как значение s. Далее цикл повторяется до появления на входе точки, причем появление двух пробелов (z и s)приводит к пропуску оператора печати.
52
9.2Внутренние коды и упорядоченность символов
ВАЖНО ! В языке принято соглашение, что везде, где синтаксис позволяет
использовать целые числа, можно использовать и символы, т.е. данные типа char, ко-
торые при этом представляются числовыми значениями своих внутренних кодов.
Такое соглашение позволяет сравнительно просто упорядочивать символы, обращаясь с ними как с целочисленными величинами.
Например, внутренние коды десятичных цифр в таблицах кодов ASCII упорядочены по числовым значениям, поэтому несложно перебирать символы десятичных цифр в нужном порядке.
Пример:
Следующая программа печатает цифры от 0 до 9 и шестнадцатеричные представления их внутренних кодов.
/*печать десятичных цифр*/
#include<stdio.h> void main()
{ |
char z; |
|
|
for ( z=’0’, z<=’9’, z++ ) { |
printf(“\n”); |
|
if ( z= =’0’ | |z= =’5’) |
|
|
printf( “%c – %x”, z, z); |
|
} |
} |
|
/*конец программы*/ |
|
Результат выполнения программы: 0–301–312–323–334–34 5–356–367–378–389–39
Обратите внимание на то, что символьная переменная z является операндом арифметической операции ‘++’, выполняемой над числовым представлением ее внутреннего кода. Для изображения значения символов в форматной строке функции printf( ) используется спецификация %с. шестнадцатеричные коды выводятся с помощью спецификации %х.
Пример:
Воспользовавшись упорядоченностью внутренних кодов букв латинского алфавита, очень просто его напечатать.
/*печать латинского алфавита*/
#include<stdio.h> void main() {
char z;
for ( z=’A’, z<=’Z’, z++) printf(“%c”,z);
}/*конец программы*/
Результат выполнения программы: ABCDEFGHIJKLMNOPQRSTUVWXYZ
53
9.3Строки или строковые константы
Впрограмме строки, или строковые константы, представляются последовательностью изображений символов, заключенной в кавычки (не в апострофы), например “любые символы”. Среди символов строки могут быть эскейп-последовательности, соответствующие кодам не изображаемых (специальных) символьных констант.
Примеры:
“1234567890”
“\t состав президиума”
“начало строки \t и конец строки”.
В качестве терминирующего символа выбран символ с кодом 0 (не путайте его с
символом '0'). Таким образом, определение
char HelloStr [] = "Hello, world";
фактически интерпретируется как
char HelloStr [] = {'H', 'e', 'l', 'l', 'o', ' ', ',', 'w', 'o', 'r', 'l', 'd', '\0'};
Это происходит при инициализации массива строкой. Во всех остальных случаях встретившаяся строка интерпретируется как еще не созданный безымянный массив соот-
ветствующей длины. То есть выражение printf ("Hello, world\n");
на самом деле интерпретируется как
char str1 [ ] = "Hello, world\n"; printf (str1);
При размещении строки в памяти транслятор автоматически добавляет в ее конец символ ‘\0’, т.е. нулевой байт.
Количество элементов в таком массиве на 1 больше, чем в изображении соответствующей строковой константы, т.к. в конец строки добавили нулевой байт ‘\0’.
У строк есть еще особенность: транслятор отводит каждой строке отдельное место в памяти ЭВМ даже в тех случаях, когда несколько строк полностью совпадают (стандарт языка СИ предполагает, что в конкретных реализациях это правило может не выполняться).
Присвоить значение массиву символов (т.е. строке) с помощью обычного опера-
тора присваивания нельзя. Поместить строку в массив можно либо с помощью инициализации (при определении символьного массива), либо с помощью функций ввода. В функции scanf() или printf() для символьных строк используется спецификация преоб-
разования %s.
Пример:
/*печать символьной строки*/
#include<stdio.h> void main()
{char B[]=”Cезам, откройся!”; printf(“%s”,B); } /*конец программы*/
Результат выполнения программы: Сезам, откройся!
В программе длина массива В – 17 элементов, т.е. длина строки, помещаемой в массив (16 символов), плюс нулевой байт окончания строки. Именно 17 байтов выделя-
54
ется при инициализации массива в приведенном примере. Инициализация массива символов с помощью строковой константы представляет собой сокращенный вариант инициализации массива и введена в язык для упрощения. Можно воспользоваться обычной инициализацией, поместив начальные значения элементов массива в фигурные скобки и не забыв при этом поместить в конце списка начальных значений специальный символ окончания строки ‘\0’. Таким образом, в программе была допустима такая инициализа-
ция массива В:
char B[ ] = {‘C’,’е’,’з’,’а’,’м’,’,’,’ ’,’о’,’т’,’к’,’р’,’о’,’й’,’с’,’я’,’!’,’\0’};
Как вводить строки или выводить их на экран?
Выводиться строка может или уже известной вам функцией printf( ) со спецификатором ввода "%s", либо специальной функцией int puts (char *string), ко-
торая выводит строку string на экран и возвращает некоторое ненулевое значение в случае успеха.
Зачем нужен спецификатор "%s"? Это делается для того, чтобы можно было выводить строки с любыми символами. Сравните:
Пример:
char str [] = "Захотелось мне вывести %d..."; printf ("%s", str); /* Правильный вариант */
printf |
("\n"); |
/* Разделитель |
новой строки */ |
printf |
(str); |
/* Неправильный |
вариант */ |
В первом случае функция напечатает именно то, что от нее требуется. А вот во втором случае printf(), встретив в строке str спецификатор "%d" (ведь теперь эта строка – первая, значит, она задает формат вывода), сделает вывод, что за ней должно следовать число. А так как оно не следует, то вместо "%d" будет напечатан некоторый мусор – число, находящееся в тот момент в стеке.
Последствия могут быть и более серьезными, если в строке находится последовательность "%s", то сочтет ее за строку, и будет выводить ее до тех пор, пока не встретит нулевой символ. А где она его встретит, сколько успеет напечатать и не crash'нется ли изза обращения не к своей памяти – не знает никто.
Cоглашение о признаке окончания строки нужно соблюдать, формируя в программах строки из отдельных символов. В качестве примера рассмотрим следующую
Пример: Необходимо сохранить введенную строку.
/*чтение строки с терминала*/
int getline(char s, /*введенная строка*/ int lim) /*ее максимальная длина*/
{ int c, i; /* с – вводимый символ*/
for ( |
i=0; i<lim && (c = getchar() )!=EOF && c!=’\n’; |
|
i++ ) { |
s[i]=c; |
} |
|
||
s[i] = ’\0’; |
} |
|
return |
i; |
|
9.5Ввод–вывод строк. Основные функции работы со строками
55
Одной из наиболее популярных операций ввода-вывода является операция вводавывода строки символов. В библиотеку языка СИ для обмена данными через стандартные потоки ввода-вывода включены функции ввода-вывода строк gets() и puts(), которые удобно использовать при создании диалоговых систем.
Для ввода строки существует функция с прототипом char *gets (char *string),
которая считывает строку с клавиатуры и помещает ее в буфер string, указатель на который и возвращает. Если произошла ошибка, то возвращается EOF.
Функция имеет только один аргумент – указатель s на массив символов. Прототип функции описан в файле <stdio.h>.
Функция gets() завершает свою работу при вводе символа ‘\n’, который автоматически передается с клавиатуры в ЭВМ при нажатии клавиши <Enter>. При этом сам символ ‘\n’ во вводимую строку не записывается. Вместо него в строку помещается нуль–символ ‘\0’. Таким образом, функция gets() производит ввод “правильной” строки,
а не просто последовательности символов.
Здесь следует обратить внимание на следующую особенность ввода данных с клавиатуры. Функция gets() начинает обработку информации от клавиатуры только после нажатия клавиши <Enter>. Таким образом, она “ожидает”, пока не будет набрана нужная информация и нажата клавиша <Enter>. Только после этого начинается ввод
данных в программу.
Пример: #include <stdio.h> int main (void) {
char string[80];
printf ( "Input a string:" ); gets ( string );
printf ( "The string input was: %s\n", string ); return 0; }
Функция puts() (вывод строки на экран) в случае успешного завершения возвращает последний выведенный символ, который всегда является символом ‘\n', если произошла ошибка, то возвращается из функции EOF. Прототип этой функции имеет следующий вид:
int puts ( char*s ); /*функция вывода строки*/
Функция имеет только один аргумент – указатель s на массив символов. Прототип функции описан в файле <stdio.h>.
Приведем простейший пример использования этих функций.
#include<stdio.h>
char strl[ ] = ”введите фамилию сотрудника:”; int main ( ) {
char name[80]; puts(strl0; gets(name); return 0; }
Напомним, что любая строка символов в языке СИ должна заканчиваться нуль– символом ‘\0’. В последний элемент массива strl нуль–символ будет записан автомати-
56
чески во время трансляции при инициализации массива. Для функции puts() наличие нуль-символа в конце строки является обязательным.
В противном случае, т.е. при отсутствии в символьном массиве символа ‘\0’,
программа может завершиться аварийно, т.к. функция puts() в поисках нульсимвола будет перебирать всю доступную память байт за байтом, начиная, в нашем примере, с адреса strl. Об этом необходимо помнить, если в программе происходит
формирование строки для вывода ее на экран дисплея.
Пример: #include <stdio.h> int main ( void ) {
char string[] = "This is an example output string\n"; puts(string);
return 0;}
9.5Функции манипуляции со строками
Так как в Cи нет предопределенного типа для строки, то нет и столь привычных многим операций, как сравнения и склеивания строк, реализованных во многих языках как операторы сложения и сравнения. Здесь сложение массивов недопустимо, а при сравнении будут сравниваться не сами строки, а только указатели на них, что нам, конечно же, неинтересно.
Для манипуляций со строками существует набор функций, объявленных в файле <string.h> (те, кто пишет под Windows, могут включать вместо него файл <windows.h>).
Наиболее важные функции:
I. int strcmp (char *string1, char *string2)
осуществляет сравнение двух строк. Возвращает отрицательное число, если первая строка меньше второй, 0, если строки равны и положительное число, если первая строка больше второй. Более детально, функция возвращает разницу между кодами первых встретившихся неодинаковых символов (если строки неодинаковы по длине, то когда-то ненулевой символ будет сравниваться с нулем).
Пример:
#include <string.h>
#include <stdio.h> |
|
|
|
int main (void) { |
*buf2 = "bbb", |
*buf3 = "ccc"; |
|
char |
*buf1 = "aaa", |
||
int |
ptr; |
buf1); |
|
ptr = strcmp (buf2, |
|
||
if |
(ptr > 0) |
|
|
printf("buffer 2 is greater than buffer 1 \n" ); |
|||
else |
|
|
|
printf("buffer 2 is less than buffer 1 \n"); |
|||
ptr = strcmp( buf2, |
buf3 ); |
|
|
if (ptr > 0) |
|
buffer 3\n"); |
|
printf("buffer 2 is greater than |
|||
else
printf("buffer 2 is less than buffer 3\n" ); return 0; }
На экране появится:
57
buffer 2 is greater than buffer 1 buffer 2 is less than buffer 3
II. char *strcpy (char *dest, char *source)
осуществляет копирование строки source на место строки dest. Опять-таки позаботьтесь о том, чтобы вся строка поместилась в отведенном для нее месте. Функция возвращает указатель на строку-приемник.
Пример: #include <stdio.h>
#include <string.h> int main (void) { char string[10];
char *str1 = "a b c d e f g h i"; strcpy (string, str1);
printf( "%s \n", string); return 0; }
На экране появится: |
a b c d e f g h I |
III.char *strcat (char *string1, char *string2)
осуществляет склеивание двух строк. Вторая строка добавляется в конец первой. Функция не проверяет (да и не может проверять технически) наличие необходимого количества памяти в конце первой строки – об этом должны позаботиться вы. Функция
возвращает указатель на первую строку. |
|
|||
Пример: |
|
|
|
|
#include <string.h> |
|
|
||
#include <stdio.h> |
{ |
|
||
int main(void) |
|
|||
char |
destination[25]; |
|
||
char |
*blank = " ", |
*c = "C++", *turbo = "Turbo"; |
||
strcpy (destination, turbo); |
//Копирование строки "turbo" |
|||
на место destination |
|
// Склеивание destination … |
||
strcat (destination, blank); |
||||
strcat (destination, c); |
// сначала с blank, потом с c |
|||
printf("%s\n", destination); |
|
|||
return 0; } |
|
|
|
|
На экране появится: |
Turbo C ++ |
|
||
IV. int strlen |
(char *string) |
|||
возвращает длину строки string (не считая нулевого символа).
V. char *strdup (char *string)
создает дубликат строки string и возвращает указатель на него. Учтите, что в отличие от остальных функций, strdup сама создает строку и поэтому после того, как она стала вам не нужна, не забывайте ее освободить.
VI. char *strncpy ( char *dest, char *source, int count) VII. char *strncat ( char *string1, char *string2, int
count)
58
аналогично strcpy и strcat, но копируются только первые count символов. Функции не добавляют к строке завершающего нуля – вам придется сделать это самим.
VIII. char *strchr (char *string, int c)
IX. char *strstr (char *string, char *substring)
ищут первое вхождение в строку string соответственно символа c и подстроки substring. Обе функции возвращают адрес первого вхождения или NULL, если такового не найдено.
Пример: |
(void) |
{ |
|
int |
main |
||
char |
string[15]; |
|
|
char |
*ptr, |
c = 'r'; |
|
//Создаем |
строку |
"This is a string"); |
|
strcpy (string, |
|||
ptr = strchr(string, c); |
|||
if (ptr)
printf("The character %c is at position: %d\n", c, ptrstring);
else
printf( "The character was not found\n" ); return 0; }
На экране появится: The character r is at position: 12
59
Глава 10 ФAЙЛЫ
Файл характеризуется именем, расширением, версией.
MS DOS: длина имени <=8 символов, длина расширения <=3 символов. Файл: = <имя> [.<расширение>]
Proba.c
Устройство внешней памяти, строго говоря, является устройством поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующих в обмене с внешней памятью, являются блоки в 512 или 1024 байтов.
При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя.
При вводе данных в файл они накапливаются в буфере, а при заполнении буфера они записываются в виде единого блока на диск за одно обращение к последнему.
чтение из файла
Жесткий диск |
Буфер |
ОЗУ |
данные
ввод в файл
Жесткий диск |
Буфер |
ОЗУ |
данные
Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылка между буферами ввода–вывода и выполняемой программой происходит достаточно быстро в отличие от реальных обменов с физическими устройствами.
Функции библиотеки ввода-вывода языка СИ, поддерживающие обмен данными с файлом на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизированный ввод и вывод. Таким образом,
ПОТОК – ЭТО ФАЙЛ ВМЕСТЕ С ПРЕДОСТАВЛЯЕМЫМИ СРЕДСТВАМИ БУФЕРИЗАЦИИ.
ФАЙЛ + СРЕДСТВА_БУФЕРИЗАЦИИ = ПОТОК
При работе с потоком можно производить следующие действия:
•Открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами).
•Вводить и выводить: символ, строку, форматированные данные, порцию данных произвольной длины.
60
•Анализировать ошибки потокового ввода–вывода и условие достижения конца потока (конца файла).
•Управлять буферизацией потока и размером буфера.
•Получать и устанавливать указатель (индикатор) текущей позиции в потоке. Для того, чтобы можно было использовать функции библиотеки ввода–вывода язы-
ка СИ, в программу необходимо включить заголовочный файл stdio.h (#include<stdio.h>), который содержит прототипы функций ввода–вывода, а также описания констант, типов и структур, необходимых для работы функций обмена с потоком.
12.1Открытие и закрытие потока
Подготовительные операции перед обменом программы с файлом:
1.Проверка наличия файла с данным именем при чтении.
2.Наличие свободного пространства на диске при создании файла.
3.Позиционирование файла.
При благополучном открытии файла ОС возвращает целое число, характеризующее этот поток и называемое ДЕСКРИПТОМ файла (потока).
FORTRAN READ(5,…) WRITE(6,…)
Процесс отсоединения файла от программы после окончания операций с ним называется закрытием файла. При этом освобождаются ресурсы (в основном память). С каждым потоком связан системный буфер, который для MS DOS составляет 512 байтов. Дескриптор – это тоже ресурс, поскольку их число для MS DOS ограничено 255. Также для каждого файла в памяти хранится его описание в виде структуры типа FILE, описанного в файле stdio.h.
РЕКОМЕНДАЦИЯ. Желательно закрывать файл явно по причинам:
1.Случайной порчи открытого файла.
2.Возможного повторного открытия того же файла с другим способом обработки.
3.При аварийном завершении программы информация, хранимая в системном буфере пропадает.
4.Возможность сэкономить на количестве открываемых потоков.
Прежде чем начать работать с потоком, его необходимо инициализировать, т.е. открыть. При этом поток связывается в исполняемой программе со структурой предопределенного типа FILE. Для каждого файла в памяти хранится его описание в виде структуры типа FILE. Определение структурного типа FILE находится в заголовочном файле stdio.h. В структуре FILE находятся компоненты, с помощью которых ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и друга информация.
При открытии потока в программу возвращается указатель на поток, являю-
щийся указателем на объект структурного типа FILE. Этот указатель идентифицирует поток во всех последующих операциях.
Указатель на поток, например fp, должен быть (1) объявлен в программе следующим образом:
#include <stdio.h> FILE *fp;
61
Указатель на поток (2) приобретает значение в результате выполнения функцией открытия потока:
Формат:
<указатель_на_поток> = fopen (<имя_файла>, <режим_открытия>);
Параметры <имя_файла> и <режим_открытия> являются указателями на массивы символов (строки!), содержащие, соответственно, имя файла, связанного с потоком, и строку режимов открытия. Однако, эти параметры могут задаваться и непосредственно в виде строк при вызове функции открытия файла.
Например: fp = fopen ( “t.txt”, ”r” );
где t.txt –имя некоторого файла, связанного с потоком; r – обозначение одного из режимов работы с файлом (тип доступа к потоку).
Стандартный текстовый файл можно открыть в одном из следующих шести
режимов:
“w”–новый текстовый (см. ниже) файл открывается для записи. Если файл уже существовал, то предыдущее содержимое стирается и файл создается заново.
“r” – существующий текстовый файл открывается только для чтения.
“a” – текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции информации (добавление в конец файла). В отличие от режима “w” режим “a” позволяет открывать уже существующий файл, не уничтожая его предыдущей версии, и писать в продолжение файла.
“w+” – новый текстовый файл откравается для записи и последующих многократных исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешается и в конец файла, т.е. файл может увеличиваться
(“расти”).
“r+” – существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла, однако в этом режиме невозможна запись в конец файла, т.е. недупустимо увеличение размеров файла.
“a+” – текстовый файл открывается или создается (если файла нет) и становится доступным для изменений, т.е. для записи и для чтения в любом месте; при этом в отличие от режима “w+” можно открыть существующий файл и не уничтожать его содержимое; в отличие от режима “r+” в режиме “a+” можно ввести запись в конец файла, т.е. увеличивать его размер.
Поток можно открыть в текстовом или двоичном (бинарном) режиме.
Втекстовом режиме прочитанная из потока комбинация символов CR (значение
13)и LF (значение 10), т.е. управляющие коды операции “возврат каретки” и “перевод
строки”, преобразуется в один новый символ ‘\n’ (значение 10, совпадающее с LF). При записи в поток в текстовом режиме осуществляется обратное преобразование, т.е. символ новой строки ‘\n’ (LF) заменяется последовательностью CR и LF.
Если файл, связанный с потоком, хранит не текстовую, а произвольную двоичную информацию, то указанные преобразования не нужны и могут быть даже вредными. Обмен без такого преобразования выполняется при выборе двоичного или бинарного
режима, который обозначается буквой b.
Например “r+b” или “wb”. В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывают “a+t” или “rt”.
62
Если поток открыт для изменений, т.е. в параметре режима присутствует символ “+”, то разрешены как чтение из потока так и запись в него. Однако смена режима (переход от записи к чтению и обратно) должна происходить только после установки указателя потока в нужную позицию.
При открытии потока могут возникнуть следующие ОШИБКИ:
1.Указанный файл, связанный с потоком, не найден (для режима чтения).
2.Диск заполнен или защищен от записи и т.п.
3.Необходимо также отметить, что при выполнении функции fopen() происходит выделение динамической памяти. При ее отсутствии устанавливается признак ошибки ”Not entough memory” (недостаточно памяти).
В перечисленных случаях указатель на поток приобретает значение NULL. Заметим. Что указатель на поток в любом режиме, отличном от аварийного, никогда не бывает равным NULL.
Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком:
if ( ( fp=fope(“t.txt”,”w”)) = = NULL ) { perror(“ошибка при открытии файла t.txt \n”); exit (0); }
где NULL – нулевой указатель, определенный в файле stdio.h.
Для вывода на экран дисплея сообщения об ошибке при открытии потока используется стандартная библиотечная функция perror( ), прототип которой в stdio.h имеет вид:
void perror ( const char *s);
Функция perror( ) выводит строку символов, адресуемую указателем s, за которой размещаются: двоеточие, пробел и сообщение об ошибке. Содержимое и формат сообщения определяются реализацией системы программирования. Текст сообщения об ошибке выбирается функцией perror() на основании номера ошибки. Номер ошибки заносится в переменную int erno (определяется в заголовочном файле erno.h) рядом функций библиотеки языка СИ, в том числе и функциями ввода-вывода.
После того, как файл открыт, с ним можно работать, записывая в него информацию или считывая ее (в зависимости от режима).
Открытые на диске файлы рекомендуется ЗАКРЫВАТЬ ЯВНО. Для этого используется библиотечная функция.
int fclose (<указатель_на_поток>) ;
Открытый файл можно открыть повторно ( например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose().
Пример:
FILE *f, *fp;
if (( f=fopen (“inp3.txt”,”rt”)) = = NULL ) { printf(“cannot open input file.\n”); return 1; }
fscanf ( f, ”%d”, &count ); fclose ( f );
if ( !(fp = fopen ("inp3.txt", "r") ) ) exit 0;
fscanf ( fp, ”%d”, &razruad); fclose ( fp );
63
12.2Работа с файлами на диске
Аналогичным образом, как это делается со стандартными потоками ввода–вывода, можно осуществлять работу с файлами на диске. Для этой цели в библиотеке языка СИ включены следующие функции:
fgetc( ) –ввод (чтение) одного символа из файла. fputc( ) –запись одного символа в файл. fprintf( ) –форматированный вывод в файл.
fscanf( ) –форматированный ввод (чтение) из файла.
fgets( ) –ввод (чтение) строки из файла. int fgets(FILE *stream); fputs( ) –запись одной строки в файл.
1.int fgetc(FILE *stream ) –чтение одного символа из файла.
int fputc(int c, FILE *stream) – запись одного символа в файл.
Например:
void main(void) { FILE *stream; char ch;
stream=fopen(“DYMMY.FIL”, “+w”); do{ ch=fgetc(stream);
putch(ch); } while (ch != EOF);
fclose(stream); }
Например:
#include<stdio.h> void main(void)
{ char msg[]=“hello, world”; int i=0;
while(msg[i])
fputc(msg[i], stdout); }
2. fprintf( ) –форматированный вывод в файл. fscanf( ) –форматированный ввод (чтение) из файла.
Например:
#include<stdio.h> void main(void) { FILE *stream; int i=100;
float f= 1.2345; char c=‘C’;
stream=fopen(“DYMMY.FIL”, “w+”); fprintf(stream, “%d %c %.4f”, i, c, f); fclose(stream); }
3. char *fgets(char *s, int n, FILE *stream) – функция чтения строки из файла. int fputs(const char *s, FILE *stream) – функция записи строки в поток.
64
Например:
void main (void) {FILE *stream; char msg[20];
stream=fopen(“DYMMY.FIL”, “w+”); fputs(“\nHello, WORLD”, stream); fclose(stream); stream=fopen(“DYMMY.FIL”, “r”); fgets(msg, strlen(str)+1, stream); printf(“%s”, msg); fclose(stream); }
В качестве примера использования функций getс() и putс() рассмотрим программы ввода данных в файл с клавиатуры и программу вывода их на экран дисплея из файла.
Программа ввода читает символы с клавиатуры и записывает их в файл.
Пусть признаком завершения ввода служит поступивший от клавиатуры символ
“#”.
Имя файла запрашивается у пользователя.
Если при вводе последовательности символов была нажата клавиша <Enter>, служащая разделителем строк при вводе с клавиатуры, то в файл записываются коды “возврат каретки” (CR – значение 13) и “перевод строки” (LF – значение 10). Код CR в дальнейшем при вызове вызывает перевод маркера (курсора) в начало строки экрана дисплея. Код LF служит для перевода маркера на новую строку дисплея. Значения этих кодов в тексте программы обозначены соответственно идентификаторами CR и LF, т.е. CR и LF
– именованные константы. Запись управляющих кодов CR и LF в файл позволяет при последующем вызове файла на экран отделить строки друг от друга.
Пример:
/*программа записи (ввода) символов в поток (файл)*/
#include<stdio.h> int main(){
FILE *fp;/*указатель на поток*/
char c, fname[20]; /*массив для имени файла*/
const char CR=‘\015’, /*восьмиричный код возврата карет-
ки*/ |
/*восьмеричный код перевода строки*/ |
||
LF=‘\012’; |
|||
/*запрос имени файла*/ |
|
|
|
puts (“Введите имя файла:\n”); |
|
||
gets(fname); |
для |
записи*\ |
|
/*открыть файл |
|
||
if ((fp=fopen(fname, “w”)) = = NULL) { |
|||
perror(fname); |
|
|
|
return 1; |
} |
|
|
/*цикл ввода и записи в файл символов*\ |
|||
while ((c=getch() ) != ‘#’) |
{ |
||
if ( c == ‘\n’) |
{ |
|
|
fputc(CR,fp);
65
fputc(LF,fp); } else { fputc(c,fp); putc(c); } }
/* цикл ввода завершен; закрыть поток: */ fclose (fp);
return 0; }
Пример:
/*программа вывода потока (файла) на экран дисплея*/
#include<stdio.h> int main(void) {
FILE *fp;/*указатель на поток*/ char c;
char fname[20]; /*массив для имени файла*/ /*запрос имени файла*/
puts(“введите имя файла:\n”); gets (fname);
/*открыть файл для чтения*/
if ( (fp=fopen(fname, “r”) ) == NULL)
{perror(fname);
return 1; }
/*цикл чтения из файла и вывода символов на экран*/ while ( (c=fgetc(fp) ) != EOF )
putchar (c);
fclose(fp); } /*закрыть файл*/
ЗАКЛЮЧИТЕЛЬНЫЙ ПРИМЕР ПО ТЕМЕ "ФАЙЛЫ"
//Дан символьный файл f.dat. Записать в файл:
//h_otr.dat все отрицательные компоненты исходного файла,
//h_por.dat компоненты исходного файла, упорядоченные по возрастанию,
//h_sum.dat сумму, произведение и сумму квадратов компонент исходного
файла.
# include <stdio.h> void main ()
{ int a[100], // массив компонент исх. файла perest, i, j, // вспомогательные переменные kol, // количество компонент исх. файла
sum, pr, sum_2; // Сумма, произведение и сумма квадратов
FILE *fil, *f;
// Очищаем файлы h_otr.dat, и h_por.dat, и h_sum.dat fil = fopen("h_otr.dat", "w");
fclose(fil);
fil = fopen("h_por.dat", "w"); fclose(fil);
fil = fopen( "h_sum.dat", "w" ); fclose (fil);
// Считываем из файла f.dat только отрицательные
66
// компоненты и заносим их в файл h_otr.dat
if ( ( fil = fopen ( "f.dat", "r" ) ) = = NULL)
{puts( " Ошибка " ); exit (0); }
f = fopen( "h_otr.dat", "w" );
while ( fscanf ( fil, "%d", & kol ) != EOF ) if ( kol < 0) fprintf ( f, "%d\n", kol );
fclose ( fil ); fclose ( f );
//Упорядочиваем компоненты исходного файла
//по возрастанию заносим их в файл h_por.dat if ( (
fil = fopen ( "f.dat", "r" )
) = = NULL)
{ puts( " Ошибка "); exit (0); }
i = 0;
while ( ( a[i] = fgetc (fil) ) != EOF) i ++;
fclose (fil);
kol = i;
for ( i = 0; i < kol; i++ ) {
for ( j = i; j < kol – 1; j++ ) { if ( a[j] > a[j+1] ) {
perest = a[j]; a[j] = a[j+1];
a[j+1] = perest; } } }
fil = fopen( "h_por.dat", "w" ); for ( i = 0; i < kol; i++ )
fputc ( a[i], fil); fclose (fil);
/* Находим сумму, произведение и сумму квадратов компонент файла
f.dat, заносим их в файл h_sum.dat */
f = fopen( “h_sum.dat”, “w” ); sum = sum_2 = 0; pr = 1;
fil = fopen (“f.dat”, “r” );
for(i=0; ( fscanf(f, “%d”, &a[i])!=EOF); i++ ) ;
for(i=0; i<kol; i++ ) |
{ |
sum += a[i]; |
|
pr *= a[i]; |
} |
sum_2 += a[i]*a[i]; |
|
fprintf(fil, “sum = %d |
\t pr = %d \t sum_2 = %d \n”, sum, |
pr, sum_2); |
|
fclose (fil); } |
|
67
