Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Технологии разработки объектно-ориентированных программ на язык C++. Основы объектно-ориентированного программирования на алгоритмическом языке C++

.pdf
Скачиваний:
7
Добавлен:
12.11.2023
Размер:
876.09 Кб
Скачать

Например, если в конструкторе используется операция new для выделения памяти, то деструктор должен обратиться к delete для ее освобождения. Как и конструктор, деструктор имеет специальное имя, совпадающее с названием класса, перед которым поставлена тильда (~).

Подобно конструктору, деструктор не имеет ни возвращаемого значения, ни объявляемого типа. Однако, в отличие от конструктора, деструктор не должен иметь аргументов и, следовательно, его невозможно перегрузить. Таким образом, объявление деструктора для класса Car выглядит следующим образом:

~Car();

А определение следующим образом:

Car::~Car() {…}

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

16.8.Указатель this

Вкаждую функцию, которая является членом класса, включая конструкторы и деструкторы, передается скрытый параметр this, в котором хранится константный указатель на вызвавший эту функцию объект [14]. Специфическим свойством this является то, что он указывает на вызывающий объект. Если метод нуждается в получении ссылки на вызвавший объект в целом, он может использовать выражение *this. Таким образом, this можно использовать для обращения ко всем элементам класса: как к полям, так и к методам, что может понадобиться для устранения неких неоднозначностей. Например, если имя формального аргумента совпадает с именем какого-либо элемента класса.

Пример. Добавление конструктора и деструктора к классу Car

//Объявление класса, заголовочный файл car2.h

#ifndef CAR_H_ #define CAR_H_

11

class Car

{

private:

int ls = 0; // Мощность автомобиля int weight = 0; // Вес автомобиля

public:

Car(); // Конструктор без параметров

Car(int ls, int weight = 1500); // Конструктор с параметрами

~Car(); // Деструктор

void startCar(); // Объявление метода startCar };

#endif

//Определение методов класса, файл исходного кода car2.cpp #include "stdafx.h"

#include <iostream>

//Определение метода void start_car();

void Car::start_car() {

std::cout <<"Завели автомобиль!\n";

}

//Определение конструктора без параметров

Car::Car() { ls = 150;

weight = 1500;

}

//Определение конструктора с параметрами

Car::Car(int ls, int weight) { /*

Пример устранения неоднозначности и обращения к членам объекта с помощью указателя this

*/

this >ls = ls; // Обращение к приватному элементу int ls this >weight = weight; // Обращение к приватному элементу weight

}

//Определение деструктора

Car::~Car() {

std::cout <<"Автомобиль больше не используется!\n";

}

12

16.9.Понятие полиморфизма

Вязыках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов.

Один из разработчиков алгоритмических языков C и C++ Бьерн Страуструп определил полиморфизм как «один интерфейс – много реализаций».

Полиморфизм в ООП – возможность объектов с одинаковой спецификацией иметь различную реализацию. Например, реализация класса может быть изменена в процессе наследования.

13

Глава 17. РАБОТА С КЛАССАМИ

17.1. Перегрузка операций

Перегрузка операций – это пример использования полиморфизма в С++.

Напомним, что перегрузка функций (функциональный полиморфизм) – это возможность определять несколько функций с одинаковыми именами, но различной сигнатурой (списками аргумен-

тов) [14].

Кроме того, С++ позволяет перегружать не только функции, но и базовые операции. Цель такой перегрузки – позволить использовать одну и ту же базовую операцию, выполняя ее с данными разных типов.

Язык С++ позволяет распространить перегрузку операций на пользовательские типы, разрешая, скажем, применять символ «+» для сложения двух объектов.

Для перегрузки операций используется специальная форма функции, называемая функцией операции. Функция операции имеет следующую форму, в которой «op» – это символ перегружаемой операции, а тип – тип возвращаемого функцией значения:

тип operator op(список аргументов);

Например, operator+(…) перегружает операцию сложения, а operator*() – операцию умножения. Операция op должна быть допустимой операцией в С++, а не произвольным символом. Например, объявить функцию операции operator@() невозможно, так как в С++ не существует операции @.

Допустим, что создан некий класс с именем Box, в котором определена функция-член класса operator+() для перегрузки операции сложения таким образом, что эта функция выполняет сложение весов двух коробок и возвращает результат операции. Пусть a – общий вес коробок, box1 и box2 – объекты класса Box. Операция сложения будет выглядеть следующим образом:

14

int a = box1 + box2;

Компилятор, распознав операцию как относящуюся к классу Box, заменит ее вызовом соответствующей функции-операции:

int a = box1.operator+(box2);

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

Использование перегрузки операции имеет некоторые ограничения [14]:

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

содним операндом, например %x.

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

Невозможно определить новые символы операций.

Для перегрузки операций присваивания (=), вызова функции, индексации ([]), доступа к членам класса (–>) через указатель используются только функции-члены.

Операции доступа к полям (.), вложенным именам, полям по указателю, sizeof и некоторые другие не перегружаются.

17.2.Друзья классов

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

15

Если функция объявлена другом класса, то она имеет те же привилегии доступа, что и у функции – члена класса.

Для чего используются друзья? Например, часто перегрузка бинарной операции (т.е. операции с двумя аргументами) приводит к потребности в друзьях.

Пусть A и B объекты некого класса, который имеет перегрузку операции умножения на число. Помните, что левый операнд – это вызывающий объект:

A = B * 2.4;

(17.1)

Этот операнд транслируется компилятором в следующий вызов функции-члена:

A = B.operator*(2.4);

Тогда что будет означать приведенный ниже оператор?

A = 2.4 * B; /* Ошибка, не соответствует функции

члену, поскольку

левым операндом

(17.2)

стало число 2.4,

а не объект B*/

 

Концептуально выражение (17.2) должно быть эквивалентно выражению (17.1), но выражение (17.2) не соответствует функциичлену, поскольку число 2.4 не является объектом класса.

Решением здесь может послужить функция, не являющаяся членом, т.е. operator*(2.4, B), но такая функция не имеет непосредственного доступа к закрытым членам класса.

В таком случае в С++ используется специальная категория функций, не являющихся членами класса, которые называются друзьями и имеют доступ к закрытым членам класса.

Первый шаг в создании дружественной функции предусматривает объявление прототипа этой функции в классе. Ключевое слово friend размещается в начале оператора объявления функции:

friend Car operator+(int m, const Car &t);

Несмотря на то, что функция operator+() присутствует в объявлении класса, она не является функцией – членом класса. Ввиду этого она не вызывается через операцию членства – точку. При

16

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

Второй шаг состоит в создании определения функции. Поскольку она не является функцией – членом класса Car, то добавлять квалификатор Car:: не нужно. Ключевое слово friend не используется в определении.

Таким образом, дружественная функция класса – это функция, не являющаяся членом, которая имеет те же права доступа, что и функция – член класса.

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

17.3. Функции преобразования

При выполнении программы часто необходимо выполнить явные и неявные преобразования величин из одного типа в другой. Для преобразования объектов класса в основные типы данных служит специальная форма функции – члена класса, которая называется функцией преобразования.

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

Например:

Car one(5); //Создан объект one типа Car со значением, равным 5 int* obj1 = one; // Тип объекта one из Car в int* преобразован

//в неявном виде, поскольку тип объекта one

//не указывается

int* obj2 = (int*) one;// Тип объекта one из Car в int*

//преобразован в явном виде,

//поскольку тип объекта one не

//указывается

17

Компилятор, отметив, что правая часть второй строки программного кода имеет тип Car, а левая int*, смотрит, определена ли соответствующая описанию функция преобразования. Если он не находит ее, то генерирует сообщение об ошибке, говорящее о невозможности присваивания значения типа Car переменной типа int*.

Сама функция преобразования создается с помощью operator имяТипа();

При этом:

функция преобразования должна быть методом класса;

в функции преобразованиянеуказывается возвращаемый тип;

функция преобразования не имеет аргументов.

Например, функция преобразования типа заданного объекта в тип int имеет следующий вид:

operator int();

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

explicit operator double();// double – это тип, для

//преобразования

//к которому типа объекта будет

//вызвана данная функция

//преобразования

Пример. Разработка класса Car, содержащего перегрузку операций, дружественную функцию, функцию преобразования

// Объявление класса, заголовочный файл car3.h #ifndef CAR_H_

18

#define CAR_H_ class Car

{

private:

int ls = 0; // Мощность автомобиля int weight = 0; // Вес автомобиля

public:

Car(); // Конструктор без параметров

/*

Конструктор с одним обязательным аргументом определяет преобразование типа аргумента в тип класса

*/

Car(int ls, int weight = 1500); ~Car(); // Деструктор

Car operator+(int m); // Перегрузка операции сложения operator int(); // Функция преобразования типа Car в int

//Дружественная функция, перегрузка операции сложения friend Car operator+(int m, Car & t);

//Объявление метода void start_car()

void start_car() const; };

#endif

//Определение методов класса, файл исходного кода car3.cpp

#include "stdafx.h" #include <iostream>

//Определение метода void start_car();

void Car::start_car() const { std::cout<<"Завели автомобиль!\n";

}

//Определение конструктора без параметров

Car::Car() { ls = 150;

weight = 1500;

}

//Определение конструктора с параметрами

Car::Car(int ls, int weight) { this >ls = ls;

this >weight = weight;

19

}

// Определение деструктора

Car::~Car() {

std::cout <<"Автомобиль больше не используется!\n";

}

/*

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

*/

Car Car::operator+(int m) { Car N = this >ls + m; return N;

}

//Функция преобразования типа Car в тип int, возвращает

//мощность автомобиля

Car::operator int() { return ls; } /*

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

второй

операнд является объектом Car

*/

friend Car operator+(int m, Car &t) { Car N = t.ls + m;

return N;

}

17.4. Статические члены класса

Статические члены класса – это члены, объявление которых указано с ключевым словом static. Такие члены принадлежат классу, а не его экземплярам-объектам. Исходя из этого статический член класса обладает особым свойством: программа создает только одну копию статической переменной класса, независимо от количества создаваемых объектов [14].

20

Соседние файлы в папке книги