4.3.1 Единственный заголовочный файл.

   


Назад  |  Вперёд

Проще всего разбить программу на несколько файлов следующим образом: поместить определения всех функций и данных в некоторое число входных файлов, а все типы, необходимые для связи между ними, описать в единственном заголовочном файле. Все входные файлы будут включать заголовочный файл. Программу калькулятора можно разбить на четыре входных файла .c: lex.c, syn.c, table.c и main.c. Заголовочный файл dc.h будет содержать описания каждого имени, которое используется более чем в одном .c файле:

           // dc.h: общее описание для калькулятора

           #include <iostream.h>

           enum token_value {
                NAME,      NUMBER,     END,
                PLUS='+',  MINUS='-',  MUL='*', DIV='/',
                PRINT=';', ASSIGN='=', LP='(',  RP=')'
           };

           extern int no_of_errors;
           extern double error(const char* s);
           extern token_value get_token();
           extern token_value curr_tok;
           extern double number_value;
           extern char name_string[256];
           extern double expr();
           extern double term();
           extern double prim();

           struct name {
               char* string;
               name* next;
               double value;
           };

           extern name* look(const char* p, int ins = 0);
           inline name* insert(const char* s) { return look(s,1); }

Если не приводить сами операторы, lex.c должен иметь такой вид:

           // lex.c: ввод и лексический анализ

           #include "dc.h"
           #include <ctype.h>

           token_value curr_tok;
           double number_value;
           char name_string[256];

           token_value get_token() { /* ... */ }

Используя составленный заголовочный файл, мы добьемся, что описание каждого объекта, введенного пользователем, обязательно окажется в том файле, где этот объект определяется. Действительно, при обработке файла lex.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 int TBLSZ = 23;
           name* table[TBLSZ];

           name* look(char* p, int ins) { /* ... */ }

Отметим, что раз строковые функции описаны в самом файле table.c, транслятор не может проверить согласованность этих описаний по типам. Всегда лучше включить соответствующий заголовочный файл, чем описывать в файле .c некоторое имя как extern. Это может привести к включению "слишком многого", но такое включение нестрашно, поскольку не влияет на скорость выполнения программы и ее размер, а программисту позволяет сэкономить время. Допустим, функция strlen() снова описывается в приведенном ниже файле main.c. Это только лишний ввод символов и потенциальный источник ошибок, т.к. транслятор не сможет обнаружить расхождения в двух описаниях strlen() (впрочем, это может сделать редактор связей). Такой проблемы не возникло бы, если бы в файле dc.h содержались все описания extern, как первоначально и предполагалось. Подобная небрежность присутствует в нашем примере, поскольку она типична для программ на С. Она очень естественна для программиста, но часто приводит к ошибкам и таким программам, которые трудно сопровождать. Итак, предупреждение сделано!

Наконец, приведем файл main.c:

           // main.c: инициализация, основной цикл, обработка ошибок

           #include "dc.h"

           double error(char* s) { /* ... */ }

           extern int strlen(const char*);

           int main(int argc, char* argv[]) { /* ... */ }

В одном важном случае заголовочные файлы вызывают большое неудобство. С помощью серии заголовочных файлов и стандартной библиотеки расширяют возможности языка, вводя множество типов (как общих, так и рассчитанных на конкретные приложения; см. главы 5-9). В таком случае текст каждой единицы трансляции может начинаться тысячами строк заголовочных файлов. Содержимое заголовочных файлов библиотеки, как правило, стабильно и меняется редко. Здесь очень пригодился бы претранслятор, который обрабатывает его. По сути, нужен язык специального назначения со своим транслятором. Но устоявшихся методов построения такого претранслятора пока нет.


Назад  |  Вперёд


OtDiatlovaOU
К началу книги

Оформление и дизайн книги OtDiatlovaOU.
Вся книга, архив.