направления.
Вывод, осуществляемый функцией PRINTF, также поступает в
стандартный вывод, и обращения к PUTCHAR и PRINTF могут пе-
ремежаться.
Поразительное количество программ читает только из одно-
го входного потока и пишет только в один выходной поток; для
таких программ ввод и вывод с помощью функций GETCHAR,
PUTCHAR и PRINTF может оказаться вполне адекватным и для на-
чала определенно достаточным. Это особенно справедливо тог-
да, когда имеется возможность указания файлов для ввода и
вывода и поточный механизм для связи вывода одной программы
с вводом другой. Рассмотрим, например, программу LOWER, ко-
торая преобразует прописные буквы из своего ввода в строч-
ные:
#INCLUDE
MAIN() /* CONVERT INPUT TO LOWER CASE */
\(
INT C;
WHILE ((C = GETCHAR()) != EOF)
PUTCHAR(ISUPPER(C) ? TOLOWER(C) : C);
\)
"Функции" ISUPPER и TOLOWER на самом деле являются макроса-
ми, определенными в STDIO.H . Макрос ISUPPER проверяет, яв-
ляется ли его аргумент буквой из верхнего регистра, и возв-
ращает ненулевое значение, если это так, и нуль в противном
случае. Макрос TOLOWER преобразует букву из верхнего регист-
ра в ту же букву нижнего регистра. Независимо от того, как
эти функции реализованы на конкретной машине, их внешнее по-
ведение совершенно одинаково, так что использующие их прог-
раммы избавлены от знания символьного набора.
Если требуется преобразовать несколько файлов, то можно
собрать эти файлы с помощью программы, подобной утилите CAT
системы UNIX,
CAT FILE1 FILE2 ... \! LOWER>OUTPUT
и избежать тем самым вопроса о том, как обратиться к этим
файлам из программы. (Программа CAT приводится позже в этой
главе).
Кроме того отметим, что в стандартной библиотеке вво-
да/вывода "функции" GETCHAR и PUTCHAR на самом деле могут
быть макросами. Это позволяет избежать накладных расходов на
обращение к функции для обработки каждого символа. В главе 8
мы продемонстрируем, как это делается.
7.3. Форматный вывод - функция PRINTF
Две функции: PRINTF для вывода и SCANF для ввода (следу-
ющий раздел) позволяют преобразовывать численные величины в
символьное представлEние и обратно. Они также позволяют ге-
нерировать и интерпретировать форматные строки. Мы уже всюду
в предыдущих главах неформально использовали функцию PRINTF;
здесь приводится более полное и точное описание. Функция
PRINTF(CONTROL, ARG1, ARG2, ...)
преобразует, определяет формат и печатает свои аргументы в
стандартный вывод под управлением строки CONTROL. Управляю-
щая строка содержит два типа объектов: обычные символы, ко-
торые просто копируются в выходной поток, и спецификации
преобразований, каждая из которых вызывает преобразование и
печать очередного аргумента PRINTF.
Каждая спецификация преобразования начинается с символа
% и заканчивается символом преобразования. Между % и симво-
лом преобразования могут находиться:
- знак минус, который указывает о выравнивании преобразован-
ного аргумента по левому краю его поля.
- Строка цифр, задающая минимальную ширину поля. Преобразо-
ванное число будет напечатано в поле по крайней мере этой
ширины, а если необходимо, то и в более широком. Если пре-
образованный аргумент имеет меньше символов, чем указанная
ширина поля, то он будет дополнен слева (или справа, если
было указано выравнивание по левому краю)заполняющими сим-
волами до этой ширины. Заполняющим символом обычно являет-
ся пробел, а если ширина поля указывается с лидирующим ну-
лем, то этим символом будет нуль (лидирующий нуль в данном
случае не означает восьмеричной ширины поля).
- Точка, которая отделяет ширину поля от следующей строки
цифр.
- Строка цифр (точность), которая указывает максимальное
число символов строки, которые должны быть напечатаны, или
число печатаемых справа от десятичной точки цифр для пере-
менных типа FLOAT или DOUBLE.
- Модификатор длины L, который указывает, что соответствую-
щий элемент данных имеет тип LONG, а не INT.
Ниже приводятся символы преобразования и их смысл:
D - аргумент преобразуется к десятичному виду.
O - Аргумент преобразуется в беззнаковую восьмеричную форму
(без лидирующего нуля).
X - Аргумент преобразуется в беззнаковую шестнадцатеричную
форму (без лидирующих 0X).
U - Аргумент преобразуется в беззнаковую десятичную форму.
C - Аргумент рассматривается как отдельный символ.
S - Аргумент является строкой: символы строки печатаются до
тех пор, пока не будет достигнут нулевой символ или не бу-
дет напечатано количество символов, указанное в специфика-
ции точности.
E - Аргумент, рассматриваемый как переменная типа FLOAT или
DOUBLE, преобразуется в десятичную форму в виде
[-]M.NNNNNNE[+-]XX, где длина строки из N определяется
указанной точностью. Точность по умолчанию равна 6.
F - Аргумент, рассматриваемый как переменная типа FLOAT или
DOUBLE, преобразуется в десятичную форму в виде
[-]MMM.NNNNN, где длина строки из N определяется указанной
точностью. Точность по умолчанию равна 6. отметим, что эта
точность не определяет количество печатаемых в формате F
значащих цифр.
G - Используется или формат %E или %F, какой короче; незна-
чащие нули не печатаются.
Если идущий за % символ не является символом преобразования,
то печатается сам этот символ; следовательно,символ % можно
напечатать, указав %%.
Большинство из форматных преобразований очевидно и было
проиллюстрировано в предыдущих главах. Единственным исключе-
нием является то, как точность взаимодействует со строками.
Следующая таблица демонстрирует влияние задания различных
спецификаций на печать "HELLO, WORLD" (12 символов). Мы по-
местили двоеточия вокруг каждого поля для того, чтобы вы
могли видеть его протяженность.
:%10S: :HELLO, WORLD:
:%10-S: :HELLO, WORLD:
:%20S: : HELLO, WORLD:
:%-20S: :HELLO, WORLD :
:%20.10S: : HELLO, WOR:
:%-20.10S: :HELLO, WOR :
:%.10S: :HELLO, WOR:
Предостережение: PRINTF использует свой первый аргумент
для определения числа последующих аргументов и их типов. Ес-
ли количество аргументов окажется недостаточным или они бу-
дут иметь несоответственные типы, то возникнет путаница и вы
получите бессмысленные результаты.
Упражнение 7-1
--------------
Напишите программу, которая будет печатать разумным об-
разом произвольный ввод. Как минимум она должна печатать
неграфические символы в восьмеричном или шестнадцатеричном
виде (в соответствии с принятыми у вас обычаями) и склады-
вать длинные строки.
7.4. Форматный ввод - функция SCANF
Осуществляющая ввод функция SCANF является аналогом
PRINTF и позволяет проводить в обратном направлении многие
из тех же самых преобразований. Функция
SCANF(CONTROL, ARG1, ARG2, ...)
читает символы из стандартного ввода, интерпретирует их в
соответствии с форматом, указанном в аргументе CONTROL, и
помещает результаты в остальные аргументы. Управляющий аргу-
мент описывается ниже; другие аргументы, каждый из которых
должен быть указателем, определяют, куда следует поместить
соответствующим образом преобразованный ввод.
Управляющая строка обычно содержит спецификации преобра-
зования, которые используются для непосредственной интерпре-
тации входных последовательностей. Управляющая строка может
содержать:
- пробелы, табуляции или символы новой строки ("символы пус-
тых промежутков"), которые игнорируются.
- Обычные символы (не %), которые предполагаются совпадающи-
ми со следующими отличными от символов пустых промежутков
символами входного потока.
- Спецификации преобразования, состоящие из символа %, нео-
бязательного символа подавления присваивания *, необяза-
тельного числа, задающего максимальную ширину поля и сим-
вола преобразования.
Спецификация преобразования управляет преобразованием
следующего поля ввода. нормально результат помещается в пе-
ременную, которая указывается соответствующим аргументом.
Если, однако , с помощью символа * указано подавление прис-
ваивания, то это поле ввода просто пропускается и никакого
присваивания не производится. Поле ввода определяется как
строка символов, которые отличны от символов простых проме-
жутков; оно продолжается либо до следующего символа пустого
промежутка, либо пока не будет исчерпана ширина поля, если
она указана. Отсюда следует, что при поиске нужного ей вво-
да, функция SCANF будет пересекать границы строк, поскольку
символ новой строки входит в число пустых промежутков.
Символ преобразования определяет интерпретацию поля вво-
да; согласно требованиям основанной на вызове по значению
семантики языка "с" соответствующий аргумент должен быть
указателем. Допускаются следующие символы преобразования:
D - на вводе ожидается десятичное целое; соответствующий ар-
гумент должен быть указателем на целое.
O - На вводе ожидается восьмеричное целое (с лидирующим ну-
лем или без него); соответствующий аргумент должен быть
указателем на целое.
X - На вводе ожидается шестнадцатеричное целое (с лидирующи-
ми 0X или без них); соответствующий аргумент должен быть
указателем на целое.
H - На вводе ожидается целое типа SHORT; соответсвующий ар-
гумент должен быть указателем на целое типа SHORT.
C - Ожидается отдельный символ; соответствующий аргумент
должен быть указателем на символы; следующий вводимый
символ помещается в указанное место. Обычный пропуск сим-
волов пустых промежутков в этом случае подавляется; для
чтения следующего символа, который не является символом
пустого промежутка, пользуйтесь спецификацией преобразо-
вания %1S.
S - Ожидается символьная строка; соответствующий аргумент
должен быть указателем символов, который указывает на
массив символов, который достаточно велик для принятия
строки и добавляемого в конце символа \0.
F - Ожидается число с плавающей точкой; соответствующий ар-
гумент должен быть указателем на переменную типа FLOAT.
Е - символ преобразования E является синонимом для F. Формат
ввода переменной типа FLOAT включает необязательный знак,
строку цифр, возможно содержащую десятичную точку и нео-
бязательное поле экспоненты, состоящее из буквы E, за ко-
торой следует целое, возможно имеющее знак.
Перед символами преобразования D, O и X может стоять L,
которая означает , что в списке аргументов должен находиться
указатель на переменную типа LONG, а не типа INT. Аналогич-
но, буква L может стоять перед символами преобразования E
или F, говоря о том, что в списке аргументов должен нахо-
диться указатель на переменную типа DOUBLE, а не типа FLOAT.
Например, обращение
INT I;
FLOAT X;
CHAR NAME[50];
SCANF("&D %F %S", &I, &X, NAME);
со строкой на вводе
25 54.32E-1 THOMPSON
приводит к присваиванию I значения 25,X - значения 5.432 и
NAME - строки "THOMPSON", надлежащим образом законченной
символом \ 0. эти три поля ввода можно разделить столькими
пробелами, табуляциями и символами новых строк, сколько вы
пожелаете. Обращение
INT I;
FLOAT X;
CHAR NAME[50];
SCANF("%2D %F %*D %2S", &I, &X, NAME);
с вводом
56789 0123 45A72
присвоит I значение 56, X - 789.0, пропустит 0123 и поместит
в NAME строку "45". при следующем обращении к любой процеду-
ре ввода рассмотрение начнется с буквы A. В этих двух приме-
рах NAME является указателем и, следовательно, перед ним не
нужно помещать знак &.
В качестве другого примера перепишем теперь элементарный
калькулятор из главы 4, используя для преобразования ввода
функцию SCANF:
#INCLUDE
MAIN() /* RUDIMENTARY DESK CALCULATOR */
\(
DOUBLE SUM, V;
SUM =0;
WHILE (SCANF("%LF", &V) !=EOF)
PRINTF("\T%.2F\N", SUM += V);
\)
выполнение функции SCANF заканчивается либо тогда, когда она
исчерпывает свою управляющую строку, либо когда некоторый
элемент ввода не совпадает с управляющей спецификацией. В