Линкфилд
Здесь может быть ваша реклама
|

 Глава 8. Классы С++

Глава 8. Классы С++

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

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

Элементы класса

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

Элементы данных

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

  • Они не могут быть объявлены как auto, extern или register.
  • Они могут быть перечислениями, битовыми полями, а также объектами ранее объявленных классов.
  • Элемент данных класса не может быть представителем самого класса.
  • Элемент данных класса может быть указателем или ссылкой на сам класс.

Элементы-функции

Функция-элемент класса объявляется внутри определения класса. Тамже может быть расположено и оределение тела функции. В этом случае функцию-элемент называют встроенной и компилятор будет генерировать ее встроенное расширение на месте вызова. Если определение функции располагается вне тела класса, то к ее имени добавляется префикс, состоящий из имени класса и операции разрешения области действия. В этомслучае функцию-элемент также можно определить как встроенную с помощью ключевого слова inline. Вот несколько модифицированный класс Point из предыдущей главы вместе с его реализацией:

#include <assert.h>

const int MaxX = 200; // Максимальные значения координат.

const int MaxY = 200;

//

struct Point { // Класс.точек.

private:

int fx;

int fy;

public:

int GetX(void) ( return fx; }

int GetY(void) { return fy; }

void SetPoint(int, int);

};

void Point::SetPoint(int x, int y)

{

assert(x >=0 && x < MaxX);

assert(y >= 0 && у < MaxY);

fx =x;

fy = y;

}

Здесь обе функции Get () определены как встроенные, а функция SetPoint () определяется вне тела класса и не является встроенной.

Класс как область действия

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

  • Для существующего представителя класса с помощью операции доступа к элементу (точки).
  • Через указатель на существующий представитель класса с помощью операции косвенного доступа к элементу (стрелки).
  • С помощью префикса, состоящего из имени файла и операции разрешения области действия (::).

Доступ к элементам данных

Поскольку функции-элементы класса находятся в его области действия, они могут обращаться к элементам данных непосредственно по имени, как можно видеть в последнем примере. Обычные функции или функции-элементы других классов могут обращаться к элементам существующего представителя класса с помощью операций “.” или “->”:

class Time { public:

int hour;

int min;

} ;

int main()

{

Time start; // Локальный объект Time.

Time *pTime = Sstart; //Указатель на локальный объект.

start.hour = 17; // Операция доступа к элементу.

pTime->min = 30; // Косвенный доступ к элементу.

return 0;

}

Вызов функций-элементов класса

Совершенно аналогично тому, что имеет место в случае элементов-данных, функции-элементы класса могут вызываться функциями-элементами того же класса просто по имени. Обычные функции и элементы других классов могут вызывать функции-элементы данного класса для существующих его представителей с помощью операций “ . ” или “->” (через указатель). Приведенный ниже пример это иллюстрирует.

#include <stdio.h>

class Time ( int hour;

int min;

public:

void SetTime(int h, int m)

{

hour = h; min = m; } void ShowTime(void)

{

printf("Time: %02d:%02d.n", hour, min);

}

};

int main()

{

Time start;

Time *pStart = &start;

int hr, min;

start.SetTime(17, 15); // Вызов элемента для объекта

// start.

pStart~>ShowTime(); // вызов элемента через указатель

//на объект.

return 0;

}

Указатель this

Любая функция-элемент класса, не являющаяся статической (что это такое, выяснится позднее) имеет доступ к объекту, для которого она вызвана, через посредство ключевого слова this. Типом this является имя_класса*.

class Dummy {

void SomeFunc(void) {...};

public:

Dummy();

};

Dummy::Dummy)

{

SomeFunc();

this->SomeFunc();

(*this).SomeFunc();

}

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

Примечание

На самом деле this является скрытым параметром, передаваемым функции-элементу класса при вызове. Именно этим функция-элемент (не статическая) отличается от обычной функции. При вызове функции-элемента компилятор генерирует код, который после всех указанных в вызове параметров помещает на стек указатель на объект, для которого функция вызвана. Поэтому, например, нельзя вызвать функцию-элемент через обычный указатель на функцию. Указатель на функцию-элементобъявить можно, но в нем явно должен быть специфицирован класс, например:

long (Dummy::*fPtr)();

fPtr = &Dummy::SomeFunc;

Привести такой указатель к обычному типу указателя на функцию невозможно. При его разыменовании всегда нужно указывать конкретный объект, и для этого в C++ предусмотрены две новых операции “. *” и “->*”. Первая из них применяется с непосредственным объектом, вторая — с указателем на объект. Вызов фун.кции-элемента через указательвыглядит так:

1 = (dummyObj.*fptr)();

1 = (dummyPtr->*fptr)();

Все сказанное приложимо и к элементам данных. На них тоже можно ссылаться с помощью указателей, которые нельзя привести к обычному типу указателя. На самом деле они содержат не адрес некоторой ячейки памяти, а смещение элемента данных относительно начала объекта. Для разыменования указателей на элемент данных используются те же операции:

double Dummy::*dptr;

dptr = &Dummy::someData;

d = dumrnyObj . *dptr;

d = duinmyPtr->*dptr;

Приоритет и правило ассоциации у этих специфических операций те же, что и у обычного разыменования (*) и других одноместных операций (14, справа налево).

Статические элементы класса

Можно объявить элемент класса (данные или функцию) как статический.

Статические элементы данных

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

Помимо объявления в определении класса, статический элемент данных должен еще и определяться:

class SomeClass

{

static int iCount;

// Объявление статического

// элемента.

//.. .

};

int SomeClass::iCount = 0;

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

// элемента.

Примечание

Обращаться к открытым статическим элементам класса можно либо через любой его представитель операциями “.” и “->”, либо с помощью операции разрешения области действия (SomeClass : : iCount). Последний способ предпочтительнее, так как ясно показывает, что элемент не связан с конкретным объектом.

Статические элементы-функции

Функция класса, объявленная с модификатором static, не связывается ни с какими его конкретными представителями. Другими словами, ей не передается указатель this в качестве скрытого параметра. Это означает, что:

  • Статическая функция-элемент может вызываться, даже если никаких
  • Статическая функция-элемент может обращаться только к статическим элементам данных класса и вызывать только другие статические функции-элементы класса.
  • Такая функция не может быть объявлена виртуальной.

Примечание

Статические функции-элементы класса могут передаваться процедурам API Windows в качестве возвратно-вызываемых, поскольку не предполагают наличия на стеке параметра this. Обычные функции-элементы для этого не годятся.

Специальные функции-элементы класса

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

  • Конструктор. Инициализирует представители класса.
  • Конструктор копии. Инициализирует новый представитель, используя значения существующего объекта.
  • Операция присваивания. Присваивает содержимое одного представителя класса другому.
  • Деструктор. Производит очистку уничтожаемого объекта.
  • Операция new. Выделяет память для динамически создаваемого объекта.
  • Операция delete. Освобождает память, выделенную под динамический объект.
  • Функции преобразования. Преобразуют представитель класса в другой тип (и наоборот).

Конструктор

Конструктор имеет то же имя, что и класс. Он вызывается компилятором всегда, когда создается новый представитель класса. Если в классе не определен никакой конструктор, компилятор генерирует конструктор по умолчанию (не имеющий параметров).,Относительно конструкторов имеют место такие правила:

  • Для него не объявляется тип возвращаемого значения.
  • Он не может возвращать значений оператором return.
  • Конструктор не наследуется.
  • Конструктор не может быть объявлен как const, volatile, virtualили static.

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

Примечание

Можно вызвать конструктор для инициализации уже существующего объекта, если перегрузить глобальную операцию new таким образом, чтобы она принимала дополнительный аргумент — указатель типа void*. Это называют размещающей формой операции; мы о ней уже упоминали в прошлой главе. Такая методика иногда применяется для глобальных представителей класса, если их нужно инициализировать после выполнения каких-то предварительных действий. Вот пример:

#include <new.h>

// Операция new, допускающая форму размещения:

inline void *operator new(size_t, void* p)

{

return p;

}

class Dummy

{

public:

Dummy() // Конструктор.

};

Dummy gblDummy;// Глобальный объект.

int main ()

{

InitSystem(); // Какие-то проверки

// и инициализации.

new(&gblDummy) Dummy; // Вызывает конструктор

// для gblDummy.

return 0;

}

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

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

Вот простейший пример класса с двумя перегруженными конструкторами, в одном из которых применяется обычный способ инициализации в теле функции, а во втором — список инициализации элементов:

class Time { int hr, min;

public:

Time(int h)

{

hr = h; min = 0;

}

Time(int h, int m): hr(h), min(m)

{

}

};

Тело второго конструктора, как видите, пусто.

Примечание

Список инициализации является единственным средством присвоения значений элементам данных класса, объявленным как const или как ссылки (а также закрытым элементам базовых классов).

 

Конструктор копии

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

class Time {

int hr, min;

public:

Time(int h, int m): hr(h), min(m) {}

Time(const Time &src) // Конструктор копии.

{ hr = src.hr; min = src.min; } //

};

int main()

(

Time start (17,45); // Вызывается первый конструктор.

Time current = start; // Вызывается конструктор копии.

return 0;

}

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

Примечание

Иногда, когда копирование объектов класса в принципе не может привести ни к чему хорошему, имеет смысл объявить конструктор копии (это может быть просто “пустышка”) в закрытом разделе определения класса. Тогда пользователь класса не сможет создавать копии существующих объектов.

Операция присваивания

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

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

Вот пример класса с операцией присваивания:

class Time { int hr, min;

public:

Time(int h, int m): hr(h), min (m) {}

Time &operator=(const Times); // Операция присваивания.

};

Time &Time::operator=(const Time &src)

{

if(&src == this) // Проверка на самоприсваивание.

error("Self assignment!");

hr = src.hr;

min = src.min;

}

return *this; // Возвращает ссылку на свой объект.

int main() {

Time start (17,45);

Time current (18, 0);

start = current; // Вызывает operator=.

return 0;

}

Здесь, кстати, показан прием проверки на самоприсваивание, позволяющей предотвратить присваивание объекта самому себе.

Примечание

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

Параметры конструктора копии и операции присваивания могут иметь тип либо имя_класса&, либо const имя_класса&. Последнее предпочтительнее, так как простая ссылка на класс не позволяет копировать константные объекты.

Примечание

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

Деструктор

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

Именем деструктора должно быть имя класса, которому предшествует тильда (~). Свойства деструкторов таковы:

  • Деструктор не имеет параметров.
  • Он не может возвращать значений.
  • Деструктор не наследуется.
  • Деструктор не может объявляться как static, const или volatile.
  • Деструктор может быть объявлен виртуальным.

Операции класса new и delete

Класс может определять свои собственные операции new и delete (new[] и delete [] для массива объектов):

  • Функция имя_класса: : operator new () вызывается при создании динамических объектов.
  • Функция имя_класса: : operator new [ ] () вызывается при создании динамических массивов объектов.
  • Функция имя_класса:: operator delete() вызывается при удалении динамических объектов.
  • Функция имя_класса:: operator delete [] () вызывается при удалении динамических массивов.

Ниже приведен довольно длинный пример, демонстрирующий определение операций класса new и delete, а также глобальных new [ ] и delete []. Вывод программы позволяет проследить порядок вызовов конструкторов/деструкторов и операций new/delete при создании автоматических и динамических объектов.

Листинг 8.1. Определение операций класса new и delete

//////////////////////////////////////////////

// NewDel.cpp: Операции класса new и delete.

//

#pragma hdrstop

#include <condefs.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#define trace(msg)

printf(#msg "n")

#define MaxStr 32

void *operator new[](size_t size)

// Замена глобальной

// new[].

{

trace(Global new [ ] .);

return malloc(size);

}

void operator delete[](void *p)

// Замена глобальной

// delete[].

{

trace(Global delete [].);

free(p) ;

}

class Hold {

char *str;

// Закрытый указатель на строку.

public:

Hold(const char*) ;

~Hold() ;

void *operator new(size t);

// Операция new класса.

void operator delete(void*);

// Операция delete класса.

void Show(void)

{ printf("%sn", str);

}

};

Hold::Hold(const char *s)

// Конструктор.

{

trace (Constructor.) ;

str = new char[MaxStr];

// Вызов глобальной new[].

strncpy(str, s, MaxStr);

// Копирование строки в объект.

str[MaxStr-1] = 0;

// На всякий случай...

}

Hold::~Hold ()

// Деструктор.

{

trace(Destructor.);

delete[] str;

// Очистка объекта.

}

void *Hold::operator new(size_t size)

{

trace(Class new.);

return malloc (size);

)

void Hold::operator delete(void *p)

{

trace(Class delete.);

free(p) ;

)

void Func()

// Функция с локальным объектом.

{

Hold funcObj ("This is a local object in Func.");

funcObj.Show() ;

}

int main () {

Hold *ptr;

Hold mainObj ("This is a local object in main.");

mainObj.Show ();

trace (*);

ptr = new Hold("This is a dynamic object.");

ptr->Show();

delete ptr;

trace (*);

FuncO ;

trace (*);

return 0;

}

Результат работы программы показан на рис. 8.1.

C++Builder, программы, электронные книги, раскрутка, оптимизация

Рис. 8.1Программа NewDel

Примечание

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

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

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

Конструкторы преобразования

Если конструктор класса А имеет единственный параметр типа В, то объект В может быть неявно преобразован в класс А с помощью такого конструктора. Другими словами, компилятор может сам вызывать такой конструктор, чтобы “из В сделать А”. Возьмите пример из предыдущего раздела. Локальный объект можно было бы инициализировать по-другому:

class Hold {

char *str;

public:

Hold(const char*);

//...

};

main () {

Hold mainObj = "This is a local object in main.";

//. . .

return 0;

Таким образом, в этом примере объявленный в классе конструктор Hold(const char*) является по сути конструктором преобразования.

Ключевое слово explicit

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

class Hold {

char *str;

public:

explicit Hold(const char*);

//. . .

};

main () {

//

// Неявное преобразование недопустимо:

//

// Hold mainObj = "This is a local object in main.";

//...

return 0;

}

Такой конструктор можно вызывать только явным образом, т. е.

Hold mainObj("This is a local object in main.");

или

Hold mainObj = Hold("This is a local object in main.");

Примечание

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

Операции приведения

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

operator имя_нового_типа();

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

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

Вот пример процедуры преобразования:

#include <stdio.h>

class Time { int hr, min;

public:

Time(int h, int m): hr(h), min(m) {}

// Конструктор.

operator int();

// Процедура преобразования.

};

Time::operator int() {

// Преобразует время в число секунд от начала суток:

return (3600*hr + 60*min);

}

main ()

{

int h = 7;

int m = 40;

Time t (h, m);

//

// Последний параметр вызывает Time::operator int():

//

printf("Time: %02d:%02d = %5d seconds.n", h, m, (int)t);

return 0;

Константные объекты и функции-элементы

Можно создать представитель класса с модификатором const. Тем самым гарантируется, что после инициализации содержимое объекта нельзя будет изменить. Компилятор C++Builder выдает предупреждение в случае, если для объекта вызывается функция-элемент, не объявленная как const. Другие компиляторы могут выдать сообщение об ошибке и отказаться транслировать подобный код.

Со своей стороны, константная функция-элемент

  • объявляется с ключевым словом const после списка параметров;
  • не может изменять содержимое элементов данных класса;
  • не может вызывать не-константные функции-элементы. Вот пример константной функции и константного объекта:

class Time {

int hr, min;

public:

Time(int h, int m): hr(h), min(m) {}

void SetTime(int h, int m) { hr = h; min = m;

}

void GetTime(int&, int&) const;

}

void Time::GetTime(int &h, int &m) const {

h = hr;

m = min;

//

// Следующий оператор здесь был бы некорректен:

//

// min = 0;

int main ()

{

Time t(17, 45); // Обычный объект.

const Time ct(18, 0); // Константный объект.

int h, m;

ct.GetTime(h, m); // Вызов const-функции для const-объекта. t.SetTime(h, m) ;

//

// Следующий вызов некорректен:

// // ct.SetTime(0, 0) ;

return 0;

}

Ключевое слово mutable

Константная функция-элемент “обещает” не изменять значений элементов данных класса, что позволяет применять ее на константных объектах. Тем не менее, в некоторых ситуациях имеет смысл разрешить некоторым элементам меняться даже у константных объектов. Например, некоторый существенный набор данных изменять ни в коем случае нельзя, в то время как отдельный элемент, скажем, некоторое сообщение, может и должно меняться. В этом случае можно объявить элемент данных с ключевым словом mutable:

class AnyClass {

int value;

mutable const char *msg;

public.:

AnyClass (): value (0), msg(NULL) {}

int GetValueO const;

// ... };

j nt AnvClass::Get Value() const

{

msg - "New message!";

// Допускается, поскольку msg - mutable.

//

// value изменять нельзя:

//

// value = 111;

//

return value;

}

Модификатор mutable не может применяться совместно с const или static (в приведенном примере все верно, поскольку const относится не к msg, а к содержимому строки, на которую он ссылается).

“Друзья”

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

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

Вот пример объявления Друзей класса:

class SomeClass (

friend class AnotherClass;

friend void regularFunc (int);

friend void OtherClass::MemFunc(double);

//...

};

Следует иметь в виду такие правила:

  • Дружественность не обратима: если А объявляет В другом, это не значит, что А будет другом В. Дружба “даруется”, но не “присваивается”.
  • Дружественность не наследуется: если А объявляет В другом, то классы, производные от В, не будут автоматически друзьями А.
  • Дружественность не транзитивна: если А объявляет В другом, то классы, производные от А, не будут автоматически признавать В своим другом.

Перегрузка функций-элементов

Функции-элементы класса могут быть перегружены подобно обычным функциям; несколько функций-элементов могут иметь одно и то же имя, если их можно однозначно идентифицировать по списку аргументов. Вы уже встречались в этой главе с перегруженным конструктором. Это весьма распространенная ситуация. Вот еще подобный пример:

class Time {

long sec; public:

Time(): sec(O) {}

Time(long s): sec(s) {}

Time(int h, int m) {

sec = 3600*h + 60*m;

}

//... };

int main ()

{

Time tl; // Вызывает Time::Time().

Time t2(86399); // Вызывает Time::Time(long).

Time t3(ll, 33); // Вызывает Time::Time(int, int).

//. . .

return 0;

}

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

+

*

/

%

/

&

|

 ~ 

!

=

<

>

+=

-=

*=

/=

%=

^=

  &=

|=

<<

>>

>>=

<<=

= =

! =

<=

>=

&&

 ||

++

'

->*

->

()

[]

new

delete

 

new[]

delete

[ ]

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

Нельзя перегружать операции:

.

.*

::

?:

 

Функции-операции, реализующие перегрузку операций, имеют вид

operator операция ([операнды]) ;

Примечание

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

Для перегрузки операций существуют такие правила:

  • Приоритет и правила ассоциации для перегруженных операций остаются теми же самыми, что и для операций над встроенными типами.
  • Нельзя изменить поведение операции по отношению к встроенному типу.
  • Функция-операция должна быть либо элементом класса, либо иметь один или несколько параметров типа класса.
  • Функция-операция не может иметь аргументов по умолчанию.
  • Функции-операции, кроме operator=(), наследуются.

Примеры

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

Листинг 8.2. Перегрузка операции сложения

//////////////////////////////////////////////////////

// StrAdd.cpp: Демонстрация перегрузки сложения для строк.

//

#pragma hdrstop

#include <condefs.h>

#include <string.h>

#include <stdio.h>

class String {

char *str; // Указатель на динамическую строку.

int len; // Длина строки.

String(int); // Вспомогательный конструктор. public:

String(const Strings); // Конструктор копии.

String(const char*); // Конструктор преобразования.

~String () ;

String Soperator=(const Strings);

String operators- (const Strings);

friend String operator+(const Strings, const Strings);

void Show () ;

};

String::String(int size)

{

len = size;

str = new char[len + 1]; / 
MKPortal©2003-2008 mkportal.it
MultiBoard ©2007-2009 RusMKPortal