Ввод и вывод данных средствами языка C

В системе ввода-вывода в Си для программ поддерживается единый интерфейс, не зависящий от того, к какому конкретному устройству осуществляется доступ. То есть в Си между программой и устройством находится нечто более общее, чем само устройство. Такое обобщенное устройство ввода или вывода (устройство более высокого уровня абстракции) называется потоком. В то же время конкретное устройство называется файлом. Наша задача - понять, каким обрзом происходит взаимодействие потоков и файлов.
Файловая система Си предназначена для работы с разными устройствами, в том числе с терминалами, дисководами и накопителями. Даже, если какое-то устройство очень сильно отличается от других устройств, буферизованная файловая система все равно представит его в виде логического устройства, которое называется потоком. Все потоки ведут себя похожим образом. И так как они в основном не зависят от физических устройств, то та же функция, которая выполняет запись в дисковый файл, может ту же операцию выполнить и на другом устройстве. Например, на консоли. Потоки бывают двух видов: текстовые и двоичные.
В языке Си файлом может быть все, что угодно, начиная в дискового файла и заканчивая терминалом или принтером. Поток связывают с определенным файлом, выполняя обязательную операцию открытия. Как только файл открыт, можно проводить обмен информацией между ним и программой.
Но не у всех файлов одинаковые возможности. Например, к дисковому файлу прямой доступ возможен, в то время как к некоторым принтерам - он не возможен. Таким образом, вы видите, что напрашивается определенный вывод, являющийся принципом системы ввода-вывода языка Си: все потоки одинаковы, а файлы - нет!
Если файл может поддерживать запросы на местоположение (указатель текущей позиции), то при открытии такого файла указатель текущей позиции в файле устанавливается в начало файла. При чтении каждого символа из файла (или записи в файл) указатель текущей позиции увеличивается. Тем самым обеспечивается продвижение по файлу.
Файл отсоединяется от определенного потока (то есть разрывается связь между файлом и потоком) с помощью операции закрытия файла. При закрытии файла, открытого с целью вывода, содержимое (если оно, конечно, есть) связанного с ним потока записывается на внешнее устройство. Этот процесс обычно называют дозаписью потока. При этом гарантируется, что никакая информация случайно не останется в буфере диска.
Если программа завершает работу нормально, то есть либо 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.

Потоковый ввод-вывод
На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, строго говоря, являются устройствами поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 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.

32) Ввод и вывод данных средствами языка C++.

 

В языке С имеется весьма развитая библиотека функций ввода-вывода. Однако в самом языке отсутствуют какие-либо предопределенные файловые структуры. Все данные обрабатываются как последовательность байт. Имеется три основных типа функций: потоковые, работающие с консолью и портами ввода-вывода и низкоуровневые.
Потоковые функции.
В потоковых функциях файлы данных рассматриваются как поток отдельных символов.
Когда программа открывает файл для ввода вывода при помощи потоковых функций, то открытый файл связывается с некоторой переменной типа FILE (определенной в stdio.h), содержащей базовую информацию об этом файле. После открытия потока с помощью функции fopen возвращается указатель на эту переменную. Этот указатель используется для ссылки к файлу при всех последующих операциях ввода-вывода и называется указателем потока.
Все потоковые функции обеспечивают буферизованный, форматированный или неформатированный ввод/вывод. Буферизация потока разгружает приложение. Однако следует иметь ввиду, что при аварийном завершении программы содержимое буферов вывода может быть потеряно.
Аналогичным образом выглядят функции, работающие с консолью и портами. Они позволяют читать и писать на терминал или в порт ввода/вывода (например в порт принтера). Функции портов ввода/вывода выполняют простое побайтное считывание или запись. Функции ввода/вывода на консоль обеспечивают несколько дополнительных возможностей, например можно определить момент, когда будет введен символ с консоли и т.п.
Для использования потоковых функций в программу должен быть включен заголовочный файл stdio.h. В нем содержатся описания прототипов функций ввода/вывода, а также - описания ряда констант. Константа EOF определяется как значение, возвращаемое при обнаружении конца файла, BUFSIZE - размер буферов потока, тип FILE определяет структуру, используемую для получения информации о потоке.
Поток открывается с помощью функций fopen(), fdopen() или freopen(). В момент открытия задаются режим файла и способ доступа. Эти функции возвращают указатель на переменную типа FILE, например
FILE *file1;
file1=fopen(“input.dat”,”r”);
открывает файл input.dat для чтения. Переменная file1 получает указатель на поток.
Возможные типы доступа:
“a” – запись в режиме добавления в конец файла,
”a+” –тоже, что и “a”, но возможно и чтение,
”r” – только для чтения,
”r+” – для чтения и записи,
”w” – открывается пустой файл для записи,
”w+” – открывается пустой файл для записи и чтения.
Когда начинается выполнение приложения автоматически открывается следующие потоки: стандартное устройство ввода stdin, стандартное устройство вывода stdout, устройство сообщений об ошибках stderr, устройство печати stdprn и стандартное дополнительное устройство stdaux. Эти файловые указатели можно использовать во всех функциях ввода/вывода в качестве указателя на поток. Некоторые функции автоматически используют указатели на поток, например getchar() и putchar() используют stdin и stdout соответственно.
Для закрытия потоков используются функции fclose() и fcloseall(). Если программа не закрывает поток явно, то он закрывается автоматически по ее завершению.
Для перемещения по файлу можно использовать функции fseek(), ftell() и rewind().
Низкоуровневый ввод и вывод.
Функции низкоуровневого ввода-вывода не выполняю никакой буферизации и форматирования. Они непосредственно обращаются к средствам ввода/вывода операционной системы.
При открытии файла на этом уровне возвращается описатель файла (file handle), представляющий собой целое число, используемое затем для обращения к этому файлу при дальнейших операциях. Для открытия используется функция open(), для закрытия – функция close().
Функция read() читает данные в указанный массив, а write() – выводит данные из массива в файл, lseek() – используется для перемещения по файлу.
Низкоуровневые функции не требуют включения заголовочного файла stdio.h, вместо него используется файл io.h.
Низкоуровневая система ввода-вывода не вошла в стандарт ANSI C, поэтому ее не рекомендуют для дальнейшего использования.
Ввод и вывод символов, строк, слов.
Наиболее общими функциями являются те, которые работают с отдельными символами. Функция getc() вводит один символ из указанного файлового потока в переменную типа int:

 

int ic;
ic=getc(stdin);

Вводится один символ из потока stdin.
Функция putc() передает один символ в указанный файловый поток:

 

putc(ic,stdout);

Для стандартных потоков stdin и stdout можно использовать функции getchar() и putchar() соответственно:

 

int ic;
ic=getchar();
putchar(ic);

Функции getch() и putch() являются низкоуровневыми функциями. Обычно функция getch() используется для перехвата символа, введенного с клавиатуры сразу после его нажатия. Нажатия клавиши “Enter” не требуется.
Для ввода/вывода текстовых строк можно использовать функции gets(), fgets(), puts(), fputs(). Функция fgets() имеет вид:
fgets(имя_массива, размер_массива, указатель_на_поток);
Она считывает символы в указанный массив и добавляет в конце null-символ. Считывание производится до заполнения массива, или до достижения конца файла. Символ перевода строки переписывается в массив.
Для ввода и вывода целых чисел можно использовать функции getw() и putw(). Они работают с двоичными файлами.
Форматированный ввод и вывод.
Богатый ассортимент средств управления форматом позволяет легко создавать таблицы, графики или отчеты. Функциями, выполняющими этот вывод являются printf() - для стандартного потока вывода stdout, и fprintf() – для любого потока. Функция fprintf() имеет вид:
fprintf(поток_вывода, “формат”, перем_1, перем_2,…);
Работает она аналогично printf() и выводит данные в указанный поток вывода.
Для форматированного ввода используются функции scanf() и fscanf().
Для преобразования текстовой строки можно использовать sscanf(). Она работает аналогично fscanf(), но данные берет из сроки, а не из файла.
Потоки cin, cout, cerr.
В языке С++ имеется другая библиотека ввода/вывода, определяемая заголовочным файлом iostream.h. Ввод/вывод в ней определяется набором специальных классов. Аналогами потоков stdin, stdout и stderr являются cin, cout и cerr. Они открываются автоматически при запуске программы.
Операции выделения >> и вставки <<.
Для ввода/вывода с помощью указанных потоков используются специальным образом определенные операции “занести в поток” и “получить из потока”, << и <<. Операция >> выделяет данные из входного потока и помещает в указанные переменные, а операция << помещает значения указанных переменных в поток.
Приведем пример использования потока stdin и потока cin:

 

scanf(“%d%lf%c”,&ivalue,&dvalue,&cvalue);
cin>>ivalue>>dvalue>>cvalue;

Аналогично для вывода:

 

printf(“Integer:%d double: %lf”,ivalue,dvalue);
cout<<”Integer:”<<ivalue<<” double:”<<dvalue;

33) Использование массивов в языке С/C++.

Массив – коллекция переменных одного типа, обращение к которым происходит с применение общего для всех имен. Чтобы произвести какое-нибудь действие с такой переменной, нужно написать ее имя, указав в скобках ее числовой индекс. Например, так: array[10]. Встретить аналоги массива в реальной жизни очень трудно. Мы все уникальны. У каждого свое имя. Если имена совпадают, то в дополнение к ним люди придумали фамилию. А так как нас много и возможно, что наше имя и фамилия могут совпадать с именем и фамилией другого человека, нам от нашего отца достается отчество, которое нас делает еще уникальней. Вероятность встретить в одном месте двух человек, у которых совпадали бы имя, фамилия и отчество очень мала. Однако такое возможно. Вот пример из реальной жизни. В одной из средних школ учились в одном классе два ученика. Обоих звали Александрами. Оба были Морозовы. Вдобавок ко всему обоим от отца досталось одно и то же отчество: Георгиевич. Естественно встал вопрос, как их различать. Ведь не будешь же называть одного Морозов младший, а другого Морозов старший лишь на том основании, что один родился на несколько дней раньше другого. Выход нашелся сам собой. Им присвоили уникальные индексы. Один из них стал Морозов первый, а другой стал Морозов второй. Когда надо было вызвать одного, отвечать к доске, учительница произносила: «Морозов» - оба Морозовых напрягались. Потом она добавляла либо первый, либо второй. Один из них облегченно вздыхал, а другой шел отвечать к доске. В отличии от этого класса, в С++ индексация массива начинается с нуля. Чтобы обратиться к члену массива, нужно написать имя и индекс в квадратных скобках. Вот как будет выглядеть первый член массива array: array[0].

Так же как и любую другую переменную в С++ массив нужно объявить, до того как он будет использован. Вот как выглядит объявление массива array, который содержит 10 переменных типа int:

int array[10];

 

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

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
int num[10];

for(int i=0; i<10; i++)
{
num[i] = i+1;
cout << num[i] << '\n';
}

cout << "\n\n";
system("PAUSE");
return 0;
}

 

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

#include <iostream>
#include <cstdlib>
#include <windows.h>
using namespace std;

int main()
{
int num[10];
int i, j, k;
int size = 10;

cout << "Введите десять целых чисел.\n"
<< "После каждого введенного числа нажмите клавишу Enter: \n\n";

for(i=0; i<size; i++)
{
cin >> num[i];
}

cout << "\n";

// пузырьковая сортировка
for(j=1; j<size; j++)
{
for(k=size-1; k>=j; k--)
{
if(num[k-1] > num[k])
{

// Меняем элементы местами.
i = num[k-1];
num[k-1] = num[k];
num[k] = i;
}
}
}
// Конец сортировки.

// Отображаем отсортированный массив.
cout << "Отсортированный массив: \n\n";

for(i=0; i<size; i++)
cout << num[i] << "\n";

cout << "\n\n";
system("PAUSE");
return 0;
}

 

Массивы, как и другие переменные можно инициализировать. Вот как будет выглядеть программа с инициализацией массива:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
int num[10] = {12, 56, 67, 45, 104, 98, 83, 125, 678, 900};
int size = 10;

cout << "Наш массив:\n";
for(int i=0; i<size; i++)
{
cout << num[i] << "\n";
}

cout << "\n\n";
system("PAUSE");
return 0;
}

 

Теперь несколько правил употребления массивов. Первый элемент массива всегда будет иметь индекс 0. Вот как будет выглядеть обращение к первому элементу массива array[] – array[0]. Второе правило: один массив нельзя присвоить другому массиву напрямую. Эта инструкция не скомпилируется: array1=array2, если array1 и array2 будут массивами. Только один элемент массива другому элементу другого массива. Вот пример программы, которая иллюстрирует это правило:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
int num1[10] = {12, 56, 67, 45, 104, 98, 83, 125, 678, 900};

int num2[10] = {35, 63, 125, 74, 112, 58, 83, 165, 578, 700};

int i;
int size = 10;

cout << "Выводим массив num1:\n\n";
for(i=0; i<size; i++)
{
cout << num1[i] << ", ";
}
cout << "\n\n";

cout << "Выводим массив num2:\n\n";
for(i=0; i<size; i++)
{
cout << num2[i] << ", ";
}
cout << "\n\n";

cout << "Теперь присваиваем массив num1 массиву num2\n"
<< "и выводим их на экран.\n\n";

for(i=0; i<size; i++)
{
num2[i]=num1[i];
}

cout << "Выводим массив num1:\n\n";
for(i=0; i<size; i++)
{
cout << num1[i] << ", ";
}
cout << "\n\n";

cout << "Выводим массив num2:\n\n";
for(i=0; i<size; i++)
{
cout << num2[i] << ", ";
}

cout << "\n\n";
system("PAUSE");
return 0;
}

 

В качестве индекса в С++ можно использовать только целые числа (в отличии от таких языков как PHP). Дробные числа, строки, отрицательные числа использовать нельзя.

Ну и последнее, самое важное правило использование массивов в С++. В С++ не производиться проверка выхода за границы массива, т. к. создатели языка посчитали, что любая попытка такой проверки сделает язык более медленным. Язык С++ считается языком профессиональных программистов. Если вы объявите массив num[10], а затем попытаетесь обратиться к его тысячному элементу, то компилятор скомпилирует вашу программу, и даже создаст исполняемый файл. При попытке записать какое-либо значение, вы всего вероятней запишете его поверх других данных, тем самым, повредив другие данные или даже операционную систему. Поэтому будьте осторожны, когда будете работать с массивами. Всегда смотрите, чтоб индексы элементов не выходили за границы массивов.

Иногда бывает неудобно вручную подсчитывать количество элементов, когда мы инициализируем массив, во время объявления. Можно просто оставить квадратные скобки пустыми. Например, так: int num1[] = {12, 56, 67, 45, 104, 98, 83, 125, 678, 900};. Компилятор сам подсчитает количество элементов. Вот еще один вариант программы, которая выводит массив на экран:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
int num[ ] = {12, 56, 67, 45, 104, 98, 83, 125, 678, 900};
int size = 10;

cout << "Наш массив:\n\n";
for(int i=0; i<size; i++)
{
cout << num[i] << ", ";
}
cout << "\n";

cout << "\n\n";
system("PAUSE");
return 0;
}

 

И так мы немного разобрались с массивами. Теперь давайте перейдем к более интересным вещам, а именно к строкам. Дело в том, что в С++ нет специального строкового типа. Для этого используется массив типа char. Каждый элемент такого массива содержит один символ, что дает потрясающие возможности для работы со строками в С++. Итак чтоб было понятно, что я имею ввиду я написал внизу программу, которая иллюстрирует это утверждение:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
char string1[] = "Символ";
char string2[] = {'С', 'и', 'м', 'в', 'о', 'л', '\0'};
int i;

cout << "Выводим string1: \n\n";

i = 0;

while(string1[i])
{
cout << string1[i];
i++;
}
cout << "\n\n";

cout << "Выводим string2: \n\n";

i = 0;

while(string2[i])
{
cout << string2[i];
i++;
}

cout << "\n\n";
system("PAUSE");
return 0;
}

 

Я специально использовал цикл while, чтобы выводить с помощью его каждый символ строк string1, и string2 на экран, хотя гораздо легче использовать инструкцию cout, чтобы продемонстрировать возможность доступа к любому символу наших строк.

Перед тем, как двигаться дальше, давайте разберемся, что такое символьные литералы. Символьные литералы, это любой набор символов заключенный в двойные кавычки. Например: "Hello, World!"; "1234567", "!@#$%&" и так далее. Каждая такая строка заканчивается нулевым символом, который является признаком завершения строки. При инициализации массива типа char символьным литералом он добавляется автоматически. Поэтому если б мы, при объявлении массива, указывали бы размер нашего массива, то мы должны были бы указать, что в нашем массиве содержится семь элементов, а не шесть. Одни дополнительный элемент для признака завершения строки.

Строку string2 мы инициализировали в ручную, заключив каждый символ в одинарные кавычки. В конце стоит символ '\0', это и есть символ завершения строки.

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

Прежде чем я покажу все преимущества символьных массивов, я хотел бы поговорить о недостатках инструкции cin. Некоторым уже встречались с некоторыми странностями этой инструкции, когда с помощью нее пытаешься ввести несколько слов. Чтоб понять, о чем я говорю, скомпилируйте следующую программу и введите: «Hello, World!».

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
char string[80];

cout << "Введите несколько слов: \n\n";
cin >> string;
cout << "\n\n" << string;

cout << "\n\n";
system("PAUSE");
return 0;
}

 

Как видим вместо «Hello, World!» наша программа вывела «Hello,». Дело в том, что оператор >> прекращает считывать строку, как только в ней встречается символ новой строки, пробел или знак табуляции. Для того, чтобы программа правильно считывала строку, лучше использовать функцию gets(), указав в скобках имя символьного массива, в который вы хотите поместить вашу строку. Для того, чтобы можно было использовать эту функцию нужно поместить заголовок <cstdio>. Вот исправленный вариант предыдущей программы:

#include <iostream>
#include <cstdio>
#include <windows.h>
using namespace std;

int main()
{
char string[80];

cout << "Введите несколько слов: \n\n";
gets(string);
cout << "\n\n" << string;

cout << "\n\n";
system("PAUSE");
return 0;
}

 

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

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <windows.h>
using namespace std;

int main()
{
char number[80];
int length, digit;

cout << "Введите номер вашего мобильного телефона\n"
<< "в формате 89990000000: \n\n";

gets(number);

length = strlen(number);

if(length==11)
{
int i = 0;

while(number[i])
{
digit = isdigit(number[i]);

if(!digit)
{
cout << "\nВы ввели не корректный номер вашего\n"
<< "мобильного телефона.\n\n";
break;
}
i++;
}

if(i==11)
{
cout << "\nВы ввели корректный номер телефона. Ваши данные \n"
<< "обрабатываются. Подождите, это может занять \n"
<< "несколько минут.\n\n";
}
}
else
{
cout << "\nВы ввели не корректный номер вашего\n"
<< "мобильного телефона.\n\n";
}
cout << "\n\n";
system("PAUSE");
return 0;
}

 

Теперь давайте разбираться, как работает наша программа. Сначала мы с помощью функции gets() записываем значение введенное пользователем в символьный массив number. Для этого надо поместить имя массива в круглые скобки (для того, чтобы работать с этой функцией, надо написать в начале программы: #include <cstdio>;). Далее мы подсчитываем, сколько символов ввел пользователь. Их должно быть ровно одиннадцать. Для этого мы используем функцию strlen(). Для работы с этой функцией мы должны записать в начале программы: #include <cstring>;. Количество символов в значении, введенном пользователем мы записываем в переменную length.

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

Теперь посмотрим, как с помощью цикла while и функции isdigit() проверить, что все символы введенные пользователем являются цифрами. Для начала объявим переменную i, которую будем использовать в качестве счетчика, и инициализируем ее значением 0. В качестве условия цикла while поместим в его условие массив number и поместим вместо индекса переменную i. Так как первый элемент массива имеет индекс 0, и переменная i инициализирована нулем, то с первой же итерации массив while начнет проходить по массиву number. Так как любое ненулевое значение, помещенное в условие, преобразуется в истинное, то цикл while будет обрабатывать символ за символом, пока не будет достигнут признак завершения строки, который автоматически преобразуется в ложь, и работа цикла while прекращается.

Затем с помощью функции isdigit() проверяем, является ли символ цифрой. Для того чтобы можно было работать с этой функцией в начале программы мы должны написать #include <cctype>. Если символ является цифрой, то результат работы функции будет не нулевое значение. Если символ не является цифрой, то результатом работы функции будет ноль. Результат помещаем в переменную digit. Затем с помощью инструкции if проверяем, какое значение содержит переменная digit. Здесь я хочу обратить ваше внимание на восклицательный знак перед переменной digit. Дело в том, что инструкция if будет выполняться только в том случае, если выражение, помещенное в условие инструкции if истинно. В нашем случае должно происходить все наоборот. Для этого мы перед переменной digit поставили знак логической инверсии. Поэтому если символ не является цифрой, в переменной digit присваивается значение 0, который преобразуется в ложь. А так как перед переменной digit стоит знак логической инверсии, то ложное значение преобразуется в истинное, а истинное в ложное. В результате код, записанный в инструкции if, будет выполняться только в том случае, если символ не является цифрой. Выводим сообщение пользователю о том, что он некорректно ввел номер мобильного телефона и выходим из цикла с помощью инструкции break так, как дальше обрабатывать данные нет смысла.

Ну а дальше все просто. Когда цикл совершил одиннадцать витков, т. е. проверил все цифры, нужно вывести пользователю сообщение, что он ввел правильно номер телефона и введенные им данные обрабатываются. Для этого с помощью инструкции if проверяем, сколько итераций совершил цикл, и если переменная i равняется одиннадцати, выводим соответствующее сообщение. Единственным недостатком нашей программы, является то, что пользователь не может еще раз ввести номер своего телефона в случаи ошибки. В новом варианте нашей программы, эти недостатки исправлены. И так вот новый вариант нашей программы:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
using namespace std;

int main()
{
char choice[80];
int j = 0;

for(;;)
{

j++;

if(j>=2)
{
cout << "Вы хотите продолжить?(y/n)\n\n";

gets(choice);

if(!strcmp(choice, "n") ||!strcmp(choice, "N"))
{
exit(0);
}
cout << "\n";
}

for(;;)
{
char number[80];
int length, digit;

cout << "Ввидите номер вашего мобильного телефона\n"
<< "в формате 89990000000: \n\n";

gets(number);

length = strlen(number);

if(length==11)
{
int i = 0;

while(number[i])
{
digit = isdigit(number[i]);

if(!digit)
{
cout << "\nВы ввели не корректный номер вашего\n"
<< "мобильного телефона. Попробуйте еще раз.\n\n";
break;
}
i++;
}

if(i==11)
{
cout << "\nВы ввели корректный номер телефона. Ваши данные \n"
<< "обрабатываются. Подождите, это может занять \n"
<< "несколько минут.\n\n";
break;
}
}
else
{
cout << "\nВы ввели не корректный номер вашего\n"
<< "мобильного телефона. Попробуйте еще раз.\n\n";
}
break;
}
}
return 0;
}

 

Здесь мы неслучайно использовали два цикла for, у которых отсутствует инициализация, условие и инкремент. Так мы сделали цикл for бесконечным. Массив, содержащий данные пользователем и все переменные мы инициализировали внутри массива. Это не случайно. Дело в том, что переменные, инициализированные внутри цикла, создаются каждый раз, когда выполняется цикл, а после его завершения или выхода из него уничтожаются. Поэтому нам не надо обнулять их, если пользователь ввел неверные данные. С помощью инструкций break мы попадаем снова в первый цикл. Для того чтобы вопрос о том, хочет ли пользователь выйти из программы или хочет еще раз попробовать ввести номер телефона, не появился на экране при первой же попытки ввести номер телефона, с помощью переменной j мы узнаем, сколько раз пользователь вводит номер телефона. Если j будет равняться двум или более сообщение появиться на экране. Затем если будет нажата n или N, то программа завершит свою работу, выполнив функцию exit(0);. Если будет нажата другая буква, то пользователь может еще раз ввести номер своего мобильного телефона.

Особенно я хотел бы обратить внимание на следующий код: if(!strcmp(choice, "n") ||!strcmp(choice, "N")). Дело в том, что результат работы функции в случае совпадения будет 0, который преобразуется в ложь. Восклицательный знак (логическое не) перед функцией strcmp преобразует ложное значение в истинное, а истинное в ложное.

Ну, вот пока и все, что я хотел рассказать в этом уроке. В следующем уроке я расскажу про многомерные массивы, и покажу на примерах как их использовать в своих программах.

Форматный ввод-вывод.
Для работы со стандартными потоками в режиме форматного ввода-вывода определены две функции:
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" пропускаются при вводе).
Заключение.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: