Керниган и Ритчи - Язык программирования Си (793773), страница 36
Текст из файла (страница 36)
Одна из обычных ситуаций, встречающаяся в задачах обработки таблиц символов длякомпиляторов, — это объединение групп однобитовых флажков. Форматы некоторых данных могут от насвообще не зависеть и диктоваться, например, интерфейсами с аппаратурой внешних устройств; здесь такжевозникает потребность адресоваться к частям слова.Вообразим себе фрагмент компилятора, который заведует таблицей символов. Каждый идентификаторпрограммы имеет некоторую связанную с ним информацию: например, представляет ли он собой ключевоеслово и, если это переменная, к какому классу принадлежит: внешняя и/или статическая и т.
д. Самыйкомпактный способ кодирования такой информации — расположить однобитовые флажки в одном словетипа char или int.Один из распространенных приемов работы с битами основан на определении набора "масок",соответствующих позициям этих битов, как, например, в#define KEYWORD 01 /* ключевое слово */#define EXTERNAL 02 /* внешний */#define STATIC 04 /* статический */или вenum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };Числа должны быть степенями двойки.
Тогда доступ к битам становится делом "побитовых операций",описанных в главе 2 (сдвиг, маскирование, взятие дополнения).Некоторые виды записи выражений встречаются довольно часто. Так,flags |= EXTERNAL | STATIC;устанавливает 1 в соответствующих битах переменной flags,flags &= ~(EXTERNAL | STATIC);обнуляет их, аif ((flags & (EXTERNAL | STATIC)) ==0) ...оценивает условие как истинное, если оба бита нулевые.Хотя научиться писать такого рода выражения не составляет труда, вместо побитовых логических операцийможно пользоваться предоставляемым Си другим способом прямого определения и доступа к полям внутрислова.
Битовое поле (или для краткости просто поле) — это некоторое множество битов, лежащих рядомвнутри одной, зависящей от реализации единицы памяти, которую мы будем называть "словом". Синтаксисопределения полей и доступа к ним базируется на синтаксисе структур. Например, строки #define,фигурировавшие выше при задании таблицы символов, можно заменить на определение трех полей:struct {unsigned int is_keyword : 1;unsigned int is_extern : 1;unsigned int is_static : 1;} flags;Эта запись определяет переменную flags, которая содержит три однобитовых поля. Число, следующее задвоеточием, задает ширину поля. Поля объявлены как unsigned int, чтобы они воспринимались какбеззнаковые величины.На отдельные поля ссылаются так же, как и на элементы обычных структур: flags.is_keyword,flags.is_extern и т.д.
Поля "ведут себя" как малые целые и могут участвовать в арифметическихвыражениях точно так же, как и другие целые. Таким образом, предыдущие примеры можно написать болееестественно:flags.is_extern = flags.is_static = 1;устанавливает 1 в соответствующие биты;flags.is_extern = flags.is_static = 0;их обнуляет, аif (flags.is_extern == 0 && flags.is_static == 0)проверяет их.Почти все технические детали, касающиеся полей, в частности, возможность поля перейти границу слова,зависят от реализации.
Поля могут не иметь имени; с помощью безымянного поля (задаваемого толькодвоеточием и шириной) организуется пропуск нужного количества разрядов. Особая ширина, равная нулю,используется, когда требуется выйти на границу следующего слова.На одних машинах поля размещаются слева направо, на других — справа налево. Это значит, что при всейполезности работы с ними, если формат данных, с которыми мы имеем дело, дан нам свыше, то необходимосамым тщательным образом исследовать порядок расположения полей; программы, зависящие от такогорода вещей, не переносимы. Поля можно определять только с типом int, а для того чтобы обеспечитьпереносимость, надо явно указывать signed или unsigned.
Они не могут быть массивами и не имеютадресов, и, следовательно, оператор & к ним не применим.7. Ввод и выводВозможности для ввода и вывода не являются частью самого языка Си, поэтому мы подробно и нерассматривали их до сих пор. Между тем реальные программы взаимодействуют со своим окружениемгораздо более сложным способом, чем те, которые были затронуты ранее. В этой главе мы опишемстандартную библиотеку, содержащую набор функций, обеспечивающих ввод-вывод, работу со строками,управление памятью, стандартные математические функции и разного рода сервисные Си-программы.
Ноособое внимание уделим вводу-выводу.Библиотечные функции ввода-вывода точно определяются стандартом ANSI, так что они совместимы налюбых системах, где поддерживается Си. Программы, которые в своем взаимодействии с системнымокружением не выходят за рамки возможностей стандартной библиотеки, можно без изменений переноситьс одной машины на другую.Свойства библиотечных функций специфицированы в более чем дюжине заголовочных файлов; вам ужевстречались некоторые из них, в том числе <stdio.h>, <string.h> и <ctype.h>. Мы не рассматриваемздесь всю библиотеку, так как нас больше интересует написание Си-программ, чем использованиебиблиотечных функций. Стандартная библиотека подробно описана в приложении В.7.1.
Стандартный ввод-выводКак уже говорилось в главе 1, библиотечные функции реализуют простую модель текстового ввода-вывода.Текстовый поток состоит из последовательности строк; каждая строка заканчивается символом новой строки.Если система в чем-то не следует принятой модели, библиотека сделает так, чтобы казалось, что эта модельудовлетворяется полностью. Например, пара символов — возврат-каретки и перевод-строки — при вводемогла бы быть преобразована в один символ новой строки, а при выводе выполнялось бы обратноепреобразование.Простейший механизм ввода — это чтение одного символа из стандартного ввода (обычно с клавиатуры)функцией getchar:int getchar(void)В качестве результата каждого своего вызова функция getchar возвращает следующий символ ввода или,если обнаружен конец файла, EOF.
Именованная константа EOF (аббревиатура от end of file — конец файла)определена в <stdio.h>. Обычно значение EOF равно -1, но, чтобы не зависеть от конкретного значенияэтой константы, обращаться к ней следует по имени (EOF).Во многих системах клавиатуру можно заменить файлом, перенаправив ввод с помощью значка <.
Так, еслипрограмма prog использует getchar, то командная строкаprog < infileпредпишет программе prog читать символы из infile, а не с клавиатуры. Переключение ввода делаетсятак, что сама программа prog не замечает подмены; в частности строка "<infile" не будет включена варгументы командной строки argv. Переключение ввода будет также незаметным, если ввод исходит отдругой программы и передается конвейерным образом. В некоторых системах командная строкаotherprog | progприведет к тому, что запустится две программы, otherprog и prog, и стандартный вывод otherprog поступитна стандартный ввод prog.Функцияint putchar(int)используется для вывода: putchar(c) отправляет символ c в стандартный вывод, под которым поумолчанию подразумевается экран. Функция putchar в качестве результата возвращает посланный символили, в случае ошибки, EOF.
To же и в отношении вывода: с помощью записи вида > имя-файла вывод можноперенаправить в файл. Например, если prog использует для вывода функцию putchar, тоprog > outfileбудет направлять стандартный вывод не на экран, а в outfile. А командная строкаprog | anotherprogсоединит стандартный вывод программы prog со стандартным вводом программы anotherprog.Вывод, осуществляемый функцией printf, также отправляется в стандартный выходной поток. Вызовыputchar и printf могут как угодно чередоваться, при этом вывод будет формироваться в тойпоследовательности, в которой происходили вызовы этих функций.Любой исходный Си-файл, использующий хотя бы одну функцию библиотеки ввода-вывода, долженсодержать в себе строку#include <stdio.h>причем она должна быть расположена до первого обращения к вводу-выводу.
Если имя заголовочного файлазаключено в угловые скобки < и >, это значит, что поиск заголовочного файла ведется в стандартном месте(например, в системе UNIX это обычно директорий /usr/include).Многие программы читают только из одного входного потока и пишут только в один выходной поток.
Дляорганизации ввода-вывода таким программам вполне хватит функций getchar, putchar и printf, а дляначального обучения уж точно достаточно ознакомления с этими функциями. В частности, перечисленныхфункций достаточно, когда требуется вывод одной программы соединить с вводом следующей. В качествепримера рассмотрим программу lower, переводящую свой ввод на нижний регистр:«include <stdio.h>«include <ctype.h>main() /* lower: переводит ввод на нижний регистр */{int с;while ((с = getchar()) != EOF)putchar(tolower(c));return 0;}Функция tolower определена в <ctype.h>.
Она переводит буквы верхнего регистра в буквы нижнегорегистра, а остальные символы возвращает без изменений. Как мы уже упоминали, "функции" вродеgetchar и putchar из библиотеки <stdio.h> и функция tolower из библиотеки <ctype.h> частореализуются в виде макросов, чтобы исключить накладные расходы от вызова функции на каждый отдельныйсимвол. В параграфе 8.5 мы покажем, как это делается.
Независимо от того, как на той или иной машинереализованы функции библиотеки <ctype.h>, использующие их программы могут ничего не знать окодировке символов.Упражнение 7.1. Напишите программу, осуществляющую перевод ввода с верхнего регистра на нижний или снижнего на верхний в зависимости от имени, по которому она вызывается и текст которого находится вargv[0].7.2. Форматный вывод (printf)Функция printf переводит внутренние значения в текст.int printf(char *format, argr arg2, ...)В предыдущих главах мы использовали printf неформально.














