Как ввести eof с клавиатуры
Стандартная библиотека Си содержит набор функций для работы с файлами. Эти функции описаны в стандарте ANSI . Отметим, что файловый ввод-вывод не является частью языка Си , и ANSI -функции - не единственное средство ввода-вывода. Так, в операционной системе Unix более популярен другой набор функций ввода-вывода, который можно использовать не только для работы с файлами, но и для обмена по сети. В C++ часто используются библиотеки классов для ввода-вывода. Тем не менее, функции ANSI -библиотеки поддерживаются всеми Си -компиляторами, и потому программы, применяющие их, легко переносятся с одной платформы на другую. Прототипы функций ввода-вывода и используемые для этого типы данных описаны в стандартном заголовочном файле "stdio.h.
Открытие файла: функция fopen
Для доступа к файлу применяется тип данных FILE . Это структурный тип, имя которого задано с помощью оператора typedef в стандартном заголовочном файле "stdio.h". Программисту не нужно знать, как устроена структура типа файл: ее устройство может быть системно зависимым, поэтому в целях переносимости программ обращаться явно к полям струтуры FILE запрещено. Тип данных "указатель на структуру FILE используется в программах как черный ящик: функция открытия файла возвращает этот указатель в случае успеха, и в дальнейшем все файловые функции применяют его для доступа к файлу.
Прототип функции открытия файла выглядит следующим образом:
Здесь path - путь к файлу (например, имя файла или абсолютный путь к файлу), mode - режим открытия файла. Строка mode может содержать несколько букв. Буква " r " (от слова read) означает, что файл открывается для чтения (файл должен существовать). Буква " w " (от слова write) означает запись в файл, при этом старое содержимое файла теряется, а в случае отсутствия файла он создается. Буква " a " (от слова append) означает запись в конец существующего файла или создание нового файла, если файл не существует.
В некоторых операционных системах имеются различия в работе с текстовыми и бинарными файлами (к таким системам относятся MS DOS и MS Windows; в системе Unix различий между текстовыми и бинарными файлами нет). В таких системах при открытии бинарного файла к строке mode следует добавлять букву " b " (от слова binary), а при открытии текстового файла -- букву " t " (от слова text). Кроме того, при открытии можно разрешить выполнять как операции чтения, так и записи; для этого используется символ + (плюс). Порядок букв в строке mode следующий: сначала идет одна из букв " r ", " w ", " a ", затем в произвольном порядке могут идти символы " b ", " t ", " + ". Буквы " b " и " t " можно использовать, даже если в операционной системе нет различий между бинарными и текстовыми файлами, в этом случае они просто игнорируются.
Значения символов в строке mode сведены в следующую таблицу:
Несколько примеров открытия файлов:
Обратите внимание, что во втором случае мы используем обычную косую черту / для разделения директорий, хотя в системах MS DOS и MS Windows для этого принято использовать обратную косую черту \ . Дело в том, что в операционной системе Unix и в языке Си, который является для нее родным, символ \ используется в качестве экранирующего символа, т.е. для защиты следующего за ним символа от интерпретации как специального. Поэтому во всех строковых константах Си обратную косую черту надо повторять дважды, как это и сделано в третьем примере. Впрочем, стандартная библиотека Си позволяет в именах файлов использовать нормальную косую черту вместо обратной; эта возможность была использована во втором примере.
В случае удачи функция fopen открытия файла возвращает ненулевой указатель на структуру типа FILE , описывающую параметры открытого файла. Этот указатель надо затем использовать во всех файловых операциях. В случае неудачи (например, при попытке открыть на чтение несуществующий файл) возвращается нулевой указатель. При этом глобальная системная переменная errno, описанная в стандартном заголовочном файле "errno.h, содержит численный код ошибки. В случае неудачи при открытии файла этот код можно распечатать, чтобы получить дополнительную информацию:
Константа NULL
В приведенном выше примере при открытии файла функция fopen в случае ошибки возвращает нулевой указатель на структуру FILE . Чтобы проверить, произошла ли ошибка, следует сравнить возвращенное значение с нулевым указателем. Для наглядности стандартный заголовочный файл "stdio.h" определяет символическую константу NULL как нулевой указатель на тип void :
Сделано это вроде бы с благой целью: чтобы отличить число ноль от нулевого указателя. При этом язык Си, в котором контроль ошибок осуществляется недостаточно строго, позволяет сравнивать указатель общего типа void * с любым другим указателем. Между тем,в Си вместо константы NULL всегда можно использовать просто 0 , и вряд ли от этого программа становится менее понятной. Более строгий язык C++ запрещает сравнение разных указателей, поэтому в случае C++ стандартный заголовочный файл определяет константу NULL как обычный ноль:
Автор языка C++ Б. Страуструп советует использовать обычный ноль 0 вместо символического обозначения NULL . Тем не менее, по традиции большинство программистов любят константу NULL .
Константа NULL не является частью языка Си или C++, и без подключения одного из стандартных заголовочных файлов, в котором она определяется, использовать ее нельзя. (По этой причине авторы языка Java добавили в язык ключевое слово null , записываемое строчными буквами.) Так что в случае Си или C++ безопаснее следовать совету Б. Страуструпа и использовать обычный ноль 0 вместо символической константы NULL .
Диагностика ошибок: функция perror
Функции бинарного чтения и записи fread и fwrite
После того как файл открыт, можно читать информацию из файла или записывать информацию в файл. Рассмотрим сначала функции бинарного чтения и записи fread и fwrite . Они называются бинарными потому, что не выполняют никакого преобразования информации при вводе или выводе (с одним небольшим исключением при работе с текстовыми файлами, которое будет рассмотрено ниже): информация хранится в файле как последовательность байтов ровно в том виде, в котором она хранится в памяти компьютера.
Функция чтения fread имеет следующий прототип:
Здесь size_t определен как беззнаковый целый тип в системных заголовочных файлах. Функция пытается прочесть numElems элементов из файла, который задается указателем f на структуру FILE , размер каждого элемента равен elemSize . Функция возвращает реальное число прочитанных элементов, которое может быть меньше, чем numElems , в случае конца файла или ошибки чтения. Указатель f должен быть возвращен функцией fopen в результате успешного открытия файла. Пример использования функции fread :
В этом примере файл " tmp.dat " открывается на чтение как бинарный, из него читается 100 вещественных чисел размером 8 байт каждое. Функция fread возвращает реальное количество прочитанных чисел, которое меньше или равно, чем 100.
Функция fread читает информацию в виде потока байтов и в неизменном виде помещает ее в память. Следует различать текстовое представление чисел и их бинарное представление! В приведенном выше примере числа в файле должны быть записаны в бинарном виде, а не в виде текста. Для текстового ввода чисел надо использовать функции ввода по формату, которые будут рассмотрены ниже.
Внимание! Открытие файла как текстового с помощью функции fopen , например,
вовсе не означает, что числа при вводе с помощью функции fopen будут преобразовываться из текстовой формы в бинарную! Из этого следует только то, что в операционных системах, в которых строки текстовых файлов разделяются парами символами " \r\n " (они имеют названия CR и LF - возврат каретки и продергивание бумаги, Carriage Return и Line Feed ), при вводе такие пары символов заменяются на один символ " \n " (продергивание бумаги). Обратно, при выводе символ " \n " заменяется на пару " \r\n ". Такими операционными системами являются MS DOS и MS Windows. В системе Unix строки разделяются одним символом " \n " (отсюда проистекает обозначение " \n ", которое расшифровывается как new line). Таким образом, внутреннее представление текста всегда соответствует системе Unix, а внешнее - реально используемой операционной системе. Отметим также, что создатели операционной системы компьютеров Apple Macintosh выбрали, чтобы жизнь не казалась скучной, третий, отличный от двух предыдущих, вариант: текстовые строки разделяются одним символом " \r " возврат каретки!
Такое представление текстовых файлов восходит к тем уже далеким временам, когда еще не было компьютерных мониторов и для просмотра текста использовались электрифицированные пишущие машинки или посимвольные принтеры. Текстовый файл фактически представлял собой программу печати на пишущей машинке и, таким образом, содержал команды возврата каретки и продергивания бумаги в конце каждой строки.
Функция бинарной записи в файл fwrite аналогична функции чтения fread . Она имеет следующий прототип:
Функция возвращает число реально записанных элементов, которое может быть меньше, чем numElems , если при записи произошла ошибка - например, не хватило свободного пространства на диске. Пример использования функции fwrite :
Закрытие файла: функция fclose
По окончании работы с файлом его надо обязательно закрыть. Система обычно запрещает полный доступ к файлу до тех пор, пока он не закрыт. (Например, в нормальном режиме система запрещает одновременную запись в файл для двух разных программ.) Кроме того, информация реально записывается полностью в файл лишь в момент его закрытия. До этого она может содержаться в оперативной памяти (в так называемой файловой кеш -памяти), что при выполнении многочисленных операций записи и чтения значительно ускоряет работу программы.
Для закрытия файла используется функция fclose с прототипом
В случае успеха функция fclose возвращает ноль, при ошибке -- отрицательное значение (точнее, константу конец файла EOF, определенную в системных заголовочных файлах как минус единица). При ошибке можно воспользоваться функцией perror , чтобы напечатать причину ошибки. Отметим, что ошибка при закрытии файла - явление очень редкое (чего не скажешь в отношении открытия файла), так что анализировать значение, возвращаемое функцией fclose , в общем-то, не обязательно. Пример использования функции fclose :
Пример: подсчет числа символов и строк в текстовом файле
В качестве содержательного примера использования рассмотренных выше функций файлового ввода приведем программу, которая подсчитывает число символов и строк в текстовом файле. Программа сначала вводит имя файла с клавиатуры. Для этого используется функция scanf ввода по формату из входного потока, для ввода строки применяется формат " %s . Затем файл открывается на чтение как бинарный (это означает, что при чтении не будет происходить никакого преобразования разделителей строк). Используя в цикле функцию чтения fread , мы считываем содержимое файла порциями по 512 байтов, каждый раз увеличивая суммарное число прочитанных символов. После чтения очередной порции сканируется массив прочитанных символов и подсчитывается число символов " \n " продергивания бумаги, которые записаны в концах строк текстовых файлов как в системе Unix, так и в MS DOS или MS Windows. В конце закрывается файл и печатается результат.
Пример выполнения программы: она применяется к собственному тексту, записанному в файле "wc.cpp.
Форматный ввод-вывод: функции fscanf и fprintf
В отличие от функции бинарного ввода fread , которая вводит байты из файла без всякого преобразования непосредственно в память компьютера, функция форматного ввода fscanf предназначена для ввода информации с преобразованием ее из текстового представления в бинарное. Пусть информация записана в текстовом файле в привычном для человека виде (т.е. так, что ее можно прочитать или ввести в файл, используя текстовый редактор). Функция fscanf читает информацию из текстового файла и преобразует ее во внутреннее представление данных в памяти компьютера. Информация о количестве читаемых элементов, их типах и особенностях представления задается с помощью формата. В случае функции ввода формат - это строка, содержащая описания одного или нескольких вводимых элементов. Форматы, используемые функцией fscanf , аналогичны применяемым функцией scanf , они уже неоднократно рассматривались (см. раздел 3.5.4). Каждый элемент формата начинается с символа процента " % ". Наиболее часто используемые при вводе форматы приведены в таблице:
Прототип функции fscanf выглядит следующим образом:
Многоточие здесь означает, что функция имеет переменное число аргументов, большее двух, и что количество и типы аргументов, начиная с третьего, произвольны. На самом деле, фактические аргументы, начиная с третьего, должны быть указателями на вводимые переменные. Несколько примеров использования функции fscanf :
Функция fscanf возвращает число успешно введенных элементов. Таким образом, возвращаемое значение всегда меньше или равно количеству процентов внутри форматной строки (которое равно числу фактических аргументов минус 2).
Функция fprintf используется для форматного вывода в файл. Данные при выводе преобразуются в их текстовое представление в соответствии с форматной строкой. Ее отличие от форматной строки, используемой в функции ввода fscanf , заключается в том, что она может содержать не только форматы для преобразования данных, но и обычные символы, которые записываются без преобразования в файл. Форматы, как и в случае функции fscanf , начинаются с символа процента " % ". Они аналогичны форматам, используемым функцией fscanf . Небольшое отличие заключается в том, что форматы функции fprintf позволяют также управлять представлением данных, например, указывать количество позиций, отводимых под запись числа, или количество цифр после десятичной точки при выводе вещественного числа. Некоторые типичные примеры форматов для вывода приведены в следующей таблице:
Прототип функции fprintf выглядит следующим образом:
int fprintf(FILE *f, const char *format, . );
Многоточие, как и в случае функции fscanf , означает, что функция имеет переменное число аргументов. Количество и типы аргументов, начиная с третьего, должны соответствовать форматной строке. В отличие от функции fscanf , фактические аргументы, начиная с третьего, представляют собой выводимые значения, а не указатели на переменные. Для примера рассмотрим небольшую программу, выводящую данные в файл "tmp.dat":
В результате выполнения этой программы в файл "tmp.dat" будет записан следующий текст:
В последнем примере форматная строка содержит внутри себя двойные апострофы. Это специальные символы, выполняющие роль ограничителей строки, поэтому внутри строки их надо экранировать (т.е. защищать от интерпретации как специальных символов) с помощью обратной косой черты \ , которая, напомним, в системе Unix и в языке Си выполняет роль защитного символа. Отметим также, что мы воспользовались стандартной функцией sqrt , вычисляющей квадратный корень числа, и стандартной функцией strlen , вычисляющей длину строки.
Понятие потока ввода или вывода
В операционной системе Unix и в других системах, использующих идеи системы Unix (например, MS DOS и MS Windows), применяется понятие потока ввода или вывода. Поток представляет собой последовательность байтов. Различают потоки ввода и вывода. Программа может читать данные из потока ввода и выводить данные в поток вывода. Программы можно запускать в конвейере, когда поток вывода первой программы является потоком ввода второй программы и т.д. Для запуска двух программ в конвейере используется символ вертикальной черты | между именами программ в командной строке. Например, командная строка
означает, что поток вывода программы ab направляется на вход программе cd , а поток вывода программы cd - на вход программе ef . По умолчанию, потоком ввода для программы является клавиатура, поток вывода назначен на терминал (или, как говорят программисты, на консоль). Потоки можно перенаправлять в файл или из файла, используя символы больше > и меньше < , которые можно представлять как воронки. Например, командная строка
перенаправляет выходной поток программы abcd в файл "tmp.res", т.е. данные будут выводиться в файл вместо печати на экране терминала. Соответственно, командная строка
заставляет программу abcd читать исходные данные из файла "tmp.dat" вместо ввода с клавиатуры. Командная строка
перенаправляет как входной, так и выходной потоки: входной назначается на файл "tmp.dat", выходной -- на файл "tmp.res".
В Си работа с потоком не отличается от работы с файлом. Доступ к потоку осуществляется с помощью переменной типа FILE * . В момент начала работы Си-программы открыты три потока:
- stdin -- стандартный входной поток. По умолчанию он назначен на клавиатуру;
- stdout -- стандартный выходной поток. По умолчанию он назначен на экран терминала;
- stderr -- выходной поток для печати информации об ошибках. Он также назначен по умолчанию на экран терминала.
Переменные stdin , stdout , stderr являются глобальными, они описаны в стандартном заголовочном файле "stdio.h. Операции файлового ввода-вывода могут использовать эти потоки, например, строка
вводит значение целочисленной переменной n из входного потока. Строка
выводит значение переменой n в выходной поток. Строка
По умолчанию, стандартный выходной поток и выходной поток для печати ошибок назначены на экран терминала. Однако операция перенаправления вывода в файл > действует только на стандартный выходной поток. Например, в результате выполнения командной строки
Функции scanf и printf ввода и вывода в стандартные потоки
Поскольку ввод из стандартного входного потока, по умолчанию назначенного на клавиатуру, и вывод в стандартный выходной поток, по умолчанию назначенный на экран терминала, используются особенно часто, библиотека функций ввода-вывода Си предоставляет для работы с этими потоками функции scanf и printf . Они отличаются от функций fscanf и fprintf только тем, что у них отсутствует первый аргумент, означающий поток ввода или вывода. Строка
Когда я собираю его в Ubuntu, он начинает получать символы, но я не знаю, как завершить программу, так как он не заканчивается вводом ENTER или возврата каретки.
Что означает EOF? Как я могу вызвать это?
Этот источник также есть в книге Денниса Ритчи:
3 ответа
Как правило, вы можете "запустить EOF" в программе, работающей в терминале с помощью нажатия клавиш CTRL + D сразу после последнего сброса ввода.
Что означает EOF? Как я могу вызвать это?
EOF означает конец файла.
"Запуск EOF" в этом случае примерно означает "информирование программы о том, что ввод больше не будет отправлен".
В этом случае, так как getchar() вернет отрицательное число, если ни один символ не прочитан, выполнение прекращается.
Но это относится не только к вашей конкретной программе, это относится ко многим различным инструментам.
В общем, "запуск EOF" может быть выполнен нажатием комбинации клавиш CTRL + D сразу после последнего сброса ввода (т. Е. Путем отправки пустого ввода).
Когда вы нажимаете CTRL + D, то происходит то, что ввод, введенный с момента последнего сброса ввода, сбрасывается; когда это случается пустой вход read() системный вызов вызван возвратом STDIN программы 0 , getchar() возвращает отрицательное число ( -1 в библиотеке GNU C), и это, в свою очередь, интерпретируется как EOF 1 .
TL; DR: EOF - это не символ, это макрос, используемый для оценки отрицательного возврата функции чтения ввода. Для отправки можно использовать Ctrl + D EOT символ, который заставит функцию вернуться -1
Каждый программист должен RTFM
Давайте обратимся к "Справочному руководству C A" Harbison and Steele, 4-е изд. с 1995 г., стр. 317:
Отрицательное целое число EOF - это значение, которое не является кодировкой "реального символа" . Например, fget (раздел 15.6) возвращает EOF в конце файла, потому что нет "реального символа" для чтения.
по существу EOF не символ, а целочисленное значение, реализованное в stdio.h представлять -1 , Таким образом, ответ Коса является правильным, но речь идет не о получении "пустого" ввода. Важно отметить, что здесь EOF служит возвращаемым значением (из getchar() ) сравнение, а не для обозначения фактического характера. man getchar поддерживает это:
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
fgetc(), getc() и getchar() возвращают символ, прочитанный как символ без знака, приведенный к типу int или EOF в конце файла или ошибки.
get () и fgets() возвращают s в случае успеха и NULL в случае ошибки или когда происходит конец файла, когда символы не были прочитаны.
ungetc () возвращает c в случае успеха или EOF в случае ошибки.
Рассмотрим while цикл - его основная цель - повторить действие, если условие в скобках верно. Посмотри снова:
Это в основном говорит продолжать делать вещи, если c = getchar() возвращает успешный код ( 0 или выше; кстати, это обычное дело, попробуйте запустить успешную команду, затем echo $? а потом не удалось echo $? и увидеть числа, которые они возвращают). Поэтому, если мы успешно получим символ и добавим C, возвращенный код состояния будет равен 0, а ошибка - -1. EOF определяется как -1 , Поэтому, когда условие -1 == -1 происходит, петли прекращаются. И когда это произойдет? Когда нет больше персонажа, чтобы получить, когда c = getchar() выходит из строя. Вы могли бы написать while ((c = getchar ()) != -1) и все равно будет работать
Кроме того, давайте вернемся к фактическому коду, вот выдержка из stdio.h
Коды ASCII и EOT
Хотя символ EOF не является действительным символом, существует EOT Символ (конец передачи), который имеет десятичное значение ASCII 04; он связан с сочетанием клавиш Ctrl + D (представлен также как метасимвол ^D ). Характер окончания передачи использовался для обозначения закрытия потока данных в обратном направлении, когда компьютеры использовались для управления телефонными соединениями, отсюда и название "конец передачи" .
Таким образом, мы можем сказать, что он существует, но это не для печати
Примечание
Мы часто забываем, что в прошлом компьютеры не были такими универсальными - дизайнеры должны использовать каждую доступную клавиатуру. Таким образом, отправка EOT символ с CtrlD по-прежнему "отправляет символ", в отличие от ввода заглавной буквы A, ShiftA, вы все равно заставляете компьютер вводить данные с помощью доступных клавиш. Таким образом, EOT - это реальный символ в том смысле, что он исходит от пользователя, он читается компьютером (хотя не для печати, не виден людьми), он существует в компьютерной памяти
Комментарий Byte Commander
Если вы попытаетесь прочитать из /dev/null, это также должно вернуть EOF, верно? Или что я получу там?
Да, совершенно верно, потому что в /dev/null нет фактического символа для чтения, следовательно, он c = getchar() вернусь -1 код, и программа выйдет сразу. Снова команда не возвращает EOF. EOF - это просто постоянная переменная, равная -1, которую мы используем для сравнения кода возврата функции getchar. EOF не существует как символ, это просто статическое значение внутри stdio.h ,
Еще один гвоздь в гробу
Иногда пытаются доказать, что EOF - это символ с кодом, подобным этому:
Проблема в том, что тип данных char может иметь значение со знаком или без знака. Кроме того, они являются наименьшим адресуемым типом данных, что делает их очень полезными в микроконтроллерах, где память ограничена. Так что вместо того, чтобы объявить int foo = 25; это часто встречается в микроконтроллерах с небольшой памятью char foo = 25; или что-то подобное. Кроме того, символы могут быть подписаны или не подписаны.
Можно проверить, что размер в байтах с программой, как это:
Какой именно смысл? Дело в том, что EOF определяется как -1, но тип данных char может печатать целочисленные значения.
ХОРОШО. то, что если мы попытаемся напечатать char как строку?
Очевидно, ошибка, но, тем не менее, ошибка скажет нам кое-что интересное:
skolodya @ ubuntu: $ gcc EOF.c -o EOF
EOF.c: В функции 'main': EOF.c:4:5: warning: format '%s' ожидает аргумент типа 'char *', но аргумент 2 имеет тип 'int' [-Wformat=] printf("%s", EOF);
Шестнадцатеричные значения
Еще одна любопытная вещь происходит со следующим кодом:
Если нажать Shift + A , мы получим шестнадцатеричное значение 41, очевидно такое же, как в таблице ASCII. Но для Ctrl + D мы имеем ffffffff опять же - возвращаемое значение getchar() Хранится в c ,
Обратитесь к другим языкам
Обратите внимание, что другие языки избегают этой путаницы, потому что они работают с оценкой состояния выхода из функции, а не сравнивают ее с макросом. Как читать файл в Java?
В заголовочном файле stdio.h содержится объявление не только функции printf() , но и многих других, связанных с вводом-выводом. Среди них есть функции, которые обрабатывают по одному символу за вызов — putchar() и getchar() .
Функция putchar() обычно принимает в качестве аргумента символ, либо символьную переменную и в результате своей работы выводит соответствующий символ на экран. Однако этой функции можно передать любое целое число, но, понятное дело, символа на экране вы можете не получить, если числу не соответствует ни один символ по таблице ASCII. Например:
Функции putchar() и printf() в определенном смысле взаимозаменяемы, так как, используя ту или другую, можно получить один и тот же результат. Хотя программный код будет выглядеть по-разному:
В результате выполнения этого кода на экране будут напечатаны два слова "Hello", разделенные переходом на новую строку. С putchar() это выглядит несколько сложнее. Как мы знаем, любая строка оканчивается нулевым по таблице ASCII символом, в данном случае этот символ служит сигналом для прекращения вывода на экран. Но если бы понадобилось вывести на экран строку, разделяя ее символы каким-нибудь другим символом (например, тире), то и в случае с printf() было бы не так все просто:
Поэтому выбор в пользу той или иной функции зависит от ситуации и ваших предпочтений.
В отличие от функции putchar() функция getchar() не имеет параметром. Когда getchar() выполняется, она считывает их потока ввода один символ и возвращает его в программу. Полученный таким образом символ может быть присвоен переменной, участвовать в выражениях или выводиться на экран с помощью функций вывода.
Если при выполнении этого кода ввести символ, то после нажатия Enter вы увидите два таких же символа на экране:
Первый — результат выполнения функции printf() , второй — putchar() . Если вы перед нажатием Enter введете несколько символов, то прочитан будет только первый, остальные будут проигнорированы. Посмотрите вот на этот код:
Как вы думает, как он будет выполняться? По идее после ввода символа, он должен сразу отображаться на экране функцией putchar() и запрашиваться следующий символ, потому что далее идет снова вызов getchar() . Если вы как корректный пользователь программы сначала введете первый символ и нажмете Enter, то символ отобразиться на экране. Потом вы введете второй символ и после Enter он тоже отобразиться. И тут программа завершится, не дав ввести вам третий символ.
Прежде чем попытаться найти объяснение, изобразим "некорректного пользователя" и перед первым нажатием Enter введем несколько символов (больше двух). После Enter вы увидите три первых символа введенной вами строки, и программа завершиться. Хотя куда логичней было бы ожидать, что будет прочитан только первый символ, потом выведен на экран и потом запрошен следующий символ.
Такое странное на первый взгляд поведение программы связано не с языком C, а с особенностью работы операционных систем, в которых реализован буферный ввод-вывод. При операциях ввода-вывода выделяется область временной памяти (буфер), куда и помещаются поступающие символы. Как только поступает специальный сигнал (например, переход на новую строку при нажатии Enter), данные из буфера передаются по месту своего назначения (на экран, в переменную и др.).
Теперь, зная это, давайте посмотрим, что происходило в нашей программе, и сначала разберем второй случай с "некорректным пользователем", т.к. для понимания этот случай проще. Когда пользователь ввел первый символ, он попал в переменную a, далее сработала функция putchar(a) и символ попал в буфер. Т.к. Enter'а не было, то содержимое буфера на экране не было отображено. Пользователь ввел второй символ, переменная b получила свое значение, а putchar(b) отправила это значение в буфер. Аналогично с третьим символом. Как только пользователь нажал Enter, содержимое буфера было выведено на экран. Но символы, которые были выведены на экран, были выведены не программой, а операционной системой. Программа же выводила символы еще до того, как мы нажали Enter.
Почему же в первом случае при выполнении программы мы смогли ввести и увидеть на экране только два символа? Когда был введен первый символ, то он был присвоен переменной a и далее выведен в буфер. Затем был нажат Enter. Это сигнал для выброса данных их буфера, но это еще и символ перехода на новую строку. Этот символ '\n', наверное, и был благополучно записан в переменную b. Тогда в буфере должен оказаться переход на новую строку, после этого введенный символ (уже помещенный в переменную c). После нажатия Enter мы должны были бы увидеть переход на новую строку от символа '\n' и букву. Однако печатается только буква. Причина этого вероятно в том, что переход на новую строку не хранится в буфере.
Во многих учебниках по языку C приводится пример считывания символов, вводимых пользователем, и их вывод на экран:
В переменной a всегда хранится последний введенный символ, но перед тем как присвоить a новое значение с помощью функции putchar() старое значение сбрасывается в буфер. Как только поступает символ новой строки, работа программы прекращается, а также, поскольку была нажата клавиша Enter, происходит вывод содержимого буфер на экран. Если в условии цикла while будет не символ '\n', а какой-нибудь другой, то программа будет продолжать обрабатывать символы, даже после нажатия Enter. В результате чего мы можем вводить и выводить множество строк текста.
Напишите программу посимвольного ввода-вывода, используя в качестве признака окончания ввода любой символ, кроме '\n'. Протестируйте ее.
При совместном использовании функций putchar() и getchar() обычно пользуются более коротким способом записи. Например:
- Объясните, почему сокращенный вариант записи посимвольного ввода-вывода работает правильно. Для этого опишите последовательность операций в условии цикла while .
- Перепишите вашу программу на более короткий вариант.
Как быть, если требуется "прочитать" с клавиатуры или файла неизвестный текст, в котором может быть абсолютно любой символ? Как сообщить программе, что ввод окончен, и при этом не использовать ни один из возможных символов?
В операционных системах и языках программирования вводят специальное значение, которое служит признаком окончания потока ввода или признаком конца файла. Называется это значение EOF (end of file), а его конкретное значение может быть разным, но чаще всего это число -1. EOF представляет собой константу, в программном коде обычно используется именно имя (идентификатор) константы, а не число -1. EOF определена в файле stdio.h.
В операционных системах GNU/Linux можно передать функции getchar() значение EOF, если нажать комбинацию клавиш Ctrl + D, в Windows – Ctrl + Z.
Исправьте вашу программу таким образом, чтобы считывание потока символов прерывалось признаком EOF.
Решение задач
Не смотря на свою кажущуюся примитивность, функции getchar() и putchar() часто используются, т.к. посимвольный анализ данных при вводе-выводе не такая уж редкая задача. Используя только функцию getchar() , можно получить массив символов (строку) и при этом отсеять ненужные символы. Вот пример помещения в строку только цифр из потока ввода, в котором может быть набран абсолютно любой символ:
Здесь ввод символов может прекратиться не только при поступлении значения EOF, но и в случае, если массив заполнен ( i < N-1 ). В цикле while проверяется условие, что числовой код очередного символа принадлежит диапазону [48, 57]. Именно в этом диапазоне кодируются цифры (0-9). Если поступивший символ является символом-цифрой, то он помещается в массив по индексу i, далее i увеличивается на 1, указывая на следующий элемент массива. После завершения цикла к массиву символов добавляется нулевой символ, т.к. по условию задачи должна быть получена строка (именно для этого символа ранее резервируется одна ячейка массива – N-1 ).
Пожалуйста, приостановите работу AdBlock на этом сайте.
Создание и инициализация строки
Так как строка – это массив символов, то объявление и инициализация строки аналогичны подобным операциям с одномерными массивами.
Следующий код иллюстрирует различные способы инициализации строк.
Рис.1 Объявление и инициализация строк
В первой строке мы просто объявляем массив из десяти символов. Это даже не совсем строка, т.к. в ней отсутствует нуль-символ \0 , пока это просто набор символов.
Вторая строка. Простейший способ инициализации в лоб. Объявляем каждый символ по отдельности. Тут главное не забыть добавить нуль-символ \0 .
Третья строка – аналог второй строки. Обратите внимание на картинку. Т.к. символов в строке справа меньше, чем элементов в массиве, остальные элементы заполнятся \0 .
Четвёртая строка. Как видите, тут не задан размер. Программа его вычислит автоматически и создаст массив символов нужный длины. При этом последним будет вставлен нуль-символ \0 .
Как вывести строку
Дополним код выше до полноценной программы, которая будет выводить созданные строки на экран.
Рис.2 Различные способы вывода строки на экран
Как видите, есть несколько основных способов вывести строку на экран.
- использовать функцию printf со спецификатором %s
- использовать функцию puts
- использовать функцию fputs , указав в качестве второго параметра стандартный поток для вывода stdout .
Единственный нюанс у функций puts и fputs . Обратите внимание, что функция puts переносит вывод на следующую строку, а функция fputs не переносит.
Как видите, с выводом всё достаточно просто.
Ввод строк
С вводом строк всё немного сложнее, чем с выводом. Простейшим способом будет являться следующее:
Функция gets приостанавливает работу программы, читает строку символов, введенных с клавиатуры, и помещает в символьный массив, имя которого передаётся функции в качестве параметра.
Завершением работы функции gets будет являться символ, соответствующий клавише ввод и записываемый в строку как нулевой символ.
Заметили опасность? Если нет, то о ней вас любезно предупредит компилятор. Дело в том, что функция gets завершает работу только тогда, когда пользователь нажимает клавишу ввод. Это чревато тем, что мы можем выйти за рамки массива, в нашем случае — если введено более 20 символов.
К слову, ранее ошибки переполнения буфера считались самым распространенным типом уязвимости. Они встречаются и сейчас, но использовать их для взлома программ стало гораздо сложнее.
Итак, что мы имеем. У нас есть задача: записать строку в массив ограниченного размера. То есть, мы должны как-то контролировать количество символов, вводимых пользователем. И тут нам на помощь приходит функция fgets :
Функция fgets принимает на вход три аргумента: переменную для записи строки, размер записываемой строки и имя потока, откуда взять данные для записи в строку, в данном случае — stdin . Как вы уже знаете из 3 урока, stdin – это стандартный поток ввода данных, обычно связанный с клавиатурой. Совсем необязательно данные должны поступать именно из потока stdin , в дальнейшем эту функцию мы также будем использовать для чтения данных из файлов.
Если в ходе выполнения этой программы мы введем строку длиннее, чем 10 символов, в массив все равно будут записаны только 9 символов с начала и символ переноса строки, fgets «обрежет» строку под необходимую длину.
Обратите внимание, функция fgets считывает не 10 символов, а 9 ! Как мы помним, в строках последний символ зарезервирован для нуль-символа.
Давайте это проверим. Запустим программу из последнего листинга. И введём строку 1234567890 . На экран выведется строка 123456789 .
Рис.3 Пример работы функции fgets
Возникает вопрос. А куда делся десятый символ? А я отвечу. Он никуда не делся, он остался в потоке ввода. Выполните следующую программу.
Вот результат её работы.
Рис.4 Непустой буфер stdin
Поясню произошедшее. Мы вызвали функцию fgets . Она открыла поток ввода и дождалась пока мы введём данные. Мы ввели с клавиатуры 1234567890\n ( \n я обозначаю нажатие клавиша Enter ). Это отправилось в поток ввода stdin . Функция fgets , как и полагается, взяла из потока ввода первые 9 символов 123456789 , добавила к ним нуль-символ \0 и записала это в строку str . В потоке ввода осталось ещё 0\n .
Далее мы объявляем переменную h . Выводим её значение на экран. После чего вызываем функцию scanf . Тут-то ожидается, что мы можем что-то ввести, но т.к. в потоке ввода висит 0\n , то функция scanf воспринимает это как наш ввод, и записывается 0 в переменную h . Далее мы выводим её на экран.
Исправим последний пример так, чтобы его работа была предсказуемой.
Теперь программа будет работать так, как надо.
Рис.4 Сброс буфера stdin функцией fflush
Подводя итог, можно отметить два факта. Первый. На данный момент использование функции gets является небезопасным, поэтому рекомендуется везде использовать функцию fgets .
Второй. Не забывайте очищать буфер ввода, если используете функцию fgets .
На этом разговор о вводе строк закончен. Идём дальше.
Практика
Решите предложенные задачи:
Для удобства работы сразу переходите в полноэкранный режим
Читайте также: