Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Введение в классы С++.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
283.65 Кб
Скачать

П. 5.8. Структура программы с классами. Встраиваемая реализация

Каждая функция (метод)-член, объявленная в классе, должна иметь определение. Определение функции, называемое реализацией методов класса, сообщает компилятору, как она работает.

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

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

Д ействительно, поскольку класс фактически определяет решение самостоятельной подзадачи в программе, из соображений получить возможность повторного использования класса, а так же согласно традициям языка С++ обычно принято объявления классов (как новых, так и стандартных) помещать в файл заголовка (header file), имя которого как правило совпадает с именем файла программы и имеет в нашем случае расширение .h 17. Затем он используется для создания библиотеки классов и может быть подключен директивой препроцессора #include "имя_класса.h" в любом файле С++, где необходимо использовать объекты этого класса.

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

Ниже приведен пример организации программы, структура которой изображена на рисунке 3.

// Файл Car.h. Содержит объявление класса Car

class Car

{

public:

Car(unsigned int initialYear);

~Car();

unsigned int GetYear( );

void SetYear (unsigned int Year);

void Start( );

private:

unsigned int itsYear;

};

//Файл Car.сpp Реализация методов класса и его использование

#include <iostream>

#include "Car.h"

using namespace std;

Car::Car(unsigned int initialYear)

{

itsYear=initialYear;

}

Car::~Car ( ) { }

int unsigned Car::GetYear ( )

{

return itsYear;

}

void Car::SetYear (unsigned int Year)

{

itsYear=Year;

}

void Car::Start ()

{

cout<<"Forward!!!\n";

}

int main( )

{

unsigned int Old;

Car myZaparozec(19);

Old=myZaparozec.GetYear ( );

cout<<"The year back to my machine was "<<Old<<" years"<<endl;

myZaparozec.SetYear (20);

Old=myZaparozec.GetYear ( );

cout<<"Now to my machine "<<Old<<" years"<<endl;

myZaparozec.Start ();

return 0;

}

Допускается полное определение функции-метода класса непосредственно в классе, тогда она будет рассматриваться как встраиваемая (inline). Такой подход оправдан в том случае, если определение функции-члена достаточно краткое. Схема структуры программы в этом случае изображена на рисунке 4.

Программа, соответствующая такой организации выглядит следующим образом:

// Объявление и реализация класса в файле Car.h

// Используется встроенная реализация функций

using namespace std;

class Car

{

public:

Car(unsigned int initialYear);

~Car( );

unsigned int GetYear( ) const { return itsYear;}

void Car::SetYear (unsigned int Year) { itsYear=Year;}

void Start( ) const { cout<<"Forward!!!\n";}

private:

unsigned int itsYear;

};

// реализация конструктора и деструктора

Car::Car(unsigned int initialYear)

{

itsYear=initialYear;

}

Car::~Car ( ) { }

// Файл Car.hpp. Использование класса

#include <iostream>

#include "Car.h"

using namespace std;

int main( )

{

unsigned int Old;

Car myZaparozec(19);

Old=myZaparozec.GetYear ( );

cout<<"The year back to my machine was "<<Old<<" years"<<endl;

myZaparozec.SetYear (20);

Old=myZaparozec.GetYear ( );

cout<<"Now to my machine "<<Old<<" years"<<endl;

myZaparozec.Start ();

return 0;

}

Обратите внимание на синтаксис определения встроенной функции GetYear( ). Ее тело начинается сразу же после объявления метода класса, причем после закрывающей скобки нет точки с запятой.

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

Т огда имеет место следующая структура программы.

Функция main( ) тоже находится в отдельном файле .cpp, и все файлы .cpp будут скомпилированы в файлы .obj, которые затем компонуются в единый исполняемый файл программы. Действительно, исходная программа, подготовленная на С++ в виде текстового файла, проходит три этапа обработки: препроцессорное преобразование текста; компиляция; компоновка (редактирование связей или сборка).

Тогда для нашего примера рабочее пространство workspace имеет компоненты, показанные на рисунке 6.

Рис. 6

// AnnounceClassCar.h. Заголовочный файл класса Car

// Функции определены в классе DefClassCar

// Предотвращение многократного включения заголовочного файла

#ifndef AnnounceClassCar_h

#define AnnounceClassCar_h

class Car

{

public:

Car(unsigned int initialYear);

~Car();

unsigned int GetYear( );

void SetYear (unsigned int Year);

void Start( );

private:

unsigned int itsYear;

};

#endif

// DefClassCar.cpp

// исходный файл определения функций класса Car

#include <iostream>

#include "AnnounceClassCar.h"

using namespace std;

Car::Car(unsigned int initialYear)

{

itsYear=initialYear;

}

Car::~Car ()

{

}

int unsigned Car::GetYear ( )

{

return itsYear;

}

void Car::SetYear (unsigned int Year)

{

itsYear=Year;

}

void Car::Start ()

{

cout<<"Forward!!!\n";

}

// UseClassCar.cpp

// файл использования объекта класса Car

#include <iostream>

#include "AnnounceClassCar.h"

using namespace std;

int main( )

{

unsigned int Old;

Car myZaparozec(19);

Old=myZaparozec.GetYear ( );

cout<<"The year back to my machine was "<<Old<<" years"<<endl;

myZaparozec.SetYear (20);

Old=myZaparozec.GetYear ( );

cout<<"Now to my machine "<<Old<<" years"<<endl;

myZaparozec.Start ();

return 0;

}

Обратите внимание, что объявление класса (файл AnnounceClassCar.h) заключено в следующие директивы препроцессора:

#ifndef AnnounceClassCar_h

#define AnnounceClassCar_h (*)

// тело файла

#endif

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

Приведенные директивы препроцессора предотвращают включение кода между #ifndef…#define, если определено имя AnnounceClassCar_h. Это означает, что если слово19 AnnounceClassCar_h уже существует, весь код определения, вплоть до директивы #endif, необходимо пропустить. А все содержимое файла как раз и располагается между директивами #ifndef…#endif.

Директива #ifndef (if not def – если не определено) возвращает true когда соответствующее слово не было определено и false, если определено. Директиву #ifndef перед завершением текущего блока (до закрывающей фигурной скобки) следует закрыть директивой #endif.

Когда программа подключает файл AnnounceClassCar_h в первый раз, препроцессор читает строку #ifndef AnnounceClassCar_h, и результат проверки оказывается истиной, так как до сих лексема AnnounceClassCar_h20 еще не была определена. Следующая директива #define AnnounceClassCar_h определяет эту лексему, после чего подключается код файла.

При попытке подключить файл AnnounceClassCar_h во второй раз препроцессор, прочитав строку #ifndef AnnounceClassCar_h, возвратит false, поскольку лексема AnnounceClassCar_h уже была определена. Управление программой переходит к следующей директиве #endif. Таким образом, все содержимое файла будет пропущено и класс дважды объявлен не будет, что существенно сэкономит время отладки программы.

14 Данные-члены и данные элементы, функции-члены и функции-элементы рассматриваются как синонимы.

15 Пусть имеется класс Автомобиль, который может быть представлен как объединение колес, кузова, двигателя, дверей и т.д. Автомобиль способен ехать, разгоняться, тормозить, останавливаться и т.д. Тогда колеса, кузов, двигатель могут рассматриваться как переменные-члены класса, а к функциям-членам можно отнести действия класса разгоняться Start( ), тормозить Break ( ) и т.д.

16 Вариант приведенной программы не является исполняемым, а всего лишь демонстрирует объявление класса.

17 Возможны расширения *.hpp, *.hp.

18 Такое может произойти, когда имеют дело с объявлениями производных классов и создании метода, использующего например, два объекта, производных одного класса.

19 Строка символов (лексема) – имеется в виду не строковая переменная. Лексема может быть применена вместо любого другого набора символов или констант.

20 Реальное имя лексемы не важно, но традиционно используют имя файла, отделенное от расширения символом подчеркивания.