Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 Mб
Скачать

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).       В таком случае текст каждой единицы трансляции может начинаться       тысячами строк заголовочных файлов. Содержимое заголовочных       файлов библиотеки, как правило, стабильно и меняется редко. Здесь       очень пригодился бы претранслятор, который обрабатывает его. По сути,       нужен язык специального назначения со своим транслятором. Но устоявшихся       методов построения такого претранслятора пока нет.