Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 27 28 29 [ 30 ] 31 32 33 ... 120


глобальных классов и перечислений должны быть уникальными во всей программе и иметь единственное определение. Поэтому, если есть два даже идентичных определения одного класса, это -все равно ошибка:

fileLc:

struct S { int a; char b; }; extern void f(S*);

file2.c:

struct S { int a; char b; }; void f(S* p) { /* ... */ }

Но будьте осторожны: опознать идентичность двух описаний класса не в состоянии большинство систем программирования С++. Такое дублирование может вызвать довольно тонкие ошибки (ведь классы в разных файлах будут считаться различными).

Глобальные функции-подстановки подлежат внутреннему связыванию, и то же по умолчанию справедливо для констант. Синонимы типов, т.е. имена typedef, локальны в своем файле, поэтому описания в двух данных ниже файлах не противоречат друг другу:

fileLc: typedef int T; const int a = 7;

inline T f(int i) { return i+a; }

file2.c: typedef void T; const int a = 8;

inline T f(double d) { cout<<d; } Константа может получить внешнее связывание только с помощью явного описания:

file3.c: extern const int a; const int a = 77;

file4.c: extern const int a; void g() { cout<<a; }

В этом примере g() напечатает 77.

4.3 Заголовочные файлы

Типы одного объекта или функции должны быть согласованы во всех их описаниях. Должен быть согласован по типам и входной текст, обрабатываемый транслятором, и связываемые части программы. Есть простой, хотя и несовершенный, способ добиться согласованности описаний в различных файлах. Это: включить во входные файлы, содержащие операторы и определения данных, заголовочные файлы, которые содержат интерфейсную информацию.

Средством включения текстов служит макрокоманда #include, которая позволяет собрать в один файл (единицу трансляции) несколько исходных файлов программы. Команда

#include включаемый- файл

заменяет строку, в которой она была задана, на содержимое файла включаемый-файл. Естественно, это содержимое должно быть текстом на С++, поскольку его будет читать транслятор. Как правило, операция включения реализуется отдельной программой, называемой препроцессором С++. Она вызывается системой программирования перед собственно трансляцией для обработки таких команд во входном тексте. Возможно и другое решение: часть транслятора, непосредственно работающая с входным текстом, обрабатывает команды включения файлов по мере их появления в тексте. В той системе программирования, в которой работает автор, чтобы увидеть результат команд включения файлов, нужно задать команду:



Бьерн Страуструп. Язык программирования С++

CC -E file.c

Эта команда для обработки файла file.c запускает препроцессор (и только!), подобно тому, как команда CC без флага -E запускает сам транслятор.

Для включения файлов из стандартных каталогов (обычно каталоги с именем INCLUDE) надо вместо кавычек использовать угловые скобки < и >. Например:

#include <stream.h> включение из стандартного каталога #include myheader.h включение из текущего каталога

Включение из стандартных каталогов имеет то преимущество, что имена этих каталогов никак не связаны с конкретной программой (обычно вначале включаемые файлы ищутся в каталоге /usr/include/CC, а затем в /usr/include). К сожалению, в этой команде пробелы существенны:

#include < stream.h> <stream.h> не будет найден

Было бы нелепо, если бы каждый раз перед включением файла требовалась его перетрансляция. Обычно включаемые файлы содержат только описания, а не операторы и определения, требующие существенной трансляторной обработки. Кроме того, система программирования может предварительно оттранслировать заголовочные файлы, если, конечно, она настолько развита, что способна сделать это, не изменяя семантики программы.

Укажем, что может содержать заголовочный файл:

Определения типов struct point { int x, y; };

Шаблоны типов template<class T>

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 <signal.h>

Макроопределения #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 <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 может иметь такой вид:



1 ... 27 28 29 [ 30 ] 31 32 33 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика