Язык Си - руководство для начинающих - M. УЭИТ
Шрифт:
Интервал:
Закладка:
Выбор макроопределения приводит к увеличению объема памяти, а выбор функции - к увеличению времени работы программы. Так что думайте, что выбрать! Макроопределение создает "строчный" код, т. е. вы получаете оператор в программе. Если макроопределение применить 20 раз, то в вашу программу вставится 20 строк кода. Если вы используете функцию 20 раз, у вас будет только одна копия операторов функции, поэтому получается меньший объем памяти. Однако управление программой следует передать туда, где находится функция, а затем вернуться в вызывающую программу, а на это потребуется больше времени, чем при работе со "строчными" кодами.
Преимущество макроопределений заключается в том, что при их использовании вам не нужно беспокоиться о типах переменных (макроопределения имеют дело с символьными строками, а не с фактическими значениями). Так наше макроопределение SQUARE(x) можно использовать одинаково хорошо с переменными типа int или float.
Обычно программисты используют макроопределения для простых действии, подобных следующим:
#define MAX(X,Y) ( (X) > (Y) ? (X) : (Y))
#define ABS(X) ( (X) < 0 ? -(X) : (X))
#define ISSIGN(X) ( (X) == '+' || (X) == '-' ? 1 : 0)
(Последнее макроопределение имеет значение 1 (истинно), если X является символом алгебраического знака.) Отметим следующие моменты:
1. В макроопределении нет пробелов, но они могут появиться в замещающей строке. Препроцессор "полагает", что макроопределение заканчивается на первом пробеле, поэтому все, что стоит после пробела, остается в замещающей строке.
РИС. 11.2. Ошибочный пробел и макроопределении.
2. Используйте круглые скобки для каждого аргумента и всего определения. Это является гарантией того, что элементы будут сгруппированы надлежащим образом в выражении, подобном forks = 2*MAX(guests + 3, last);
3. Для имен макрофункций следует использовать прописные буквы. Это соглашение не распространяется так широко, как соглашение об использовании прописных букв для макроконстант. Применение их предостережет вас от возможных побочных эффектов макроопределений.
Предположим, что вы разработали несколько макрофункций по своему усмотрению. Должны ли вы их переопределять каждый раз, когда пишете новую программу? Нет, если вы вспомните о директиве #include. Теперь рассмотрим ее.
ВКЛЮЧЕНИЕ ФАЙЛА: #include
Когда препроцессор "распознает" директиву #include, он ищет следующее за ней имя файла и включает его в текущий файл. Директива выдается в двух видах:
#include <stdio.h> имя файла в угловых скобках
#include "mystuff.h" имя файла в двойных кавычках
В операционной системе UNIX угловые скобки сообщают препроцессору, что файл следует искать в одном или нескольких стандартных системных каталогах. Кавычки говорят ему, что сначала нужно смотреть в вашем каталоге (или в каком-то другом, если вы определяете его именем файла), а затем искать в "стандартных" местах.
#include <stdio.h> ищет в системном каталоге
#include "hot.h" ищет в вашем текущем рабочем каталоге
#include "/usr/biif/p.h" ищет в каталоге /usr/biff
В типичной микропроцессорной системе эти две формы являются синонимами, и препроцессор ведет поиск на указанном диске.
#include "stdio.h" ищет на стандартном диске
#include <stdio.h> ищет на стандартном диске
#include "a : stdio.h" ищет на диске а
Зачем включают файлы? Потому что они несут нужную вам информацию. Файл stdio.h, например, обычно содержит определения EOF, getchar( ) и putchar( ). Две последние Функции определены как макрофункции.
По соглашению суффикс .h используется для "заголовочных" файлов, т. е. файлов с информацией, которая располагается в начале вашей программы. Заголовочные файлы обычно состоят из операторов препроцессора. Некоторые файлы, подобно stdio.h, включены в систему, но вы можете создать и свой собственный.
Заголовочные файлы:
Пример:
Предположим, например, что вам правится использовать булевы переменные, т. с. вместо того чтобы иметь 1 как "истину" и 0 как "ложь", хотели бы использовать слова TRUE и FALSE. Можно создать файл, названный, скажем, bool.h, который содержал бы эти определения:
/* файл bool.h */
#define BOOL int;
#define TRUE 1
#define FALSE 0
Вот пример программы, использующей этот заголовок:
/* считает пустые символы */
#include <stdio.h>
#include "bool.h"
main( )
{
int ch;
int count = 0;
BOOL whitesp( );
while((ch = getchar( )) != EOF)
if(whitesp(ch)) count++;
printf(" Имеется %d пустых символов. n", count);
}
BOOL whitesp(c)
char c;
if(c == ' ' || с == 'n' || с == 't')
return(TRUE);
else
return(FALSE);
}
Замечания по программе
1. Если две функции в этой программе ('main( )' и 'whitesp( )') следовало скомпилировать раздельно, то нужно было бы использовать директиву #include "bool.h" с каждой из них.
2. Выражение if(whitesp(ch)) аналогично if(whitesp(ch)) == TRUE, так как сама функция whitesp(ch) имеет значение TRUE или FALSE.
3. Мы не создали новый тип BOOL, так как BOOL уже имеет тип int. Цель наименования функции BOOL - напомнить пользователю, что функция используется для логических вычислений (в противоположность арифметическим).
4. Использование функции в предусматриваемых логических сравнениях может сделать программу яснее. Она может также сэкономить наш труд, если сравнение встречается в программе более одного раза.
5. Мы могли бы использовать макроопределение вместо функции для задания whitesp( ).
Многие программисты разрабатывают свои стандартные заголовочные файлы, чтобы использовать их в программах. Некоторые файлы могут создаваться для специальных целей, другие можно использовать почти в каждой программе. Так как включенные файлы могут содержать директивы #include, можно, если хотите, создать сжатый, хорошо огранизованный заголовочный файл. Рассмотрим этот пример:
/*заголовочный файл mystuff.h*/
#include
#include "bool.h"
#include "funct.h"
#define YES 1
#define NO 0
Во-первых, мы хотим напомнить вам, что препроцессор языка Си распознает комментарии, помеченные /* и */, поэтому мы можем включать комментарии в эти файлы.
Во-вторых, мы включили три файла. По-видимому, третий содержит некоторые макрофункции, которые используются часто.
В-третьих, мы определили YES как 1, тогда как в файле bool.h мы определили TRUE как 1. Но здесь нет конфликта, и мы можем использовать слова YES и TRUE в одной и той же программе. Каждое из них будет заменяться на 1. Может возникнуть конфликт, если мы добавим в файл строку
#define TRUE 2
Второе определение вытеснило бы первое, и некоторые препроцессоры предупреждали бы вас, что TRUE переопределено.
Директива #include не ограничивается заголовочными файлами. Если вы записали нужную функцию в файл sort.с, то можно использовать
#include "sort.с"
чтобы скомпилировать его совместно с вашей текущей программой.
Директивы #include и #define являются наиболее активно используемыми средствами препроцессора языка Си. Рассмотрим более сжато другие его директивы.
ДРУГИЕ ДИРЕКТИВЫ: #undef, #if, #ifdef, #ifndef, #else И #endif
Эти директивы обычно используются при программировании больших модулей. Они позволяют приостановить действие более ранних определений и создать файлы, каждый из которых можно компилировать по-разному.
Директива #undef отменяет самое последнее определение поименованного макроопределения.
#define BIG 3
#define HUGE 5
#undef BIG /* BIG теперь не определен */
#define HUGE 10 /* HUGE переопределен как 10 */
#undef HUGE /* HUGE снова равен 5*/