Керниган и Ритчи - Язык программирования Си (793773), страница 24
Текст из файла (страница 24)
Такого рода программыособенно удобны для обработки рекурсивно определяемых структур данных вроде деревьев; с хорошимпримером на эту тему вы познакомитесь в параграфе 6.5.Упражнение 4.12. Примените идеи, которые мы использовали в рrintd, длянаписания рекурсивной версиифункции itoa; иначе говоря, преобразуйте целое число в строку цифр с помощью рекурсивной программы.Упражнение 4.13. Напишите рекурсивную версию функции reverse(s), переставляющую элементы строкив ту же строку в обратном порядке.4.11.
Препроцессор языка СиНекоторые возможности языка Си обеспечиваются препроцессором, который работает на первом шагекомпиляции. Наиболее часто используются две возможности: #include, вставляющая содержимоенекоторого файла во время компиляции, и #define, заменяющая одни текстовые последовательности надругие. В этом параграфе обсуждаются условная компиляция и макроподстановка с аргументами.4.11.1. Включение файлаСредство #include позволяет, в частности, легко манипулировать наборами #define и объявлений.
Любаястрока вида#include "имя-файла"или#include <имя-файла>заменяется содержимым файла с именем имя-файла. Если имя-файла заключено в двойные кавычки, то, какправило, файл ищется среди исходных файлов программы; если такового не оказалось или имя-файлазаключено в угловые скобки < и >, то поиск осуществляется по определенным в реализации правилам.Включаемый файл сам может содержать в себе строки #include.Часто исходные файлы начинаются с нескольких строк #include, ссылающихся на общие инструкции#define и объявления extern или прототипы нужных библиотечных функций из заголовочных файловвроде <stdio.h>. (Строго говоря, эти включения не обязательно являются файлами; технические деталитого, как осуществляется доступ к заголовкам, зависят от конкретной реализации.)Средство #include — хороший способ собрать вместе объявления большой программы.
Он гарантирует, чтовсе исходные файлы будут пользоваться одними и теми же определениями и объявлениями переменных,благодаря чему предотвращаются особенно неприятные ошибки. Естественно, при внесении изменений вовключаемый файл все зависимые от него файлы должны перекомпилироваться.4.11.2. МакроподстановкаОпределение макроподстановки имеет вид:#define имя замещающий-текстМакроподстановка используется для простейшей замены: во всех местах, где встречается лексема имя,вместо нее будет помещен замещающий-текст.
Имена в #define задаются по тем же правилам, что иимена обычных переменных. Замещающий текст может быть произвольным. Обычно замещающий текстзавершает строку, в которой расположено слово #define, но в длинных определениях его можнопродолжить на следующих строках, поставив в конце каждой продолжаемой строки обратную наклоннуючерту \. Область видимости имени, определенного в #define, простирается от данного определения доконца файла. В определении макроподстановки могут фигурировать более ранние #define-определения.Подстановка осуществляется только для тех имен, которые расположены вне текстов, заключенных вкавычки.
Например, если YES определено с помощью #define, то никакой подстановки в printf("YES") или в YESMAN выполнено не будет.Любое имя можно определить с произвольным замещающим текстом. Например,#define forever for(;;) /* бесконечный цикл */определяет новое слово forever для бесконечного цикла.Макроподстановку можно определить с аргументами, вследствие чего замещающий текст будетварьироваться в зависимости от задаваемых параметров. Например, определим max следующим образом:#define max(A, В) ((А) > (В) ? (А) : (В))Хотя обращения к max выглядят как обычные обращения к функции, они будут вызывать только текстовуюзамену.
Каждый формальный параметр (в данном случае А и В) будет заменяться соответствующим емуаргументом. Так, строках = max(p+q, r+s);будет заменена на строкух = ((p+q) > (r+s) ? (p+q) : (r+s));Поскольку аргументы допускают любой вид замены, указанное определение max подходит для данныхлюбого типа, так что не нужно писать разные max для данных разных типов, как это было бы в случае заданияс помощью функций.Если вы внимательно проанализируете работу max, то обнаружите некоторые подводные камни. Выражениявычисляются дважды, и если они вызывают побочный эффект (из-за инкрементных операций или функцийввода-вывода), это может привести к нежелательным последствиям. Например,max(i++, j++) /* НЕВЕРНО */вызовет увеличение i и j дважды.
Кроме того, следует позаботиться о скобках, чтобы обеспечить нужныйпорядок вычислений. Задумайтесь, что случится, если при определении#define square(x) x*x /* НЕВЕРНО */вызвать square(z+1).Тем не менее, макросредства имеют свои достоинства. Практическим примером их использования являетсячастое применение getchar и putchar из <stdio.h>, реализованных с помощью макросов, чтобыизбежать расходов времени от вызова функции на каждый обрабатываемый символ. Функции в <ctype.h>обычно также реализуются с помощью макросов.Действие #define можно отменить с помощью #undef:#undef getcharint getchar(void) {…}Как правило, это делается, чтобы заменить макроопределение настоящей функцией с тем же именем.Имена формальных параметров не заменяются, если встречаются в заключенных в кавычки строках.
Однако,если в замещающем тексте перед формальным параметром стоит знак #, этот параметр будет заменен нааргумент, заключенный в кавычки. Это может сочетаться с конкатенацией (склеиванием) строк, например,чтобы создать макрос отладочного вывода:#define dprint(expr) printf(#expr " = %g\n", expr)Обращение кdprint(x/y);развернется вprintf("x/y" " = %g\n", x/y);а в результате конкатенации двух соседних строк получимprintf("x/y = %g\n", x/y);Внутри фактического аргумента каждый знак " заменяется на \", а каждая \ на \\, так что результатподстановки приводит к правильной символьной константе.Оператор ## позволяет в макрорасширениях конкатенировать аргументы.
Если в замещающем текстепараметр соседствует с ##, то он заменяется соответствующим ему аргументом, а оператор ## и окружающиеего символы-разделители выбрасываются. Например, в макроопределении paste конкатенируются двааргумента#define paste(front, back) front ## backтак что paste(name, 1) сгенерирует имя name1.Правила вложенных использований оператора ## не определены; другие подробности, относящиеся к ##,можно найти в приложении А.Упражнение 4.14. Определите swap(t, x, y) в виде макроса, который осуществляет обмен значениямиуказанного типа t между аргументами x и y.
(Примените блочную структуру.)4.11.3. Условная компиляцияСамим ходом препроцессирования можно управлять с помощью условных инструкций. Они представляютсобой средство для выборочного включения того или иного текста программы в зависимости от значенияусловия, вычисляемого во время компиляции.Вычисляется константное целое выражение, заданное в строке #if. Это выражение не должно содержать ниодного оператора sizeof или приведения к типу и ни одной enum-константы. Если оно имеет ненулевоезначение, то будут включены все последующие строки вплоть до #endif, или #elif, или #else.(Инструкция препроцессора #elif похожа на else if.) Выражение defined(имя) в #if есть 1, если имябыло определено, и 0 в противном случае.Например, чтобы застраховаться от повторного включения заголовочного файла hdr.h, его можно оформитьследующим образом:#if !defined(HDR)#define HDR/* здесь содержимое hdr.h */#endifПри первом включении файла hdr.h будет определено имя HDR, а при последующих включенияхпрепроцессор обнаружит, что имя HDR уже определено, и перескочит сразу на #endif.
Этот прием можетоказаться полезным, когда нужно избежать многократного включения одного и того же файла. Если импользоваться систематически, то в результате каждый заголовочный файл будет сам включать заголовочныефайлы, от которых он зависит, освободив от этого занятия пользователя.Вот пример цепочки проверок имени SYSTEM, позволяющей выбрать нужный файл для включения:#if SYSTEM == SYSV#define HDR "sysv.h"#elif SYSTEM == BSD#define HDR "bsd.h"#elif SYSTEM == MSDOS#define HDR "msdos.h"#else#define HDR "default.h"#endif#include HDRИнструкции #ifdef и #ifndef специально предназначены для проверки того, определено или нетзаданное в них имя. И следовательно, первый пример, приведенный выше для иллюстрации #if, можнозаписать и в таком виде:#ifndef HDR#define HDR/* здесь содержимое hdr.h */#endif5.
Указатели и массивыУказатель — это переменная, содержащая адрес переменной. Указатели широко применяются в Си — отчастипотому, что в некоторых случаях без них просто не обойтись, а отчасти потому, что программы с ними обычнокороче и эффективнее. Указатели и массивы тесно связаны друг с другом; в данной главе мы рассмотрим этузависимость и покажем, как ею пользоваться.Наряду с goto указатели когда-то были объявлены лучшим средством для написания малопонятныхпрограмм. Так оно и есть, если ими пользоваться бездумно. Ведь очень легко получить указатель,указывающий на что-нибудь совсем нежелательное. При соблюдении же определенной дисциплины спомощью указателей можно достичь ясности и простоты. Мы попытаемся убедить вас в этом.Изменения, внесенные стандартом ANSI, связаны в основном с формулированием точных правил, какработать с указателями. Стандарт узаконил накопленный положительный опыт программистов и удачныенововведения разработчиков компиляторов.














