Бьерн Страуструп.
Язык программирования С++
101
всех
функций и данных в некоторое число входных файлов, а все типы, необходимые для связи между
ними, описать в единственном заголовочном файле. Все входные файлы будут включать заголовочный
файл. Программу калькулятора можно разбить на четыре входных файла .c: lex.c, syn.c, table.c и main.c.
Заголовочный файл dc.h будет содержать описания каждого имени, которое используется более чем в
одном .c файле:
// dc.h: общее описание для калькулятора
#include
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
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 может иметь такой вид:
Бьерн Страуструп.
Язык программирования С++
102
// 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). В таком случае текст
каждой единицы трансляции может начинаться тысячами строк заголовочных файлов. Содержимое
заголовочных файлов библиотеки, как правило, стабильно и меняется редко. Здесь очень пригодился
бы претранслятор, который обрабатывает его. По сути, нужен язык специального назначения со своим
транслятором. Но устоявшихся
методов построения такого претранслятора пока нет.
Достарыңызбен бөлісу: