описаниях. Должен быть согласован по типам и входной текст,
обрабатываемый транслятором, и связываемые части программы. Есть
описаний в различных файлах. Это: включить во входные файлы,
которые содержат интерфейсную информацию.
несколько исходных файлов программы. Команда
#include "включаемый-файл"
заменяет строку, в которой она была задана, на содержимое файла
включаемый-файл. Естественно, это содержимое должно быть текстом
С++. Она вызывается системой программирования перед собственно
трансляцией для обработки таких команд во входном тексте. Возможно
появления в тексте. В той системе программирования, в которой
транслятор.
затем в /usr/include). К сожалению, в этой команде пробелы существенны:
не будет найден
Было бы нелепо, если бы каждый раз перед включением файла
требовалась его перетрансляция. Обычно включаемые файлы содержат
только описания, а не операторы и определения, требующие существенной
трансляторной обработки. Кроме того, система программирования
может предварительно оттранслировать заголовочные
файлы, если, конечно, она настолько развита, что способна сделать
это, не изменяя семантики программы.
Укажем, что может содержать заголовочный файл:
Определения типов struct point { int x, y; };
Шаблоны типов template
class V { /* ... */ }
Описания функций extern int strlen(const char*);
Определения inline char get() { return *p++; }
функций-подстановок
Описания данных extern int a;
Определения констант const float pi = 3.141593;
Перечисления enum bool { false, true };
Описания имен class Matrix;
Команды включения файлов #include
Макроопределения #define Case break;case
Комментарии /* проверка на конец файла */
Перечисление того, что стоит помещать в заголовочный файл, не является
требованием языка, это просто совет по разумному использованию включения
файлов. С другой стороны, в заголовочном файле никогда не должно быть:
Определений обычных функций char get() { return *p++; }
Определений данных int a;
Определений составных const tb[i] = { /* ... */ };
констант
По традиции заголовочные файлы имеют расширение .h, а файлы,
содержащие определения функций или данных, расширение .c. Иногда
их называют "h-файлы" или "с-файлы" соответственно. Используют
и другие расширения для этих файлов: .C, cxx, .cpp и
.cc. Принятое расширение вы найдете в своем справочном руководстве.
Макросредства описываются в $$4.7. Отметим только, что в С++ они
используются не столь широко, как в С, поскольку С++ имеет определенные
возможности в самом языке: определения констант (const),
функций-подстановок (inline), дающие возможность более простой
операции вызова, и шаблонов типа, позволяющие порождать семейство
типов и функций ($$8).
Совет помещать в заголовочный файл определения только простых,
но не составных, констант объясняется вполне прагматической причиной.
Просто большинство трансляторов не настолько разумно, чтобы
предотвратить создание ненужных копий составной константы. Вообще
говоря, более простой вариант всегда является более общим, а значит
транслятор должен учитывать его в первую очередь, чтобы создать
хорошую программу.
4.3.1 Единственный заголовочный файл
Проще всего разбить программу на несколько файлов следующим
образом: поместить определения всех функций и данных в некоторое
число входных файлов, а все типы, необходимые для связи между
ними, описать в единственном заголовочном файле. Все входные
файлы будут включать заголовочный файл. Программу
калькулятора можно разбить на четыре входных файла .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 может иметь такой вид:
// 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).
В таком случае текст каждой единицы трансляции может начинаться
тысячами строк заголовочных файлов. Содержимое заголовочных
файлов библиотеки, как правило, стабильно и меняется редко. Здесь
очень пригодился бы претранслятор, который обрабатывает его. По сути,
нужен язык специального назначения со своим транслятором. Но устоявшихся
методов построения такого претранслятора пока нет.
Достарыңызбен бөлісу: