Средства ввода-вывода в Си++

Введение.	2

	Общие  положения.	3

	Потоковый ввод-вывод	9

	Форматный ввод-вывод.	13

	Форматный ввод из входного потока.	15

	Литература	17

	

	
Введение.

	

	В стандарте языка Си отсутствуют средства ввода-вывода. Все операции ввода-вывода реализуются с помощью функций, находящихся в библиотеке языка Си, поставляемой в составе конкретной системы программирования Си. Во время работы с файлами данные могут передаваться или в своем внутреннем двоичном представлении или в текстовом формате, то есть в более удобочитаемом виде.

	Особенностью языка Си, который впервые был применен три разработке операционной системы UNIX, является отсутствие заранее спланированных структур файлов. Все файлы рассматриваются как неструктурированная последовательность байтов. При таком подходе к организации файлов удалось распространить понятие файла и на различные устройства. В UNIX конкретному устройству соответствует так называемый "специальный файл", а одни и те же функции библиотеки языка Си используются как для обмена данными с файлами, так и для обмена с устройствами. 

	Библиотека языка Си поддерживает три уровня ввода-вывода: потоковый ввод-вывод, ввод-вывод нижнего уровня и ввод-вывод для консоли и портов. Последний уровень, обеспечивающий удобный специализированный обмен данными с дисплеем и портами ввода-вывода, мы рассматривать не будем в силу его системной зависимости. Например, он различен для MS-DOS, Windows и UNIX. 

	
Общие положения.

	

	Язык Си является фундаментом С++. При этом С++ поддерживает всю файловую систему Си. Поэтому при использовании С-кода в С++ нет необходимости менять процедуры ввода-вывода. Хотя при написании программ на С++ обычно более удобно использовать именно систему С++. Это касается, в частности, и использования "iostream.h" взамен "stdio.h", реализующим ввод-вывод. Изучим  файловый ввод-вывод в языке Си. Тем более, что это само по себе очень интересно и очень важно для понимания "потоков" и "файлов" как в Си, так и в С++. 

	В системе ввода-вывода в Си для программ поддерживается единый интерфейс, не зависящий от того, к какому конкретному устройству осуществляется доступ. То есть в Си между программой и устройством находится нечто более общее, чем само устройство. Такое обобщенное устройство ввода или вывода (устройство более высокого уровня абстракции) называется потоком. В то же время конкретное устройство называется файлом. Наша задача - понять, каким обрзом происходит взаимодействие потоков и файлов. 

	Файловая система Си предназначена для работы с разными устройствами, в том числе с терминалами, дисководами и накопителями. Даже, если какое-то устройство очень сильно отличается от других устройств, буферизованная файловая система все равно представит его в виде логического устройства, которое называется потоком. Все потоки ведут себя похожим образом. И так как они в основном не зависят от физических устройств, то та же функция, которая выполняет запись в дисковый файл, может ту же операцию выполнить и на другом устройстве. Например, на консоли. Потоки бывают двух видов: текстовые и двоичные. 

	В языке Си файлом может быть все, что угодно, начиная в дискового файла и заканчивая терминалом или принтером. Поток связывают с определенным файлом, выполняя обязательную операцию открытия. Как только файл открыт, можно проводить обмен информацией между ним и программой. 

	Но не у всех файлов одинаковые возможности. Например, к дисковому файлу прямой доступ возможен, в то время как к некоторым принтерам - он не возможен. Таким образом, вы видите, что напрашивается определенный вывод, являющийся принципом системы ввода-вывода языка Си: все потоки одинаковы, а файлы - нет! 

	Если файл может поддерживать запросы на местоположение (указатель текущей позиции), то при открытии такого файла указатель текущей позиции в файле устанавливается в начало файла. При чтении каждого символа из файла (или записи в файл) указатель текущей позиции увеличивается. Тем самым обеспечивается продвижение по файлу. 

	Файл отсоединяется от определенного потока (то есть разрывается связь между файлом и потоком) с помощью операции закрытия файла. При закрытии файла, открытого с целью вывода, содержимое (если оно, конечно, есть) связанного с ним потока записывается на внешнее устройство. Этот процесс обычно называют дозаписью потока. При этом гарантируется, что никакая информация случайно не останется в буфере диска. 

	Если программа завершает работу нормально, то есть либо main() возвращает управление операционной системе, либо выход происходит через exit(), то все файлы закрываются автоматически. 

	В случае же аварийного завершения работы программы, например, в случа краха или завершения путем вызова abort(), файлы не закрываются. 

	У каждого потока, связанного с файлом, имеется управляющая структура, содержащая информацию о файле. Она имеет тип FILE. Блок управления файлом - это небольшой блок памяти, временно выделенный операционной системой для хранения информации о файле, который был открыт для использования. Блок управления файлом обычно содержит информацию об идентификаторе файла, его расположении на диске и указателе текущей позиции  в файле. 

	Для выполнения всех операций ввода-вывода следует использовать только понятия потоков и применять всего лишь одну файловую систему. Ввод или вывод от каждого устройства автоматически преобразуется системой в легко управлемый поток. И это является достижением  языка Си. 

	Таковы основополагающие замечания относительно существования различных потоков информации и связанных с ними файлов. 

	Файловая система языка Си состоит из нескольких взаимосвязанных между собой функций. Для их работы в Си требуется заголовочный файл <stdio.h> и такой же аналогичный ему заголовочный файл <iostream.h> требуется для работы в С++. 

	Ниже приведена таблица основных (часто используемых) функций файловой системы языка Си. 

	

	Имя	

	Что делает эта функция

	Имя

	Что делает эта функция

	fopen()

	Открывает файл

	feof()

	Возвращает значение true (истина), если достигнут конец файла

	fclose()

	Закрывает файл

	ferror()

	Возвращает значение true (истина), если произошла ошибка

	putc()

	Записывает символ в файл

	remove()

	Стирает файл	

	

	fputc()

	То же, что и putc()

	fflush()

	Дозапись потока в файл

	getc()

	Читает символ из файла

	rewind()

	Устанавливает указатель текущей позиции в начало файла	

	fgetc()

	То же, что и getc()

	ftell()

	Возвращает текущее значение указателя текущей позиции в файле

	fgets()

	Читает строку из файла

	fprintf()

	Для файла то же, что printf() для консоли

	fputs()

	Записывает строку в файл

	fscanf()

	Для файла то же, что scanf() для консоли

	fseek()

	Устанавливает указатель текущей позиции на определенный байт файла	

	

	

	

	Заголовок <stdio.h> представляет прототипы функций ввода-вывода в Си и определяет следующие три типа: size_t, fpos_t и FILE. Первые два: size_t, fpos_t представляют собой разновидности такого типа, как целое без знака. Отдельно рассмотрим третий тип: FILE. 

	Указатель файла - это то, что соединяет в единое целое всю систему ввода-вывода языка Си. Указатель файла - это указатель на структуру типа FILE. Он указывает на структуру, содержащую различные сведения о файле, например, его имя, статус, и указатель текущей позиции в начало файла. В сущности указатель файла определяет конкретный файл и используется соответствующим потоком  при выполнении   функции ввода-вывода. 

	Чтобы выполнять в файлах операции чтения и записи, программы должны использовать указатели соответствующих файлов. Чтобы объвить переменную-указатель файла необходимо использовать следующий оператор: 

	FILE *fp; 

	Функция fopen() открывает поток и связывает с этим потоком файл. Затем она возвращает указатель этого файла. Прототип функции имеет вид:

	FILE *fopen(const char *имя_файла, const char *режим); 

	Здесь имя_файла - это указатель на строку символов, представляющую собой допустимое имя файла, в которое может входить спецификация файла (включает обозначение логического устройства, путь к файлу и собственно имя файла). 

	Режим - определяет, каким образом файл будет открыт. Ниже в таблице показаны допустимые значения режимов. 

	

	Режим

	Что обозначает данный режим

	r

	Открыть текстовый файл для чтения

	w

	Создать текстовый файл для записи

	a

	Добавить в конец текстового файла

	wb

	Создать двоичный файл для записи

	rb

	Открыть двоичный файл для чтения

	ab

	Добавить в конец двоичного файла

	r+

	Открыть текстовый файл для чтения/записи

	w+

	Создать текстовый файл для чтения/записи

	a+	

	Добавить в конец текстового файла или создать текстовый файл для чтения/записи

	r+b

	Открыть двоичный файл для чтения/записи

	w+b

	Создать двоичный файл для чтения/записи

	a+b

	Добавить в конец двоичного файла или создать двоичный файл для чтения/записи	

	

	Приведем фрагмент программы, в котором используется функция fopen() для открытия файла по имени TEST. 

	FILE *fp;

	fp = fopen("test", "w");

	Следует сразу же указать на недостаточность такого кода в программе. Хотя приведенный код технически правильный, но его обычно пишут немного по-другому. 

	FILE *fp;

	if ((fp = fopen("test", "w")==NUL)

	{

	printf("Ошибка при открытии файла.\n\r")"

	exit(1);

	}

	

	

	Рис. 1

	

	Этот метод помогает при открытии файла обнаружить любую ошибку. 

	Например, защиту от записи или полный диск. Причем, обнаружить еще до того, как программа попытается в этот файл что-то записать. Поэтому всегда нужно вначале получить подтверждение, что функция fopen() выполнилась успешно, и лишь затем выполнять c файлом другие операции. Ниже на рисунке 1 приведена небольшую часть программы, которая. подтверждает или не подтверждает открытие файла. Результаты работы указанной программы приведены на рисунке 2.

	

	

	Рис. 2.

	
Потоковый ввод-вывод

	

	На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, строго говоря, являются устройствами поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт или 1024 байта. При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой программой происходят достаточно быстро в отличие от реальных обменов с физическими устройствами. 

	Функции библиотеки ввода-вывода языка Си, поддерживающие обмен данными с файлами на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизованный ввод и вывод. Таким образом, поток - это файл вместе с предоставляемыми средствами буферизации. 

	При работе с потоком можно производить следующие действия: 

	открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами); 

	вводить и выводить: символ, строку, форматированные данные, порцию данных произвольной длины; 

	анализировать ошибки потокового ввода-вывода и условие достижения конца потока (конца файла); 

	управлять буферизацией потока и размером буфера; 

	получать и устанавливать указатель (индикатор) текущей позиции 

	При открытии потока могут возникнуть следующие ошибки: указанный файл, связанный с потоком, не найден (для режима "чтение"); диск заполнен или диск защищен от записи и т.п. Необходимо также отметить, что при выполнении функции fopen() происходит выделение динамической памяти. При её отсутствии устанавливается признак ошибки "Not enough memory" (недостаточно памяти). В перечисленных случаях указатель на поток приобретает значение NULL. Заметим, что указатель на поток в любом режиме, отличном от аварийного никогда не бывает равным NULL. 

	Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком: 

	if ((fp = fopen("t.txt","w")) == NULL) 

	perror("ошибка при открытии файла t.txt \n"); 

	exit(0); 

	} 

	Где NULL - нулевой указатель, определенный в файле stdio.h. 

	Открытые на диске файлы после окончания работы с ними рекомендуется закрыть явно. Для этого используется библиотечная функция 

	int fclose (указатель_на_поток); 

	Открытый файл можно открыть повторно (например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose(). 

	Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются: 

	стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток stdin); 

	стандартный поток вывода (stdout); 

	стандартный поток вывода сообщений об ошибках (stderr). 

	По умолчанию стандартному потоку ввода stdin ставится в соответствие клавиатура, а потокам stdout и stderr соответствует экран дисплея. 

	Одним из наиболее эффективных способов осуществления ввода-вывода одного символа является использование библиотечных функций getchar( ) и putchar(). Прототипы этих функций имеют следующий вид: 

	int getchar(void); 

	int putchar(int c); 

	Функция getchаr( ) осуществляет ввод одного символа. При обращении она возвращает в вызвавшую ее функцию один введенный символ. 

	Функция putchar( ) выводит в стандартный поток один символ, при этом также возвращает в вызвавшую ее функцию только что выведенный символ. 

	Обратите внимание на то, что функция getchar( ) вводит очередной байт информации (символ) в виде значения типа int. Это сделано для того, чтобы гарантировать успешность распознавания ситуации "достигнут конец файла". Дело в том, что при чтении из файла с помощью функции getchar() может быть достигнут конец файла. В этом случае операционная система в ответ на попытку чтения символа передает функции getchar() значение EOF (End of File). Константа EOF определена в заголовочном файле stdio.h и в разных операционных системах имеет значение 0 или -1. Таким образом, функция getchar() должна иметь возможность прочитать из входного потока не только символ, но и целое значение. Именно с этой целью функция getchar( ) всегда возвращает значение типа int. 

	В случае ошибки при вводе функция getchar() также возвращает EOF. 

	При наборе текста на клавиатуре коды символов записываются во внутренний буфер операционной системы, Одновременно они отображаются (для визуального контроля) на экране дисплея. Набранные на клавиатуре символы можно редактировать (удалять и набирать новые). Фактический перенос символов из внутреннего буфера в программу происходит при нажатии клавиши . При этом код клавиши также заносится во внутренний буфер. Таким образом, при нажатии на (Клавишу 'А' и клавишу (завершение ввода) во внутреннем буфере оказываются: код символа 'А' и код клавиши . ) Об этом необходимо помнить, если вы рассчитываете на ввод функцией getchar() одиночного символа. 

	Приведём в пример программу копирования из стандартного ввода в стандартный вывод: 

	#include 

	int main()

	{

	int c;

	while ((c=getchar())!=EOF)

	Putchar(c);

	return 0;

	}

	Для завершения приведенной выше программы копирования необходимо ввести с клавиатуры сигнал прерывания Ctrl+C. 

	Одной из наиболее популярных операций ввода-вывода является операция ввода-вывода строки символов. В библиотеку языка Си для обмена данными через Стандартные потоки ввода-вывода включены функции ввода-вывода строк gets() и puts(), которые удобно использовать при создании диалоговых систем. Прототипы этих функций имеют следующий вид: 

	char * gets (char * s);  /* Функция ввода */ 

	int puts (char * s); /* Функция вывода */

	Обе функции имеют только один аргумент - указатель s на массив символов. Бели строка прочитана удачно, функция gets( ) возвращает адрес того массива s, в который производился ввод строки. Если произошла ошибка, то возвращается NULL. 

	Функция puts() в случае успешного завершения возвращает последний выведенный символ, который всегда является символом ‘\n’. Если произошла ошибка, то возвращается EOF. 

	
Форматный ввод-вывод.



	Для работы со стандартными потоками в режиме форматного ввода-вывода определены две функции: 

	printf( ) - форматный вывод; 

	scanf( ) - форматный ввод. 

	Прототип функции printf() имеет вид: 

	int printf(const char *format,...); 

	При обращении к функции printf() возможны две формы задания первого параметра: 

	int printf ( *форматная строка, список_аргументов); 

	int printf (указателъ_на_форматную_строку,. список_аргументов); 

	В обоих случаях функция printf() преобразует данные из внутреннего представления в символьный вид в соответствии с форматной строкой и выводит их в выходной поток. Данные, которые преобразуются и выводятся, задаются как аргументы функции printf(). 

	Возвращаемое значение функции printf() - число напечатанных символов; а в случае ошибки - отрицательное число. 

	Форматная_строка ограничена двойными кавычками и может включать произвольный текст, управляющие символы и спецификации преобразования данных. Текст и управляющие символы из форматной строки просто копируются в выходной поток. Форматная строка обычно размещается в списке фактических параметров функции, что соответствует первому варианту вызова функции printf(). Второй вариант предполагает, что первый фактический параметр - это указатель типа char *, a сама форматная строка определена в программе как обычная строковая константа или переменная. 

	В список аргументов функции printf() включают выражения, значения которых должны быть выведены из программы. Частные случаи этих выражений - переменные и константы. Количество аргументов и их типы должны соответствовать последовательности спецификаций преобразования в форматной строке. Для каждого аргумента должна быть указана точно одна спецификация преобразования. 

	Если аргументов недостаточно для данной форматной строки, то результат зависит от реализации (от операционной системы и от системы программирования). Если аргументов больше, чем указано в форматной строке, "лишние" аргументы игнорируются. Гарантируется, что при любом количестве параметров и любом их типе после выполнения функций printf() дальнейшее выполнение программы будет корректным. 

	

	Спецификация преобразования имеет следующую форму:

	

	% флаги ширина_поля. точностъ спецификатор 

	Символ % является признаком спецификации преобразования. В спецификации преобразования обязательными являются только два элемента: признак % и спецификатор. 

	Спецификатор	Тип аргумента	Формат вывода	

	d	int, char,unsigned	Десятичное целое со знаком	

	u	int, char,unsigned	Десятичное целое без знака	

	o	int, char,unsigned	Восьмеричное целое без знака

	x	int, char,unsigned	Шестнадцатеричное целое без знака; при выводе используются символы “0..9a..f”

	X	int, char,unsigned	Шестнадцатеричное целое без знака; при выводе используются символы "0...9A...F”

	f	double, float	Вещественное значение со знаком в виде: 

	Знак_числа dddd.dddd 

	где dddd - одна или более десятичных цифр. Количество цифр перед десятичной точкой зависит от величины выводимого числа, а количество цифр после десятичной точки зависит от требуемой точности. Знак числа при отсутствии модификатора '+' изображается только для отрицательного числа. 

	
Форматный ввод из входного потока.

	

	Форматный ввод из входного потока осуществляется функцией scanf(). Прототип функции scanf( ) имеет вид: 

	int scanf(const char * format, …); 

	При обращении к функции scanf() возможны две формы задания первого параметра: 

	int scanf ( форматная строка, список аргументов ); 

	int scanf(указатель_на_форматную _строку, список_аргументов); 

	Функция scanf() читает последовательности кодов символов (байты) из входного потока и интерпретирует их в соответствии с форматной_строкой как целые числа, вещественные числа, одиночные символы, строки. В первом варианте вызова функции форматная строка размещается непосредственно в списке фактических параметров. Во втором варианте вызова предполагается, что первый фактический параметр - это указатель типа char *, адресующий собственно форматную строку. Форматная строка в этом случае должна быть определена в программе как обычная строковая константа или переменная. 

	После преобразования во внутреннее представление данные записываются в области памяти, определенные аргументами, которые следуют за форматной строкой. Каждый аргумент должен быть указателем на переменную, в которую будет записано очередное значение данных и тип которой соответствует типу, указанному в спецификации преобразования из форматной строки. 

	Если аргументов недостаточно для данной форматной строки, то результат зависит от реализации (от операционной системы и от системы программирования). Если аргументов больше, чем требуется в форматной строке, "лишние" аргументы игнорируются. 

	Последовательность кодов символов, которую функция scanf() читает, из входного потока, как правило, состоит из полей (строк), разделенных символами промежутка или обобщенными пробельными символами. Поля просматриваются и вводятся функцией scanf() посимвольно. Ввод поля прекращается, если встретился пробельный символ или в спецификации преобразования точно указано количество вводимых символа. 

	Функция scanf() завершает работу, если исчерпана форматная строка. При успешном завершении scanf() возвращает количество преобразованных и введенных полей (точнее, количество объектов, получивших значения при вводе). Значение EOF возвращается при возникновении ситуации "конец файла"; значение -1 - при возникновении ошибки преобразования данных. 

	Рассмотрим форматную строку функции scanf(): "code: %d %*s %c %s" 

	Строка "code:" присутствует во входном потоке для контроля вводимых данных и поэтому указана в форматной строке. Спецификации преобразования задают следующие действия: 

	%d - ввод десятичного целого; 

	%*s - пропуск строки; 

	%с - ввод одиночного символа; 

	%s - ввод строки. 

	Приведем результаты работы программы для трех различных наборов входных данных. 

	1.Последовательность символов исходных данных: 

	code:    5    поле2    D   asd

	Результат выполнения программы: 

	i=5    c=D    s=asd    ret=3

	Значением переменной ret является код возврата функц»-scanf(). Число 3 говорит о том, что функция scanf() ввела данные без ошибки и было обработано 3 входных поля (строки "code:" и "поле2" пропускаются при вводе). 

	
Заключение.

	

	В данной работе были рассмотрены особенности операций ввода-вывода в языке програмирования Си/С++, в которых есть много общего. Рассмотрены понятия «функции» и «потока», их виды и особенности функционирования в языке программирования.

	Боллее глубоко и подробно были рассмотрены потоковый ввод-вывод символов, его особенности работы. В работе для иллюстрации приведены фрагменты программ с кодом, которые показывают особенности реализации тех или иных функций ввода-вывода.

	
Литература

	

	Подбельский В.В. Программирование на языке С. М.: Финансы и статистика, 1999. 

	В.В.Подбельский, С.С.Фомин «Язык Си++». – М.: Финансы и статистика, 2003.

	Павловская Т.А., Щупак Ю.А. С/С++. Структур-ное программирование (практикум). – Спб.: Пи-тер, 2004. 

	Аляев Ю.А., Козлов О.А. Программирование. Pascal, C++, Visual Basic. М.: Финансы и стати-стика, 2004. 

	Гуденко Д., Петроченко Д. Сборник задач по программированию. – Спб.: Питер, 2003. 

	Культин Н. С/С++ в задачах и примерах.. БХВ – Петербург, 2004. 

	Фридман А., Кландер Л. И др. С/С++. Алгоритмы и примеры. М. Бином, 2003.

1