CHAP6&7 (1018807)
Текст из файла
Глава
6
Препроцессор
Многие свойства языка С++ делают препроцессор С менее важным, чем он был по традиции. Тем не менее, препроцессор иногда нужен даже в программе на С++ и, естественно, остается неотъемлемой частью программирования на С. Правила в этой главе книги посвящены правильному использованию препроцессора.
Я должен сказать, что многие из тех макросов, которые мне пришлось видеть, имеют довольно сомнительные основы с точки зрения удобочитаемости и сопровождения. Я часто предпочитаю не пользоваться макросом, если эквивалентная функция сделает ту же самую работу в более удобочитаемой манере, и никогда не хочу использовать макрос, вызывающий побочный эффект (будет рассмотрено ниже). В С++ я никогда не пользуюсь макросами с параметрами, используя вместо них встроенные (inline) функции или шаблоны, которые расширяются компилятором до встроенных функций. Макрос с параметрами даже в языке С должен быть методом, используемым лишь в крайнем случае. В макросах трудно обнаруживать ошибки (так как они не выполняется в пошаговом режиме), часто тяжело читать и, в лучшем случае, сложно сопровождать. Используйте их лишь тогда, когда скорость выполнения действительно является критерием, подтверждаемым фактическим тестированием кода с их использованием. Таким образом, эта глава книги содержит правила для тех случаев, где препроцессор является единственным решением проблемы.
79. Все из одного .h файла должно быть использовано в по меньшей мере двух .c файлах.
Это правило говорит само за себя - не загромождайте область глобальных имен идентификаторами, которые не используются глобально. Если идентификатор не используется вне текущего файла, то он должен объявляться лишь в области действия текущего файла. Если этот не используемый совместно идентификатор является глобальной переменной или функцией, то он должен быть объявлен статическим.
Заметьте, что статические функции находят применение даже в С++. Имеется тенденция помещать все основные функции, используемые любым из обработчиков сообщений, в собственно определение класса. Иногда локальная статическая функция в файле .cpp делает эту работу так же хорошо, и нет нужды загромождать прототипом этой функции определение класса.
80. Используйте вложенные директивы #include.
Хотя большинство из правил в этой главе говорят вам, как избежать использования препроцессора, механизм включения файлов директивой #include является обязательной функцией препроцессора как в С, так и в С++. Тем не менее, даже здесь существуют проблемы.
Это на самом деле плохая идея - требовать, чтобы кто-нибудь включал файл, способный включать в себя следующий. Я всегда располагаю директивы #include без определенного порядка или забываю вставить одну из них. Следовательно, заголовочный файл должен всегда включать те файлы, которые определяют то, что используется в текущем заголовочном файле. Вследствие того, что могут возникнуть проблемы, если компилятор прочитает какой-нибудь .h файл более одного раза, вы должны предпринять шаги для предотвращения многократной обработки одного и того же файла. Помещайте строки типа:
#ifndef FILENAME_H_
#define FILENAME_H_
в начале каждого заголовочного файла, и вставляйте соответственно:
#endif // FILENAME_H_
в конце. Так как константа FILENAME_H_ будет уже определена к моменту второй попытки препроцессора обработать этот файл, то его содержание при втором проходе будет проигнорировано.
81. Вы должны быть всегда способны заменить макрос функцией.
Это вариант для макросов правила "не нужно неожиданностей (без сюрпризов)". Что-то, похожее внешне на функцию, должно действовать подобно функции, даже если это на самом деле макрос. (По этой причине я иногда предпочитаю записывать имя макроса заглавными буквами не полностью, если его поведение сильно напоминает функцию. Хотя я всегда использую все заглавные, если у макроса есть побочные эффекты). При этом возникает насколько вопросов.
Во-первых, макрос не должен использовать переменные, не передаваемые в качестве аргументов. Вот наихудший из возможных способов в таких обстоятельствах:
Следующий код находится в заголовочном файле:
#define end() while (*p) \
++p
а этот - в файле .c:
char *f( char *str )
{
char *p = str;
end();
// ...
return p;
}
Здесь для сопровождающего программиста имеется несколько неприятных сюрпризов. Во-первых, переменная p явно не используется, поэтому появляется искушение стереть ее, разрушая этим код. Аналогично, программа разрушится, если имя p будет заменено другим. Наконец, будет большой неожиданностью то, что вызов end(), который выглядит внешне как вызов обычной функции, будет модифицировать p.
Первая попытка урегулирования этой проблемы может выглядеть следующим образом. В заголовочном файле:
#define end(p) while (*p) \
++p
и в файле .c:
char *f( char *str )
{
end(str);
// ...
return str;
}
Но теперь макрос все еще необъяснимо модифицирует str, а нормальная функция С не может работать таким образом. (Функция С++ может, но не должна. Я объясню почему в той главе книги, которая посвящена С++). Для модификации строки str в функции вы должны передать в нее ее адрес, поэтому то же самое должно быть применимо к макросу. Вот третий (наконец-то правильный) вариант, в котором макрос end() попросту заменен функцией с таким же именем. В заголовочном файле:
#define end(p) while (*(*p)) \
++(*p)
и в файле .c:
char *f( char *str )
{
end(&str);
// ...
return str;
}
Вместо end(&str) будет подставлено:
while (*(*&p))
++(*&p)
и *&p - это то же самое, что и p, так как знаки * и & отменяют друг друга - поэтому макрос в результате делает следующее:
while (*(p))
++(p)
Вторая проблема с макросом в роли функции возникает, если вы желаете выполнить в макросе больше, чем одно действие. Рассмотрим такой макрос:
#define две_вещи() a();b()
if ( x )
две_вещи();
else
нечто_другое();
который будет расширен следующим образом (тут я переформатировал, чтобы сделать происходящее неприятно очевидным):
if ( x )
a();
b();
else
нечто_другое();
Вы получаете сообщение об ошибке "у else отсутствует предшествующий оператор if". Вы не можете решить эту проблему, используя лишь фигурные скобки. Переопределение макроса следующим образом:
#define две_вещи() { a(); b(); }
вызовет такое расширение:
if ( x )
{
a();
b();
}
;
else
нечто_другое();
Эта вызывающая беспокойство точка с запятой - та, что следует после две_вещи() в вызове макроса. Помните, что точка с запятой сама по себе является законным оператором в С. Она ничего не делает, но она законна. Вследствие этого else пытается связаться с этой точкой с запятой, и вы получаете то же самое "у else отсутствует предшествующий оператор if".
Не нужно говорить, что несмотря на то, что макрос выглядит подобно вызову функции, его вызов может не сопровождаться точкой с запятой. К счастью, для этой проблемы имеется два настоящих решения. Первое из них использует малоизвестный оператор "последовательного вычисления" (или запятую):
#define две_вещи() ( a(), b() )
Эта запятая - та, что разделяет подвыражения в инициализирующей или инкрементирующей частях оператора for. (Запятая, которая разделяет аргументы функции, не является оператором последовательного вычисления). Оператор последовательного вычисления выполняется слева направо и получает значение самого правого элемента в списке (в нашем случае значение, возвращаемое b()). Запись:
x = ( a(),b() );
означает просто:
a();
x = b();
Если вам все равно, какое значение имеет макрос, то вы можете сделать нечто-подобное, используя знак плюс вместо запятой. (Выражение:
a()+b();
в отдельной строке совершенно законно для С, где не требуется, чтобы результат сложения был где-нибудь сохранен). Тем не менее при знаке плюс порядок выполнения не гарантируется; функция b() может быть вызвана первой. Не путайте приоритеты операций с порядком выполнения. Приоритет просто сообщает компилятору, где неявно размещаются круглые скобки. Порядок выполнения вступает в силу после того, как все круглые скобки будут расставлены по местам. Невозможно добавить дополнительные скобки к ((a())+(b())). Оператор последовательного вычисления гарантированно выполняется слева направо, поэтому в нем такой проблемы нет.
Я должен также отметить, что оператор последовательного вычисления слишком причудлив, чтобы появляться в нормальном коде. Я использую его лишь в макросах, сопровождая все обширными комментариями, объясняющими, что происходит. Никогда не используйте запятую там, где должна быть точка к запятой. (Я видел людей, которые делали это, чтобы не использовать фигурные скобки, но это страшно даже пересказывать).
Второе решение использует фигурные скобки, но с одной уловкой:
#define две_вещи() \
do \
{ \
a(); \
b(); \
} while ( 0 )
if ( x )
две_вещи();
else
нечто_другое();
что расширяется до:
if ( x )
do
{
a();
b();
} while ( 0 ) ; // <== точка с запятой связывается с оператором while ( 0 )
else
нечто_другое();
Вы можете также попробовать так:
#define две_вещи() \
if ( 1 ) \
{ \
a(); \
b(); \
} else
но я думаю, что комбинация do с while (0) незначительно лучше.
Так как вы можете объявить переменную после любой открытой фигурной скобки, то у вас появляется возможность использования предшествующего метода для определения макроса, имеющего по существу свои собственные локальные переменные. Рассмотрим следующий макрос, осуществляющий обмен значениями между двумя целочисленными переменными:
#define swap_int(x,y) \
do \
{ \
int x##y; \
x##y = x; \
x = y; \
y = x##y \
} \
while (0)
Сочетание ## является оператором конкатенации в стандарте ANSI С. Я использую его здесь для обеспечения того, чтобы имя временной переменной не конфликтовало с любым из имен исходных переменных. При данном вызове:
swap(laurel, hardy);
препроцессор вначале подставляет аргументы обычным порядком (заменяя x на laurel, а y на hardy), давая в результате следующее имя временной переменной:
int laurel##hardy;
Затем препроцессор удаляет знаки решетки, давая
int laurelhardy;
Дополнительная польза от возможности замены макросов функциями заключается в отладке. Иногда вы хотите, чтобы что-то было подобно макросу по эффективности, но вам нужно при отладке установить в нем точку прерывания. Используйте для этого в С++ встроенные функции, а в С используйте следующие:
#define _AT_LEFT(this) ((this)->left_child_is_thread ? NULL : (this)->left)
#ifdef DEBUG
static tnode *at_left(tnode *this) { return _AT_LEFT(this); }
#else
# define at_left(this) _AT_LEFT(this)
#endif
Я закончу это правило упоминанием о еще двух причудливых конструкциях, которые иногда полезны в макросе, прежде всего потому, что они помогают макросу расширяться в один оператор, чтобы избежать проблем с фигурными скобками, рассмотренных ранее. Положим, вы хотите, чтобы макрос по возможности расширялся в единственное выражение. Оператор последовательного вычисления достигает этого за счет читаемости, и наряду с ним я никогда не использую формы, показанные в таблице 1, по той же причине - их слишком трудно читать. (Коли на то пошло, я также не использую их в макросах, если я могу достичь желаемого каким-то другим способом).
Таблица 1. Макросы, эквивалентные условным операторам.
| Этот код: | Делает то же самое, что и: |
| ( a && f() ) | if ( a ) f(); |
| ( b || f() ) | if ( !b ) f(); |
| ( z ? f() : g()) | if ( z ) f(); else g(); |
Первые два выражения опираются на тот факт, что вычисления в выражении с использованием операций && и || гарантированно осуществляются слева направо и прекращаются сразу, как только устанавливается истина или ложь. Возьмем для примера выражение a && f(). Если a ложно, то тогда не важно, что возвращает f(), так как все выражение ложно, если любой из его операндов значит ложь. Следовательно, компилятор никогда не вызовет f(), если a ложно, но он должен вызвать f(), если a истинно. То же самое применимо и к b, но здесь f() вызывается, если b, напротив, ложно.
81.1. Операция ?: не то же самое, что и оператор if/else.
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.















