C++
Шрифт:
extern token_value get_token; // ... token_value get_token (* /* ... */ *)
Это обеспечивает то, что компилятор обнаружит любую нсогласованность в типах, указанных для имени. Например, если бы get_token была описана как возвращающая token_value, но при этом определена как возвращающая int, компиляция lex.c не прошла бы изза ошибки несоответствия типов.
Файл syn.c будет выглядеть примерно так:
// syn.c: синтаксический анализ и вычисление
#include «dc.h»
double prim (* /* ... */ *) double term (* /* ... */ *) double expr (* /* ... */ *)
Файл table.c будет выглядеть примерно так:
// table.c: таблица имен и просмотр
#include «dc.h»
extern char* strcmp(const char*, const char*); extern char* strcpy(char*, const char*); extern int strlen(const char*);
const TBLSZ = 23; name* table[TBLSZ];
name* look(char* p; int ins) (* /* ... */ *)
Заметьте,
И main.c, наконец, выглядит так:
// main.c: инициализация, главный цикл и обработка ошибок
#include «dc.h»
int no_of_errors;
double error(char* s) (* /* ... */ *)
extern int strlen(const char*);
main(int argc, char* argv[]) (* /* ... */ *)
Важный случай, когда размер заголовочных файлов станвится серьезной помехой. Набор заголовочных файлов и библиотеку можно использовать для расширения языка множеством общи специальноприкладных типов (см. Главы 5-8). В таких случаях не принято осуществлять чтение тысяч строк заголовоных файлов в начале каждой компиляции. Содержание этих файлов обычно «заморожено» и изменяется очень нечасто. Наиболее плезным может оказаться метод затравки компилятора содержанием этих заголовочных фалов. По сути, создается язык специального назначения со своим собственным компилятором. Никакого стадартного метода создания такого компилятора с затравкой не принято.
4.3.2 Множественные заголовочные файлы
Стиль разбиения программы с одним заголовочным файлом наиболее пригоден в тех случаях, когда программа невелика и ее части не предполагается использовать отдельно. Поэтому то, что невозможно установить, какие описания зачем помещены в заголовочный файл, несущественно. Помочь могут комментарии. Другой способ – сделать так, чтобы каждая часть программы имела свой заголовочный файл, в котором определяются предотавляемые этой частью средства. Тогда каждый .c файл имеет соответствующий .h файл, и каждый .c файл включает свой собтвенный (специфицирующий то, что в нем задается) .h файл и, возможно, некоторые другие .h файлы (специфицирующие то, что ему нужно).
Рассматривая организацию калькулятора, мы замечаем, что error используется почти каждой функцией программы, а сама использует только «stream.h». Это обычная для функции ошибок ситуация, поэтому error следует отделить от main:
// error.h: обработка ошибок
extern int no_errors;
extern double error(char* s);
// error.c
#include «stream.h» #include «error.h»
int no_of_errors;
double error(char* s) (* /* ... */ *)
При таком стиле использования заголовочных файлов .h файл и связанный с ним .c файл можно рассматривать как мдуль, в котором .h файл задает интерфейс, а .c файл задает реализацию. Таблица символов не зависит от остальной части калькулятора за исключением использования функции ошибок. Это можно сделать явным:
// table.h: описания таблицы имен
struct name (* char* string; name* next; double value; *);
extern name* look(char* p, int ins = 0); inline name* insert(char* s) (* return look(s,1); *)
// table.c:
определения таблицы имен#include «error.h» #include «string.h» #include «table.h»
const TBLSZ = 23; name* table[TBLSZ];
name* look(char* p; int ins) (* /* ... */ *)
Заметьте, что описания функций работы со строками теперь включаются из «string.h». Это исключает еще один возможный источник ошибок.
// lex.h: описания для ввода и лексического анализа
enum token_value (* NAME, NUMBER, END, PLUS='+', MINUS='-', MUL='*', DIV='/', PRINT=';', ASSIGN='=', LP='(', RP=')' *);
extern token_value curr_tok; extern double number_value; extern char name_string[256];
extern token_value get_token;
Этот интерфейс лексического анализатора достаточно бепорядочен. Недостаток в надлежащем типе лексемы обнаруживает себя в необходимости давать пользователю get_token фактческие лексические буферы number_value и name_string.
// lex.c: определения для ввода и лексического анализа
#include «stream.h» #include «ctype.h» #include «error.h» #include «lex.h»
token_value curr_tok; double number_value; char name_string[256];
token_value get_token (* /* ... */ *)
Интерфейс синтаксического анализатора совершенно прозрчен: // syn.c: описания для синтаксического анализа и вычисления
extern double expr; extern double term;
extern double prim;
// syn.c: определения для синтаксического анализа и // вычисления
#include «error.h» #include «lex.h» #include «syn.h»
double prim (* /* ... */ *) double term (* /* ... */ *) double expr (* /* ... */ *)
Главная программа, как всегда, тривиальна:
// main.c: главная программа
#include «stream.h» #include «error.h» #include «lex.h» #include «syn.h» #include «table.h» #include «string.h»
main(int argc, char* argv[]) (* /* ... */ *)
Сколько заголовочных файлов использовать в программе, зависит от многих факторов. Многие из этих факторов сильнее связаны с тем, как ваша система работает с заголовочными фалами, нежели с С++. Например, если в вашем редакторе нет средств, позволяющих одновременно видеть несколько файлов, использование большого числа файлов становится менее привлкательным. Аналогично, если открывание и чтение 10 файлов по 50 строк в каждом требует заметно больше времени, чем чтение одного файла в 500 строк, вы можете дважды подумать, прежде чем использовать в небольшом проекте стиль множественных зголовочных файлов. Слово предостережения: набор из десяти зголовочных файлов плюс стандартные заголовочные файлы обычно легче поддаются управлению. С другой стороны, если вы разбили описания в большой программе на логически минимальные по рамеру заголовочные файлы (помещая каждое описание структуры в свой отдельный файл и т.д.), у вас легко может получиться нразбериха из сотен файлов.
4.3.3 Сокрытие данных
Используя заголовочные файлы пользователь может опредлять явный интерфейс, чтобы обеспечить согласованное исползование типов в программе. С другой стороны, пользователь может обойти интерфейс, задаваемый заголовочным файлом, вводя в .c файлы описания extern.
Заметьте, что такой стиль компоновки не рекомендуется:
// file1.c: // «extern» не используется int a = 7; const c = 8; void f(long) (* /* ... */ *)
// file2.c: // «extern» в .c файле extern int a; extern const c; extern f(int); int g (* return f(a+c); *)
Поскольку описания extern в file2.c не включаются вместе с определениями в файле file1.c, компилятор не может проверить согласованность этой программы. Следовательно, если только загрузчик не окажется гораздо сообразительнее среднго, две ошибки в этой программе останутся, и их придется икать программисту.
Пользователь может защитить файл от такой недисциплинрованной компоновки, описав имена, которые не предназначены для общего пользования, как static, чтобы их областью видмости был файл, и они были скрыты от остальных частей прораммы. Например: