Главная страница
Навигация по странице:

  • 6.9. Определение типа

  • 7. ВВОД И ВЫВОД

  • 7.1. Обращение к стандартной библиотеке

  • 7.2. Стандартный ввод и вывод - функции GETCHAR и PUTCHAR

  • 7.3. Форматный вывод - функция PRINTF

  • Упражнение 7-1.

  • Язык С (Керниган, Ричи). Язык сиБ. В. Керниган, Д. М. Ричи


    Скачать 1.46 Mb.
    НазваниеЯзык сиБ. В. Керниган, Д. М. Ричи
    АнкорЯзык С (Керниган, Ричи).pdf
    Дата23.04.2018
    Размер1.46 Mb.
    Формат файлаpdf
    Имя файлаЯзык С (Керниган, Ричи).pdf
    ТипДокументы
    #18413
    страница15 из 23
    1   ...   11   12   13   14   15   16   17   18   ...   23
    6.8. Объединения.
    Oбъединения - это переменная, которая в различные моменты времени может содержать объекты разных типов и размеров, причем компилятор берет на себя отслеживание размера и требований выравнивания. Объединения представляют возможность работать с различными видами данных в одной области памяти,
    не вводя в программу никакой машинно-зависимой информации.
    В качестве примера, снова из символьной таблицы компилятора,
    предположим, что константы могут быть типа INT , FLOAT или быть указателями на символы. значение каждой конкретной константы должно храниться в переменной соотвествующего типа, но все же для управления таблицей самым удобным было бы, если это значение занимало бы один и тот же объем памяти и хранилось в том же самом месте независимо от его типа.
    это и является назначением объединения - выделить отдельную переменную,
    в которой можно законно хранить любую одну из переменных нескольких типов. Как и в случае полей, синтаксис основывается на структурах.
    UNION U_TAG \(
    INT IVAL;
    FLOAT FVAL;
    CHAR *PVAL;
    \) UVAL;
    Переменная UVAL будет иметь достаточно большой размер,чтобы хранить наибольший из трех типов, независимо от машины, на которой осуществляется компиляция, - программа не будет зависить от характеристик аппаратных средств. Любой из этих трех типов может быть присвоен UVAR
    и затем использован в выражениях, пока такое использование совместимо:
    извлекаемый тип должен совпадать с последним помещенным типом. Дело программиста - следить за тем, какой тип хранится в объединении в данный момент; если что-либо хранится как один тип, а извлекается как другой, то результаты будут зависеть от используемой машины.
    Синтаксически доступ к членам объединения осуществляется следующим образом:
    имя объединения.член

    «Язык С» Б.В. Керниган, Д.М. Ричи
    147
    или указатель объединения ->член то есть точно так же, как и в случае структур. если для отслеживания типа, хранимого в данный момент в UVAL, используется переменная UTYPE,
    то можно встретить такой участок программы:
    IF (UTYPE == INT)
    PRINTF(“%D\N”,
    UVAL.IVAL);
    ELSE IF (UTYPE
    == FLOAT)
    PRINTF(“%F\N”,
    UVAL.FVAL);
    ELSE IF (UTYPE
    == STRING)
    PRINTF(“%S\N”,
    UVAL.PVAL);
    ELSE
    PRINTF(“BAD TYPE %D IN UTYPE\N”, UTYPE);
    Объединения могут появляться внутри структур и массивов и наоборот.
    Запись для обращения к члену объединения в структуре (или наоборот)
    совершенно идентична той, которая используется во вложенных структурах.
    например, в массиве структур, определенным следующим образом
    STRUCT \(
    CHAR *NAME;
    INT FLAGS;
    INT UTYPE;
    UNION \(
    INT IVAL;
    FLOAT FVAL;
    CHAR *PVAL;
    \) UVAL;
    \) SYMTAB[NSYM];
    на переменную IVAL можно сослаться как
    SYMTAB[I].UVAL.IVAL
    а на первый символ строки PVAL как
    *SYMTAB[I].UVAL.PVAL
    В сущности объединение является структурой, в которой все члены имеют нулевое смещение. Сама структура достаточно велика, чтобы хранить “самый широкий” член, и выравнивание пригодно для всех типов, входящих в объединение. Как и в случае структур, единственными операциями, которые в настоящее время можно проводить с объединениями, являются доступ к члену и извлечение адреса; объединения не могут быть присвоены,
    переданы функциям или возвращены ими. указатели объединений можно использовать в точно такой же манере, как и указатели структур.

    148
    «Язык С» Б.В. Керниган, Д.М. Ричи
    Программа распределения памяти, приводимая в главе 8 , показывает,
    как можно использовать объединение, чтобы сделать некоторую переменную выровненной по определенному виду границы памяти.
    6.9. Определение типа
    В языке “C” предусмотрена возможность, называемая TYPEDEF для введения новых имен для типов данных. Например, описание
    TYPEDEF INT LENGTH;
    делает имя LENGTH синонимом для INT. “Тип” LENGTH может быть использован в описаниях, переводов типов и т.д. Точно таким же образом, как и тип INT:
    LENGTH LEN, MAXLEN;
    LENGTH *LENGTHS[];
    Аналогично описанию
    TYPEDEF CHAR *STRING;
    делает STRING синонимом для CHAR*, то есть для указателя на символы, что затем можно использовать в описаниях вида
    STRING P, LINEPTR[LINES], ALLOC();
    Обратите внимание, что объявляемый в конструкции TYPEDEF тип появляется в позиции имени переменной, а не сразу за словом TYPEDEF.
    Синтаксически конструкция TYPEDEF подобна описаниям класса памяти
    EXTERN, STATIC и т. Д. мы также использовали прописные буквы, чтобы яснее выделить имена.
    В качестве более сложного примера мы используем конструкцию
    TYPEDEF для описания узлов дерева, рассмотренных ранее в этой главе:
    TYPEDEF STRUCT TNODE \( /* THE BASIC NODE */
    CHAR *WORD; /* POINTS TO THE TEXT */
    INT COUNT; /* NUMBER OF OCCURRENCES */
    STRUCT TNODE *LEFT;
    /* LEFT CHILD */
    STRUCT TNODE *RIGHT;
    /* RIGHT CHILD */
    \) TREENODE, *TREEPTR;
    В результате получаем два новых ключевых слова: TREENODE
    (структура) и TREEPTR (указатель на структуру). Тогда функцию TALLOC
    можно записать в виде
    TREEPTR TALLOC()
    \(

    «Язык С» Б.В. Керниган, Д.М. Ричи
    149
    CHAR *ALLOC();
    RETURN((TREEPTR) ALLOC(SIZEOF(TREENODE)));
    \)
    Необходимо подчеркнуть, что описание TYPEDEF не приводит к созданию нового в каком-либо смысле типа; оно только добавляет новое имя для некоторого существующего типа. при этом не возникает и никакой новой семантики: описанные таким способом переменные обладают точно теми же свойствами, что и переменные, описанные явным образом. По существу конструкция TYPEDEF сходна с #DEFINE за исключением того, что она интерпретируется компилятором и потому может осуществлять подстановки текста, которые выходят за пределы возможностей макропроцессора языка
    “C”. Например,
    TYPEDEF INT (*PFI) ();
    создает тип PFI для “указателя функции, возвращающей значение типа
    INT”, который затем можно было бы использовать в программе сортировки из главы 5 в контексте вида
    PFI STRCMP, NUMCMP, SWAP;
    Имеются две основные причины применения описаний TYPEDEF. Первая причина связана с параметризацией программы, чтобы облегчить решение проблемы переносимости. Если для типов данных, которые могут быть машинно-зависимыми, использовать описание TYPEDEF, то при переносе программы на другую машину придется изменить только эти описания. Одна из типичных ситуаций состоит в использовании определяемых с помощью
    TYPEDEF имен для различных целых величин и в последующем подходящем выборе типов SHORT, INT и LONG для каждой имеющейся машины.
    Второе назначение TYPEDEF состоит в обеспечении лучшей доку- ментации для программы - тип с именем TREEPTR может оказаться более удобным для восприятия, чем тип, который описан только как указатель сложной структуры.
    И наконец, всегда существует вероятность, что в будущем компилятор или некоторая другая программа, такая как LINT, сможет использовать содержащуюся в описаниях TYPEDEF информацию для проведения некоторой дополнительной проверки программы.

    150
    «Язык С» Б.В. Керниган, Д.М. Ричи
    7. ВВОД И ВЫВОД
    Средства ввода/вывода не являются составной частью языка “с”, так что мы не выделяли их в нашем предыдущем изложении. Однако реальные программы взаимодействуют со своей окружающей средой гораздо более сложным образом,
    чем мы видели до сих пор. В этой главе будет описана “стандартная библиотека ввода/вывода”, то есть набор функций, разработанных для обеспечения стандартной системы ввода/вывода для “с”- программ. Эти функции предназначены для удобства программного интерфейса, и все же отражают только те операции, которые могут быть обеспечены на большинстве современных операционных систем. Процедуры достаточно эффективны для того, чтобы пользователи редко чувствовали необходимость обойти их “ради эффективности”, как бы ни была важна конкретная задача. И, наконец, эти процедуры задуманы быть “переносимыми” в том смысле, что они должны существовать в совместимом виде на любой системе, где имеется язык “с”, и что программы, которые ограничивают свои взаимодействия с системой возможностями, предоставляемыми стандартной библиотекой, можно будет переносить с одной системы на другую по существу без изменений.
    Мы здесь не будем пытаться описать всю библиотеку ввода/вывода; мы более заинтересованы в том, чтобы продемонстрировать сущность написания
    “с”-программ, которые взаимодействуют со своей операционной средой.
    7.1. Обращение к стандартной библиотеке
    Каждый исходный файл, который обращается к функции из стандартной библиотеки, должен вблизи начала содержать строку
    #INCLUDE
    в файле STDIO.H определяются некоторые макросы и переменные,
    используемые библиотекой ввода/вывода. Использование угловых скобок вместо обычных двойных кавычек - указание компилятору искать этот файл в справочнике, содержащем заголовки стандартной информации (на системе
    UNIX обычно LUSRLINELUDE).
    Кроме того, при загрузке программы может оказаться необходимым указать библиотеку явно; на системе PDP-11 UNIX, например, команда компиляции программы имела бы вид:
    CC исходные файлы и т.д. -LS
    где -LS указывает на загрузку из стандартной библиотеки.
    7.2. Стандартный ввод и вывод - функции GETCHAR и
    PUTCHAR
    Самый простой механизм ввода заключается в чтении по одному символу

    «Язык С» Б.В. Керниган, Д.М. Ричи
    151
    за раз из “стандартного ввода”, обычно с терминала пользователя, с помощью функции GETCHAR. Функция GETCHAR() при каждом к ней обращении возвращает следующий вводимый символ. В большинстве сред, которые поддерживают язык “с”, терминал может быть заменен некоторым файлом с помощью обозначения < : если некоторая программа PROG использует функцию GETCHAR то командная строка
    PROGприведет к тому, что PROG будет читать из файла INFILE, а не с терминала.
    Переключение ввода делается таким образом, что сама программа PROG не замечает изменения; в частности строка”OTHERPROG \! PROG
    прогоняет две программы, OTHERPROG и PROG, и организует так, что стандартным вводом для PROG служит стандартный вывод OTHERPROG.
    Функция GETCHAR возвращает значение EOF, когда она попадает на конец файла, какой бы ввод она при этом не считывала. Стандартная библиотека полагает символическую константу EOF равной -1 (посредством
    #DEFINE в файле STDIO.H), но проверки следует писать в терминах EOF, а не -1, чтобы избежать зависимости от конкретного значения.
    Вывод можно осуществлять с помощью функции PUTCHAR(C),
    помещающей символ ‘с’ в “стандартный ввод”, который по умолчанию является терминалом. Вывод можно направить в некоторый файл с помощью обозначения > : если PROG использует PUTCHAR, то командная строка
    PROG>OUTFILE
    приведет к записи стандартного вывода в файл OUTFILE, а не на терминал. На системе UNIX можно также использовать поточный механизм. Строка
    PROG \! ANOTHERPROG
    помещает стандартный вывод PROG в стандартный ввод
    ANOTHERPROG. И опять PROG не будет осведомлена об изменении направления.
    Вывод, осуществляемый функцией PRINTF, также поступает в стандартный вывод, и обращения к PUTCHAR и PRINTF могут пе- ремежаться.
    Поразительное количество программ читает только из одного входного потока и пишет только в один выходной поток; для таких программ ввод и вывод с помощью функций GETCHAR, PUTCHAR и PRINTF может оказаться

    152
    «Язык С» Б.В. Керниган, Д.М. Ричи
    вполне адекватным и для начала определенно достаточным. Это особенно справедливо тогда, когда имеется возможность указания файлов для ввода и вывода и поточный механизм для связи вывода одной программы с вводом другой. Рассмотрим, например, программу 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, ...)

    «Язык С» Б.В. Керниган, Д.М. Ричи
    153
    преобразует, определяет формат и печатает свои аргументы в стандартный вывод под управлением строки 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 определяется указанной

    154
    «Язык С» Б.В. Керниган, Д.М. Ричи
    точностью. Точность по умолчанию равна 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, и помещает результаты в остальные аргументы. Управляющий аргумент описывается ниже; другие аргументы, каждый из которых должен быть указателем, определяют, куда следует поместить соответствующим образом преобразованный ввод.
    Управляющая строка обычно содержит спецификации преобразования,
    которые используются для непосредственной интерпретации входных последовательностей. Управляющая строка может содержать:

    «Язык С» Б.В. Керниган, Д.М. Ричи
    155
    - пробелы, табуляции или символы новой строки (“символы пустых промежутков”), которые игнорируются.
    - Обычные символы (не %), которые предполагаются совпадающими со следующими отличными от символов пустых промежутков символами входного потока.
    - Спецификации преобразования, состоящие из символа %, нео- бязательного символа подавления присваивания *, необязательного числа,
    задающего максимальную ширину поля и символа преобразования.
    Спецификация преобразования управляет преобразованием следующего поля ввода. нормально результат помещается в переменную, которая указывается соответствующим аргументом. Если, однако , с помощью символа
    * указано подавление присваивания, то это поле ввода просто пропускается и никакого присваивания не производится. Поле ввода определяется как строка символов, которые отличны от символов простых промежутков; оно продолжается либо до следующего символа пустого промежутка, либо пока не будет исчерпана ширина поля, если она указана. Отсюда следует, что при поиске нужного ей ввода, функция SCANF будет пересекать границы строк,
    поскольку символ новой строки входит в число пустых промежутков.
    Символ преобразования определяет интерпретацию поля ввода; согласно требованиям основанной на вызове по значению семантики языка “с”
    соответствующий аргумент должен быть указателем. Допускаются следующие символы преобразования: D - на вводе ожидается десятичное целое; соответствующий аргумент должен быть указателем на целое.
    O - На вводе ожидается восьмеричное целое (с лидирующим нулем или без него); соответствующий аргумент должен быть указателем на целое.
    X - На вводе ожидается шестнадцатеричное целое (с лидирующими 0X
    или без них); соответствующий аргумент должен быть указателем на целое.
    H - На вводе ожидается целое типа SHORT; соответсвующий аргумент должен быть указателем на целое типа SHORT.
    C - Ожидается отдельный символ; соответствующий аргумент должен быть указателем на символы; следующий вводимый символ помещается в указанное место. Обычный пропуск символов пустых промежутков в этом случае подавляется; для чтения следующего символа, который не является символом пустого промежутка, пользуйтесь спецификацией преобразования %1S.
    S - Ожидается символьная строка; соответствующий аргумент должен быть указателем символов, который указывает на массив символов, который достаточно велик для принятия строки и добавляемого в конце символа \0.
    F - Ожидается число с плавающей точкой; соответствующий аргумент должен быть указателем на переменную типа FLOAT.
    Е - символ преобразования E является синонимом для F. Формат ввода переменной типа FLOAT включает необязательный знак, строку цифр,
    возможно содержащую десятичную точку и необязательное поле экспоненты,
    состоящее из буквы E, за которой следует целое, возможно имеющее знак.

    156
    «Язык С» Б.В. Керниган, Д.М. Ричи
    Перед символами преобразования 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 заканчивается либо тогда, когда она исчер- пывает свою управляющую строку, либо когда некоторый элемент ввода не

    «Язык С» Б.В. Керниган, Д.М. Ричи
    157
    совпадает с управляющей спецификацией. В качестве своего значения она воз- вращает число правильно совпадающих и присвоенных элементов ввода. Это число может быть использовано для определения количества найденных элемен- тов ввода. при выходе на конец файла возвращается EOF; подчеркнем, что это значение отлично от 0, что следующий вводимый символ не удовлетворяет первой спецификации в управляющей строке. При следующем обращении к SCANF
    поиск возобновляется непосредственно за последним введенным символом.
    Заключительное предостережение: аргументы функции SCANF должны быть указателями. Несомненно наиболее распространенная ошибка состоит в написании
    SCANF(“%D”, N);
    вместо
    SCANF(“%D”, &N);
    1   ...   11   12   13   14   15   16   17   18   ...   23


    написать администратору сайта