Продолжая развитие нашего проекта со стеком, создадим третий модуль, в котором объект класса TStack будет передаваться в функцию как параметр. Назовем этот файл function.срр (листинг 13.3).
Листинг 13.3. Содержимое файла function.срр
// пока стек не пустой
// выводим число с вершины
// удаляем элемент из стека
void function (TStack &t) { while (!t.empty())
{ cout << "(double *)t.top() << endl:
double *p = (double *)t.pop();
delete p;
}
}
Проблем пока нет, но они появляются при подключении файла к нашей главной программе. Выясняется, что мы должны учитывать порядок следования директив #i nclude, чтобы все работало. Правильный порядок такой:
#include <iostream> using namespace std: #include "TStack.cpp" #include "function.cpp"
Стандартный файл iostream должен быть указан перед файлом function.cpp, так как в функции f () выполняется вывод в стандартный поток. Ранее должен быть включен и файл со стеком, так как функция f () его получает в качестве параметра. Порядок следования файлов TStack.cpp и iostream в данном случае может быть произвольным, но только потому, что в нашем стеке никак не используется стандартный ввод-вывод.
Налицо зависимость файлов друг от друга во время компиляции — ситуация крайне неприятная: мало того, что каждый раз транслируется объединенный текст, так еще мы должны «вручную» отслеживать связи между различными модулями. Это достижимо только для очень небольших программ, состоящих не более чем из пары десятков модулей, а дальше начинаются проблемы.
На ум приходит естественное решение — добавить в модуль function.cpp три первые строки из модуля main.cpp:
#include <iostream> using namespace std; #include "TStack.cpp"
Однако при трансляции тут же выясняется, что класс TStack определен дважды1. По стандарту в программе разрешается иметь несколько определений класса, но в разных единицах трансляции (см. п. п. 3.2/5 в [1]). А у нас получается двойное определение в одной единице — в модуле main.cpp. Действительно, после постановки всех наших файлов (про системные файлы пока умолчим) объединенный модуль main.cpp содержит следующие директивы #include:
• #include <iostream> — включает системный файл iostream;
• #include "TStack.cpp" в модуле main.cpp — включает класс TStack;
• #include "function.cpp" в модуле main.cpp — включает файл function.cpp;
• #include "TStack.cpp" в модуле function.cpp — включает класс TStack.
Таким образом, класс TStack оказывается определенным дважды. Справиться с этой проблемой нам опять поможет препроцессор: мы должны определить во включаемом файле «стража» включения. Для этого используется директива #def ine и директивы условной трансляции препроцессора. Обычно это делается так:
// myfile.h
#ifndef MYFILE // страж определен?
#define MYFILE // если нет - определяем
// содержимое файла
#endif // конец #ifndef
Первая строка часто пишется немного по-другому:
#if !defined( MYFILE ) // страж определен?
Однако сути дела это не меняет.
Когда препроцессор обрабатывает первую директиву, имя MYFILE (страж) не определено:
Обратите внимание, что с системным файлом ошибок не возникает!
#include "myfile.h"
Поэтому содержимое файла включается в обработку. Если эта директива встречается еще раз, то страж уже определен и содержимое файла пропускается.
Именно так организованы стандартные файлы, и поэтому не было проблем со стандартным файлом iostream. Например, начало стандартного файла math.h в интегрированной среде Borland С++ Builder 6 выглядит так:
/* math.h
Definitions for the math floating point package. ,
*/ /*
* C/C++ Run Time Library - Version 11.0
* Copyright (c) 1987, 2002 by Borland Software Corporation
* All Rights Reserved. */
/* $Revision: 9.17.2.6 $ */
#ifndef MATH_H // проверка стража
#define MATH_H // определение стража
Как видите, определен страж с именем МАТН_Н. Практически не отличается от
этого файла соответствующий файл системы Visual C++.NET 2003 (пропущены некоторые строки, не относящиеся к делу):
/***
¦math.h - definitions and declarations for math library *
* Copyright (c) Microsoft Corporation. All rights reserved. *
¦Purpose:
* This file contains constant definitions and external subroutine
* declarations for the math subroutine library.
* [ANSI/System V] *
* [public] ¦
* * * * /
#ifndef _INC_MATH #define _INC_MATH
В этой системе определен страж с именем _INC_MATH.
По негласному соглашению имена, определяемые в директиве #def i пе, пишутся прописными буквами — это позволяет по одному виду имени определить, что оно «принадлежит» препроцессору и «работает» только на стадии компиляции.
Наш файл TStack.cpp должен быть таким, как показано в листинге 13.4.
Листинг 13.4. Файл TStack.cpp со стражем
#ifndef __STACK // страж определен?
#define _STACK // определение стража
class TStack {
// определение класса
}:
#endif /* _STACK */ // конец ifndef
Теперь все работает правильно. |