Анимация
JavaScript
|
Главная Библионтека Часть Препроцессор Многие свойства языка Си++ делают препроцессор Си менее важным, чем он был по традиции. Тем не менее, препроцессор иногда нужен даже в программе на Си++ и, естественно, остается неотъемлемой частью программирования на Си. Правила в этой главе книги посвящены правильному использованию препроцессора. Я должен сказать, что многие из тех макросов, которые мне пришлось видеть, имеют довольно сомнительные основания с точки зрения удобочитаемости и сопровождения. Я часто предпочитаю не пользоваться макросом, если эквивалентная функция сделает ту же самую работу в более удобочитаемой манере, и никогда не хочу использовать макрос, вызывающий побочный эффект (будет рассмотрено ниже). В Си++ я никогда не пользуюсь макросами с параметрами, используя вместо них встроенные (inline) функции или шаблоны, которые расширяются компилятором до встроенных функций. Макрос с параметрами даже в языке Си должен быть методом, используемым лишь в крайнем случае. В макросах трудно обнаруживать ошибки (так как они не выполняются в пошаговом режиме), их трудно читать и, в лучшем случае, сложно сопровождать. Используйте их лишь тогда, когда скорость выполнения действительно является критерием, подтверждаемым фактическим тестированием кода с их использованием. Таким образом, эта глава книги содержит правила для тех случаев, где препроцессор является единственным решением проблемы. 79. Все из одного .h файла должно быть использовано в, по меньшей мере, двух .с файлах Это правило говорит само за себя - не загромождайте область глобальных имен идентификаторами, которые не используются глобально. Если идентификатор не используется вне текущего файла, то он должен объявляться лишь в области действия текущего файла. Если этот не используемый совместно идентификатор является глобальной переменной или функцией, то он должен быть объявлен статическим. Заметьте, что статические функции находят применение даже в Си++. Имеется тенденция помещать все основные функции, используемые любым из обработчиков сообщений, в собственно определение класса. Иногда локальная статическая функция в файле .срр делает эту работу так же хорошо, и нет нужды загромождать прототипом этой функции определение класса. 80. Используйте вложенные директивы #include Хотя большинство из правил в этой главе говорят вам, как избежать использования препроцессора, механизм включения файлов директивой #include является обязательной функцией препроцессора как в Си, так и в Си++. Тем не менее, даже здесь существуют проблемы. Это на самом деле плохая идея - требовать, чтобы кто-нибудь включал файл, способный включать в себя следующий. Я всегда располагаю директивы #include без определенного порядка или забываю вставить одну из них. Следовательно, заголовочный файл должен всегда включать те файлы, которые определяют то, что используется в текущем заголовочном файле. Вследствие того, что могут возникнуть проблемы, если компилятор прочитает какой-нибудь .Ъ. файл более одного раза, вы должны предпринять шаги для предотвращения многократной обработки одного и того же файла. Помещайте строки типа: #ifndef FILENAME H #define FILENAME H в начале каждого заголовочного файла, и вставляйте соответственно: #endif FILENAME H в конце. Так как константа FILENAME H будет уже определена к моменту второй попытки препроцессора обработать этот файл, то его содержание при втором проходе будет проигнорировано. 81. Вы должны быть всегда способны заменить макрос функцией Это вариант для макросов правила "не нужно неожиданностей (без сюрпризов) ". Что-то, похожее внешне на функцию, должно действовать подобно функции, даже если это на самом деле макрос. (По этой причине я иногда предпочитаю записывать имя макроса заглавными буквами не полностью, если его поведение сильно напоминает функцию. Хотя я всегда использую все заглавные, если у макроса есть побочные эффекты). При этом возникает насколько вопросов. Во-первых, макрос не должен использовать переменные, не передаваемые в качестве аргументов. Вот наихудший из возможных способов в таких обстоятельствах: Следующий код находится в заголовочном файле: #define end() while(*p) \ ++p а этот - в файле .с: char *f( char *str ) char *p = str; end(); ... return p; Здесь для сопровождающего программиста имеется несколько неприятных сюрпризов. Во-первых, переменная p явно не используется, поэтому появляется искушение стереть ее, разрушая этим код. Аналогично, программа разрушится, если имя p будет заменено другим. Наконец, будет большой неожиданностью то, что вызов end() , который выглядит внешне как вызов обычной функции, будет модифицировать p. Первая попытка урегулирования этой проблемы может выглядеть следующим образом. В заголовочном файле: #define end(p) while(*p) \ + +p и в файле .с: char *f( char *str ) end(str); ... return str; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 [ 31 ] 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |