п. 5.5. Реализация методов класса




 

Хотя функции доступа SetKey() и GetKey() объявлены (строки 7и 8) в Car, они еще не определены. SetKey() и GetKey() обеспечивает открытый интерфейс к закрытым переменным-членам класса. Для каждой функции доступа, наряду с другими методами, объявленными в классе, должна существовать реализация. Реализация – это описание действия функции. Для определения функции-члена необходимо связать имя класса, частью которого является функция-член, с именем функции. Это достигается путем написания имени функции вслед за именем класса с двумя двоеточиями. Два двоеточия называются оператором расширением области видимости.

 

Итак, реализация (определение) функции имеет следующий формат:

 

типВозвращаемогоЗначения имяКласса:: имяФункции (списокПараметров)

{

// тело функции

}

 

Для рассматриваемого примера реализация функций-членов SetKey() и GetKey() имеет вид:

void Car:: SetKey (unsigned short Num)

{

// закрытой переменной-члену присваивается значение,

// передаваемое извне через параметр Num

Key = Num;

}

unsigned short Car:: GetKey()

{

// возвращение значения закрытой

// переменной Key

return Key;

}

 

Окончательно имеем:

 

#include <iostream>

using namespace std;

 

class Car

{

private:

unsigned short Key;

 

public:

void SetKey (unsigned short Num);

unsigned short GetKey();

};

 

void Car:: SetKey (unsigned short Num)

{

Key = Num;

}

unsigned short Car:: GetKey()

{

return Key;

}

 

int main ()

{

Car myZaparozec;

unsigned short Pass;

 

myZaparozec.SetKey (65000);

Pass =myZaparozec.GetKey ();

 

cout<<"The key of my automobile is "<<Pass<<endl;

return 0;

}

 

Результат:

The key of my automobile is 65000

 

п. 5.6. Конструкторы и деструкторы

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

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

Для конструктора всегда выполняются следующие правила:

· конструктор никогда не вызывается явно, а по своему назначению используется в основном для инициализации полей данных при создании объекта;

· конструктор автоматически вызывается при создании объекта класса;

· имя конструктора совпадает с именем класса;

· от других собственных функций класса конструктор отличается тем, что не имеет возвращаемого значения (даже типа void) – результатом выполнения конструктора является объект;

· конструктор может иметь параметры, а следовательно, в классе можно определить несколько конструкторов;

· конструктор объекта можно не объявлять, компилятор сам снабдит такой объект стандартными конструктором (и деструктором), стандартный конструктор не принимает аргументов и (вместе со стандартным деструктором) ничего не делает.

· важно помнить, что при наличии конструктора, созданного программистом, компилятор не будет создавать свой, но если созданный конструктор получает в виде параметра какое-либо значение, а при инициализации объекта оно не передается, то произойдет ошибка компиляции.

Объявив конструктор необходимо объявить и деструктор. Деструкторы удаляют из памяти отработавшие объекты и освобождают выделенную для них память. Правила деструктора:

· деструктору присваивается имя класса с символом тильды «~» впереди;

· деструкторы не имеет параметров (не принимают аргументов) и не возвращают значений;

· должен быть открытым (public) методом;

· никогда не вызывается явно, вызывается автоматически для каждого объекта при выходе из области видимости объекта;

· если деструктор не определен в классе явно, то он создается компилятором автоматически, по умолчанию освобождая память, занимаемую объектом.

 

Рассмотрим небольшой пример класса с конструктором.

#include <iostream>

using namespace std;

 

class myclass

{

int a;

public:

myclass (); // объявление конструктора

void Show ();

};

 

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

myclass:: myclass()

{

cout << "In designer \n";

a = 10;

}

// реализация метода

void myclass:: Show ()

{

cout << a<<"\n";

}

 

int main ()

{

myclass object; // объявление объекта

object. Show();

return 0;

}

 

В этом примере значение a инициализируется конструктором myclass. Конструктор вызывается тогда, когда создается объект object. Объект, в свою очередь, создается при выполнении инструкции объявления объекта, что влечет за собой выполнение записанных в конструкторе действий.

 

 

П. 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_h[20] еще не была определена. Следующая директива #define AnnounceClassCar_h определяет эту лексему, после чего подключается код файла.

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

 

 


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

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

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

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

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

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

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



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-15 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: