1 что такое форма ввода вывода
Ввод/вывод
С информатике, ввод/вывод (в англ. языке часто используется сокращение I/O — input/output) означает взаимодействие между обработчиком информации (например, компьютер) и внешним миром, который может представлять как человек, так и любая другая система обработки информации. Ввод — сигнал или данные, полученные системой, а вывод — сигнал или данные, посланные ею (или из нее). Термин также может использоваться как обозначение (или дополнение к обозначению) определенного действия: «выполнять ввод/вывод» означает выполнение операций ввода или вывода. Устройства ввода-вывода используются человеком (или другой системой) для взаимодействия с компьютером. Например, клавиатуры и мыши — специально разработанные компьютерные устройства ввода, а мониторы и принтеры — компьютерные устройства вывода. Устройства для взаимодействия между компьютерами, как модемы и сетевые карты, обычно служат устройствами ввода и вывода одновременно.
Стоит отметить, что назначение устройства в качестве устройства ввода или вывода зависит от перспективы. Мыши и клавиатуры принимают физическое взаимодействие, осуществляемое человеком-пользователем (кстати, относительно него это будут действия по выводу информации), и превращает его в сигналы, понятные компьютеру. Вывод информации из этих устройств является вводом ее в компьютер. Аналогично, принтеры и мониторы получают на входе сигналы, которые выводит компьютер. Затем они преобразуют эти сигналы в такой вид, который человек сможет увидеть или прочитать. (Для людей-пользователей процесс чтения или просмотра подобных вариантов представления информации является вводом или получением информации).
В компьютерной архитектуре объединение процессора и основной памяти (то есть памяти, из которой процессор может читать и записывать в нее напрямую с помощью особых инструкций) составляет «мозг» компьютера, и с этой точки зрения, любой обмен информацией с этим объединением, например, с дисковым накопителем, подразумевает ввод-вывод. Процессор и его сопутствующие электронные цепи реализуют ввод-вывод с распределением памяти, используемый в низкоуровневом программировании при реализации драйверов устройств.
Высокоуровневая операционная система и программное обеспечение используют другие, более абстрактные концепции и примитивы ввода-вывода. Например, большинство операционных систем реализуют прикладные программы через концепцию файлов. Языки программирования Си и C++, а также операционные системы семейства Unix, традиционно абстрагируют файлы и устройства в виде потоков данных, из которых можно читать и в которые можно записывать, или и то и другое вместе. Стандартная библиотека языка Си реализует функции для работы с потоками для ввода и вывода данных.
Альтернативой специальным простейшим функциям служит монада ввода-вывода, которая позволяет программам просто описывать ввод-вывод, а действия выносятся за рамки программы. Это весьма примечательно, так как функции ввода-вывода имеют побочные эффекты в любом языке программирования, но сейчас получило распространение чисто функциональное программирование.
Содержание
Интерфейс ввода-вывода
Интерфейс ввода-вывода требует управления процессором каждого устройства. Интерфейс должен иметь соответствующую логику для интерпретации адреса устройства, генерируемого процессором.
Установление контакта должно быть реализовано интерфейсом при помощи соответствующих команд типа (ЗАНЯТ, ГОТОВ, ЖДУ), чтобы процессор мог взаимодействовать с устройством ввода-вывода через интерфейс.
Если существует необходимость передачи различающихся форматов данных, то интерфейс должен уметь конвертировать последовательные (упорядоченные) данные в параллельную форму и наоборот.
Должна быть возможность для генерации прерываний и соответствующих типов чисел для дальнейшей обработки процессором (при необходимости).
Компьютер, использующий ввод-вывод с распределением памяти, обращается к аппаратному обеспечению при помощи чтения и записи в определенные ячейки памяти, используя те же самые инструкции языка ассемблера, которые компьютер обычно использует при обращении к памяти.
Режимы адресации
Существует несколько способов, которыми данные могут быть прочитаны или помещены в память. Каждый метод представляет собой режим адресации и имеет собственные преимущества и ограничения.
Режимы адресации делятся на множество типов, как например, прямая адресация, косвенная (непрямая) адресация, непосредственная адресация, индексная адресация, базовая адресация, базово-индексная адресация, предполагаемая адресация и т. д.
Прямая адресация
В этом типе адрес данных сам является частью инструкции. Когда процессор декодирует инструкцию, он получает адрес ячейки памяти, откуда может быть считана (куда может быть записана) требуемая информация.
В данном случае операнд Addr указывает на область памяти, содержащее данные и копирует их в указанный регистр Reg.
Косвенная адресация
В этом случае адрес может храниться в регистре. Инструкции будут обращаться к регистру, содержащему адрес. То есть, для получения данных, инструкция должна декодировать данные соответствующего регистра. Содержимое регистра будет обработано как адрес, используя который, будет считана/записана информация из/в соответствующую область памяти.
Ввод-вывод с распределением (вводимой информации) по портам (памяти)
Ввод-вывод с распределением (вводимой информации) по портам (памяти) обычно требует применения инструкций, специально разработанных для выполнения операций ввода-вывода.
Определение форм ввода-вывода
Форма для загрузки данных с ТТН (см. форму 14) является многотабличной составной. Основная часть формы строится на основе главной из двух загружаемых таблиц TTN. В нее входят поля NTTN, DOTGR, KZ и SUMOTG. Включаемая многозаписевая форма строится на подчиненной и тоже загружаемой таблице CTTN, в которой загружаются поля NTTN, KI и KOLOTG. Включаемая форма связывается с основной по ключу связи таблиц NTTN. Причем значение ключа NTTN вводится однократно в основную часть формы, но за счет связи с включаемой формой это ключевое поле загружается как в главную, так и в подчиненную таблицы. Таким образам, с одной экранной формы создаются одна запись в таблице TTN и подчиненные ей записи в таблице CTTN.
Рис. 14. Макет формы ввода-вывода для загрузки данных с ТТН в таблицы базы. данных TTN и TTN
Для проверки правильности вводимой информации можно через форму отображать наименования заказчика из таблицы ZAK. Для этого предусмотрим включаемую простую однозаписевую форму, которая строится на базе таблицы ZAK. Заметим, что таблица ZAK является главной по отношению к таблице TTN, используемой для основной части формы. Поэтому будет отображаться единственное значение наименования заказчика. Включаемая форма должна быть связана с основной по ключу связи таблиц KZ.
Для отображения справочной информации о наименовании изделии, единиц измерения и цены предусмотрим несвязанную форму, которая строится на основе таблицы IZD.
Форма для загрузки данных с документа ПТР (см. рис. 15) также является многотабличной составной. Основная часть формы строится на основе главной из двух загружаемых таблиц PTR. В нее входят поля NPTR, KZ, DOPL и SUMOPL. Включаемая многозаписевая форма строится на основе подчиненной и тоже загружаемой таблицы CPTR. В эту включаемую форму вводятся значения KI и KOLOPL. Включаемая форма связывается с основной по составному ключу связи таблиц NPTR+KZ, что обеспечивает формирование записей подчиненной таблицы CPTR при однократном вводе значений этих ключевых полей.
Рис. 15. Макет формы ввода-вывода для загрузки данных с ПТР в таблицы базы данных PTR и CPTR
Для проверки правильности вводимой информации предусмотрено отображение наименования заказчика из таблицы ZAK с помощью включаемой простой одиозаписевой формы, которая строится на базе таблицы ZAK.
Для отображения справочной информации об изделиях предусмотрена несвязанная форма на основе таблицы IZD.
Ввод-вывод в Си
Основной задачей программирования является обработка информации, поэтому любой язык программирования имеет средства для ввода и вывода информации. В языке Си нет операторов ввода-вывода.
Вывод информации
Функция printf() предназначена для форматированного вывода. Она переводит данные в символьное представление и выводит полученные изображения символов на экран. При этом у программиста имеется возможность форматировать данные, то есть влиять на их представление
на экране.
Общая форма записи функции printf() :
СтрокаФорматов состоит из следующих элементов:
Объекты могут отсутствовать.
Управляющие символы не выводятся на экран, а управляют расположением выводимых символов. Отличительной чертой управляющего символа является наличие обратного слэша ‘\’ перед ним.
Основные управляющие символы:
Форматы нужны для того, чтобы указывать вид, в котором информация будет выведена на экран. Отличительной чертой формата является наличие символа процент ‘%’ перед ним:
Результат работы программы
Тот же самый код может быть представлен с использованием одного вызова printf :
Табличный вывод
При указании формата можно явным образом указать общее количество знакомест и количество знакомест, занимаемых дробной частью:
В приведенном примере 10 — общее количество знакомест, отводимое под значение переменной; 5 — количество позиций после разделителя целой и дробной части (после десятичной точки). В указанном примере количество знакомест в выводимом числе меньше 10, поэтому свободные знакоместа слева от числа заполняются пробелами. Такой способ форматирования часто используется для построения таблиц.
Ввод информации
Функция форматированного ввода данных с клавиатуры scanf() выполняет чтение данных, вводимых с клавиатуры, преобразует их во внутренний формат и передает вызывающей функции. При этом программист задает правила интерпретации входных данных с помощью спецификаций форматной строки.
Общая форма записи функции scanf( ) :
Строка форматов и список аргументов для функции обязательны.
Результат работы программы:
Функция scanf( ) является функцией незащищенного ввода, т.к. появилась она в ранних версиях языка Си. Поэтому чтобы разрешить работу данной функции в современных компиляторах необходимо в начало программы добавить строчку
1 что такое форма ввода вывода
Возможности для ввода и вывода не являются частью самого языка Си, поэтому мы подробно и не рассматривали их до сих пор. Между тем реальные программы взаимодействуют со своим окружением гораздо более сложным способом, чем те, которые были затронуты ранее. В этой главе мы опишем стандартную библиотеку, содержащую набор функций, обеспечивающих ввод-вывод, работу со строками, управление памятью, стандартные математические функции и разного рода сервисные Си-программы. Но особое внимание уделим вводу-выводу.
Библиотечные функции ввода-вывода точно определяются стандартом ANSI, так что они совместимы на любых системах, где поддерживается Си. Программы, которые в своем взаимодействии с системным окружением не выходят за рамки возможностей стандартной библиотеки, можно без изменений переносить с одной машины на другую.
7.1 Стандартный ввод-вывод
Во многих системах клавиатуру можно заменить файлом, перенаправив ввод с помощью значка имя-файла вывод можно перенаправить в файл. Например, если prog использует для вывода функцию putchar, то
будет направлять стандартный вывод не на экран, а в outfile. А командная строка
соединит стандартный вывод программы prog со стандартным вводом программы anotherprog.
Вывод, осуществляемый функцией printf, также отправляется в стандартный выходной поток. Вызовы putchar и printf могут как угодно чередоваться, при этом вывод будет формироваться в той последовательности, в которой происходили вызовы этих функций.
Любой исходный Си-файл, использующий хотя бы одну функцию библиотеки ввода-вывода, должен содержать в себе строку
Многие программы читают только из одного входного потока и пишут только в один выходной поток. Для организации ввода-вывода таким программам вполне хватит функций getchar, putchar и printf, а для начального обучения уж точно достаточно ознакомления с этими функциями. В частности, перечисленных функций достаточно, когда требуется вывод одной программы соединить с вводом следующей. В качестве примера рассмотрим программу lower, переводящую свой ввод на нижний регистр:
Упражнение 7.1. Напишите программу, осуществляющую перевод ввода с верхнего регистра на нижний или с нижнего на верхний в зависимости от имени, по которому она вызывается и текст которого находится в arg[0].
7.2 Форматный вывод (printf)
Функция printf переводит внутренние значения в текст.
В предыдущих главах мы использовали printf неформально. Здесь мы покажем наиболее типичные случаи применения этой функции: полное ее описание дано в приложении B.
Функция printf преобразует, форматирует и печатает свои аргументы в стандартном выводе под управлением формата. Возвращает она количество напечатанных символов.
Форматная строка содержит два вида объектов: обычные символы, которые напрямую копируются в выходной поток, и спецификации преобразования, каждая из которых вызывает преобразование и печать очередного аргумента printf. Любая спецификация преобразования начинается знаком % и заканчивается символом-спецификатором. Между % и символом-спецификатором могут быть расположены (в указанном ниже порядке) следующие элементы:
Символы-спецификаторы перечислены в таблице 7.1. Если за % не помещен символ- спецификатор, поведение функции printf будет не определено. Ширину и точность можно специфицировать с помощью *; значение ширины (или точности) в этом случае берется из следующего аргумента (который должен быть типа int). Например, чтобы напечатать не более max символов из строки s, годится следующая запись:
Таблица 7.1 Основные преобразования printf
Большая часть форматных преобразований была продемонстрирована в предыдущих главах. Исключение составляет задание точности для строк. Далее приводится перечень спецификаций и показывается их влияние на печать строки «hello, world», состоящей из 12 символов. Поле специально обрамлено двоеточиями, чтобы была видна его протяженность.
Предостережение: функция printf использует свой первый аргумент, чтобы определить, сколько еще ожидается аргументов и какого они будут типа. Вы не получите правильного результата, если аргументов будет не хватать или они будут принадлежать не тому типу. Вы должны также понимать разницу в следующих двух обращениях:
Функция sprintf выполняет те же преобразования, что и printf, но вывод запоминает в строке
Эта функция форматирует arg1, arg2 и т. д. в соответствии с информацией, заданной аргументом format, как мы описывали ранее, но результат помещает не в стандартный вывод, а в string. Заметим, что строка string должна быть достаточно большой, чтобы в ней поместился результат.
Упражнение 7.2. Напишите программу, которая будет печатать разумным способом любой ввод. Как минимум она должна уметь печатать неграфические символы в восьмеричном или шестнадцатеричном виде (в форме, принятой на вашей машине), обрывая длинные текстовые строки.
7.3 Списки аргументов переменной длины
Этот параграф содержит реализацию минимальной версии printf. Приводится она для того, чтобы показать, как надо писать функции со списками аргументов переменной длины, причем такие, которые были бы переносимы. Поскольку нас главным образом интересует обработка аргументов, функцию minprintf напишем таким образом, что она в основном будет работать с задающей формат строкой и аргументами; что же касается форматных преобразований, то они будут осуществляться с помощью стандартного printf.
Объявление стандартной функции printf выглядит так:
Многоточие в объявлении означает, что число и типы аргументов могут изменяться. Знак многоточие может стоять только в конце списка аргументов. Наша функция minprintf объявляется как
поскольку она не будет выдавать число символов, как это делает printf.
Макрос va_arg на каждом своем вызове выдает очередной аргумент, а ap передвигает на следующий: но имени типа он определяет тип возвращаемого значения и размер шага для выхода на следующий аргумент. Наконец, макрос va_end делает очистку всего, что необходимо. К va_end следует обратиться перед самым выходом из функции.
Перечисленные средства образуют основу нашей упрощенной версии prinf.
Упражнение 7.3. Дополните minprintf другими возможностями printf.
7.4 Форматный ввод (scanf)
Функция scanf, обеспечивающая ввод, является аналогом printf; она выполняет многие из упоминавшихся преобразований, но в противоположном направлении. Ее объявление имеет следующий вид:
Функция scanf читает символы из стандартного входного потока, интерпретирует их согласно спецификациям строки format и рассылает результаты в свои остальные аргументы. Аргумент format мы опишем позже; другие аргументы, каждый из которых должен быть указателем, определяют, где будут запоминаться должным образом преобразованные данные. Как и для printf, в этом параграфе дается сводка наиболее полезных, но отнюдь не всех возможностей данной функции.
Функция scanf прекращает работу, когда оказывается, что исчерпался формат или вводимая величина не соответствует управляющей спецификации. В качестве результата scanf возвращает количество успешно введенных элементов данных. По исчерпании файла она выдает EOF. Существенно то, что значение EOF не равно нулю, поскольку нуль scanf выдает, когда вводимый символ не соответствует первой спецификации форматной строки. Каждое очередное обращение к scanf продолжает ввод символа, следующего сразу за последним обработанным.
Существует также функция sscanf, которая читает из строки (а не из стандартного ввода).
Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в arg1, arg2 и т. д. Последние должны быть указателями.
Формат обычно содержит спецификации, которые используются для управления преобразованиями ввода. В него могут входить следующие элементы:
Спецификация преобразования управляет преобразованием следующего вводимого поля. Обычно результат помещается в переменную, на которую указывает соответствующий аргумент. Однако если в спецификации преобразования присутствует *, то поле ввода пропускается и никакое присваивание не выполняется. Поле ввода определяется как строка без символов-разделителей; оно простирается до следующего символа-разделителя или же ограничено шириной поля, если она задана. Поскольку символ новой строки относится к символам- разделителям, то sscanf при чтении будет переходить с одной строки на другую. (Символами-разделителями являются символы пробела, табуляции, новой строки, возврата каретки, вертикальной табуляции и перевода страницы.)
Символ-спецификатор указывает, каким образом следует интерпретировать очередное поле ввода. Соответствующий аргумент должен быть указателем, как того требует механизм передачи параметров по значению, принятый в Си. Символы-спецификаторы приведены в таблице 7.2.
Таблица 7.2 Основные преобразования scanf
| Символ | Вводимые данные; тип аргумента |
|---|---|
| d | десятичное целое: int * |
| i | целое: int *. Целое может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева) |
| o | восьмеричное целое (с нулем слева или без него); int * |
| u | беззнаковое десятичное целое; unsigned int * |
| x | шестнадцатеричное целое (с 0x или 0X слева или без них); int * |
| c | символы; char *. Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов- разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте %1s |
| s | Строка символов(без обрамляющих кавычек); char *, указывающая на массив символов, достаточный для строки и завершающего символа ‘\0’, который будет добавлен |
| e, f, g | число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float * |
| % | сам знак %, никакое присваивание не выполняется |
Чтобы построить первый пример, обратимся к программе калькулятора из главы 4, в которой организуем ввод с помощью функции scanf:
Предположим, что нам нужно прочитать строки ввода, содержащие данные вида
Обращение к scanf выглядит следующим образом:
Знак & перед monthname не нужен, так как имя массива есть указатель.
В строке формата могут присутствовать символы, не участвующие ни в одной из спецификаций; это значит, что эти символы должны появиться на вводе. Так, мы могли бы читать даты вида mm/dd/yy с помощью следующего обращения к scanf:
В своем формате функция scanf игнорирует пробелы и табуляции. Кроме того, при поиске следующей порции ввода она пропускает во входном потоке все символы- разделители (пробелы, табуляции, новые строки и т.д.). Воспринимать входной поток, не имеющий фиксированного формата, часто оказывается удобнее, если вводить всю строку целиком и для каждого отдельного случая подбирать подходящий вариант sscanf. Предположим, например, что нам нужно читать строки с датами, записанными в любой из приведенных выше форм. Тогда мы могли бы написать:
Обращения к scanf могут перемежаться с вызовами других функций ввода. Любая функция ввода, вызванная после scanf, продолжит чтение с первого еще непрочитанного символа.
В завершение еще раз напомним, что аргументы функций scanf и sscanf должны быть указателями.
Одна из самых распространенных ошибок состоит в том, что вместо того, чтобы написать
Компилятор о подобной ошибке ничего не сообщает.
Упражнение 7.4. Напишите свою версию scanf по аналогии с minprintf из предыдущего параграфа.
Упражнение 7.5. Перепишите основанную на постфиксной записи программу калькулятора из главы 4 таким образом, чтобы для ввода и преобразования чисел она использовала scanf и/или sscanf.
7.5 Доступ к файлам
Во всех предыдущих примерах мы имели дело со стандартным вводом и стандартным выводом, которые для программы автоматически предопределены операционной системой конкретной машины.
направит в стандартный вывод содержимое файлов x.c и y.c (и ничего более).
Возникает вопрос: что надо сделать, чтобы именованные файлы можно было читать; иначе говоря, как связать внешние имена, придуманные пользователем, с инструкциями чтения данных?
На этот счет имеются простые правила. Для того чтобы можно было читать из файла или писать в файл, он должен быть предварительно открыт с помощью библиотечной функции fopen. Функция fopen получает внешнее имя типа x.c или y.c, после чего осуществляет некоторые организационные действия и «переговоры» с операционной системой (технические детали которых здесь не рассматриваются) и возвращает указатель, используемый в дальнейшем для доступа к файлу.
Это говорит, что fp есть указатель на FILE, a fopen возвращает указатель на FILE. Заметим, что FILE — это имя типа) наподобие int, а не тег структуры. Оно определено с помощью typedef. (Детали того, как можно реализовать fopen в системе UNIX, приводятся в параграфе 8.5.)
Обращение к fopen в программе может выглядеть следующим образом:
Тот факт, что некий файл, которого раньше не было, открывается на запись или добавление, означает, что он создается (если такая процедура физически возможна). Открытие уже существующего файла на запись приводит к выбрасыванию его старого содержимого, в то время как при открытии файла на добавление его старое содержимое сохраняется. Попытка читать несуществующий файл является ошибкой. Могут иметь место и другие ошибки; например, ошибкой считается попытка чтения файла, который по статусу запрещено читать. При наличии любой ошибки fopen возвращает NULL. (Возможна более точная идентификация ошибки; детальная информация по этому поводу приводится в конце параграфа 1 приложения B.)
Функция getc возвращает следующий символ из потока, на который указывает *fp; в случае исчерпания файла или ошибки она возвращает EOF.
Функция putc пишет символ c в файл fp
и возвращает записанный символ или EOF в случае ошибки. Аналогично getchar и putchar, реализация getc и putc может быть выполнена в виде макросов, а не функций.
С помощью getc, putc, stdin и stdout функции getchar и putchar теперь можно определить следующим образом:
Форматный ввод-вывод файлов можно построить на функциях fscanf и fprintf. Они идентичны scanf и printf с той лишь разницей, что первым их аргументом является указатель на файл, для которого осуществляется ввод-вывод, формат же указывается вторым аргументом.
Вот теперь мы располагаем теми сведениями, которые достаточны для написания программы cat, предназначенной для конкатенации (последовательного соединения) файлов. Предлагаемая версия функции cat, как оказалось, удобна для многих программ. Если в командной строке присутствуют аргументы, они рассматриваются как имена последовательно обрабатываемых файлов. Если аргументов нет, то обработке подвергается стандартный ввод.
Файловые указатели stdin и stdout представляют собой объекты типа FILE*. Это константы, а не переменные, следовательно, им нельзя ничего присваивать.
7.6 Управление ошибками (stderr и exit)
Обработку ошибок в cat нельзя признать идеальной. Беда в том, что если файл по какой-либо причине недоступен, сообщение об этом мы получим по окончании конкатенируемого вывода. Это нас устроило бы, если бы вывод отправлялся только на экран, a не в файл или по конвейеру другой программе. Чтобы лучше справиться с этой проблемой, программе помимо стандартного вывода stdout придается еще один выходной поток, называемый stderr. Вывод в stderr обычно отправляется на экран, даже если вывод stdout перенаправлен в другое место. Перепишем cat так, чтобы сообщения об ошибках отправлялись в stderr.
Инструкция return exp главной программы main эквивалентна обращению к функции exit(exp). Последний вариант (с помощью exit) имеет то преимущество, что он пригоден для выхода и из других функций, и, кроме того, слово exit легко обнаружить с помощью программы контекстного поиска, похожей на ту, которую мы рассматривали в главе 5. Функция ferror выдает ненулевое значение, если в файле fp была обнаружена ошибка.
Хотя при выводе редко возникают ошибки, все же они встречаются (например, оказался переполненным диск); поэтому в программах широкого пользования они должны тщательно контролироваться.
Функция feof(FILE *fp) aнaлoгичнa функции ferror; oнa вoзвpaщaeт нeнулевое значение, если встретился конец указанного в аргументе файла.
В наших небольших иллюстративных программах мы не заботились о выдаче статуса выхода, т. е. выдаче некоторого числа, характеризующего состояние программы в момент завершения: работа закончилась нормально или прервана из-за ошибки? Если работа прервана в результате ошибки, то какой? Любая серьезная программа должна выдавать статус выхода.
7.7 Ввод-вывод строк
В стандартной библиотеке имеется программа ввода fgets, аналогичная программе getline, которой мы пользовались в предыдущих главах.
Функция вывода fputs пишет строку (которая может и не заканчиваться символом новой строки) в файл.
Эта функция возвращает EOF, если возникла ошибка, и неотрицательное значение в противном случае.
Библиотечные функции gets и puts подобны функциям fgets и fputs. Отличаются они тем, что оперируют только стандартными файлами stdin и stdout, и кроме того, gets выбрасывает последний символ ‘\n’, a puts его добавляет.
Чтобы показать, что ничего особенного в функциях вроде fgets и fputs нет, мы приводим их здесь в том виде, в каком они существуют в стандартной библиотеке на нашей системе.
С помощью fgets легко реализовать нашу функцию getline:
Упражнение 7.6. Напишите программу, сравнивающую два файла и печатающую первую строку, в которой они различаются.
Упражнение 7.7. Модифицируйте программу поиска по образцу из главы 5 таким образом, чтобы она брала текст из множества именованных файлов, а если имен файлов в аргументах нет, то из стандартного ввода. Будет ли печататься имя файла, в котором найдена подходящая строка?
Упражнение 7.8. Напишите программу, печатающую несколько файлов. Каждый файл должен начинаться с новой страницы, предваряться заголовком и иметь свою нумерацию страниц.
7.8 Другие библиотечные функции
В стандартной библиотеке представлен широкий спектр различных функций. Настоящий параграф содержит краткий обзор наиболее полезных из них. Более подробно эти и другие функции описаны в приложении B.
7.8.1 Операции со строками
| strcat(s,t) | — приписывает t в конец s. |
| strncat(s,t,n) | — приписывает n символов из t в конец s. |
| strcmp(s,t) | — возвращает отрицательное число, нуль или положительное число для s t, соответственно. |
| strncmp(s,t,n) | — делает то же, что и strcmp, но количество сравниваемых символов не может превышать n |
| strcpy(s,t) | — копирует t в s. |
| strncpy(s,t,n) | — копирует не более n символов из t в s. |
| strlen(s) | — возвращает длину s. |
| strchr(s,c) | — возвращает указатель на первое появление символа c в s или, если c нет в s, NULL. |
| strrchr(s,c) | — возвращает указатель на последнее появление символа c в s или, если c нет в s, NULL. |
7.8.2 Анализ класса символов и преобразование символов
7.8.3 Функция ungetc
В стандартной библиотеке содержится более ограниченная версия функции ungetch по сравнению с той, которую мы написали в главе 4. Называется она ungetc. Эта функция, имеющая прототип
отправляет символ c назад в файл fp и возвращает c, а в случае ошибки EOF. Для каждого файла гарантирован возврат не более одного символа. Функцию ungetc можно использовать совместно с любой из функций ввода вроде scanf, getc, getchar и т. д.
7.8.4 Исполнение команд операционной системы
Функция system(char *s) выполняет команду системы, содержащуюся в строке s, и затем возвращается к выполнению текущей программы.
Содержимое s, строго говоря, зависит от конкретной операционной системы. Рассмотрим простой пример: в системе UNIX инструкция
7.8.5 Управление памятью
Функции malloc и calloc динамически запрашивают блоки свободной памяти. Функция malloc
возвращает указатель на n байт неинициализированной памяти или NULL, если запрос удовлетворить нельзя. Функция calloc
возвращает указатель на область, достаточную для хранения массива из n объектов указанного размера (size), или NULL, если запрос не удается удовлетворить. Выделенная память обнуляется.
Указатель, возвращаемый функциями malloc и calloc, будет выдан с учетом выравнивания, выполненного согласно указанному типу объекта. Тем не менее к нему должна быть применена операция приведения к соответствующему типу (Как уже отмечалось (см. примеч. в параграфе 6.5), замечания о приведении типов значений, возвращаемых функциями malloc или calloc, — неверно. — Примеч. авт.), как это сделано в следующем фрагменте программы:
Нельзя также использовать те области памяти, которые уже освобождены. Следующий пример демонстрирует типичную ошибку в цикле, освобождающем элементы списка.
Правильным будет, если вы до освобождения сохраните то, что вам потребуется, как в следующем цикле:
В параграфе 8.7 мы рассмотрим реализацию программы управления памятью вроде malloc, позволяющую освобождать выделенные блоки памяти в любой последовательности.
7.8.6 Математические функции
В описано более двадцати математических функций. Здесь же приведены наиболее употребительные. Каждая из них имеет один или два аргумента типа double и возвращает результат также типа double.
| sin(x) | — синус x, x в радианах |
| cos(x) | — косинус x, x в радианах |
| atan2(y,x) | — арктангенс y/x, y и x в радианах |
| exp(x) | — экспоненциальная функция e в степени x |
| log(x) | — натуральный (по основанию e) логарифм x (x>0) |
| log10(x) | — обычный (по основанию 10) логарифм x (x>0) |
| pow(x,y) | — x в степени y |
| sqrt(x) | — корень квадратный x (x > 0) |
| fabs(x) | — абсолютное значение x |
7.8.7 Генератор случайных чисел
(Если в вашей библиотеке уже есть функция для получения случайных чисел с плавающей точкой, вполне возможно, что ее статистические характеристики лучше указанной.)
Функция srand(unsigned) устанавливает семя для rand. Реализации rand и srand, предлагаемые стандартом и, следовательно, переносимые на различные машины, рассмотрены в параграфе 2.7.
Упражнение 7.9. Реализуя функции вроде isupper, можно экономить либо память, либо время. Напишите оба варианта функции.




