Классы и объекты

9 Классы и объекты

9.1 Параметризированные конструкторы

При создании объекта конструкторам можно передавать аргументы, которые обычно используются для того, чтобы инициализировать поля объекта. В этом случае конструктор должен иметь параметры. Например, можно усовершенствовать класс Queue, введя дополнит. переменную – номер объекта.

class Queue {

int Mas[100];

int Ins,

Del,

Number; // № объекта

public:

Queue(int n); // конструктор

~Queue( ); // деструктор

void Put(int i);

int Get( );

};

Конструктор будет иметь следующий вид:

Queue :: Queue (int n)

{ Ins = Del = 0;

Number = n;

cout << "Очередь " << Number <<

"инициализирована \n";

}

Деструктор:

Queue :: ~Queue( )

{

cout << "Очередь " << Number <<

"разрушена \n";

}

Для передачи аргумента конструктору необходимо задать его значение при объявлении объекта. C++ поддерживает два основных способа решения этой задачи. Первый из них выглядит следующим образом:

Queue A = Queue (1);

Здесь конструктор класса Queue вызывается непосредственно с передачей ему значения 1.

Второй способ короче и удобнее – аргументы следуют непосредственно за именем объекта в скобках.

Queue A (1);

Поскольку этот метод используется почти всеми программистами, пишущими на языке C++, то в дальнейшем будем использовать только его.

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

Queue A = 1;

Общая форма передачи аргументов конструктору имеет следующий вид:

имя_класса объект (список_аргументов);

Здесь список_аргументов представляет собой список аргументов, разделенных запятыми.

Пример использования параметризованного конструктора

void main()

{

Queue A(1), B(2);

A.Put(10);

B.Put(20);

A.Put(30);

B.Put(40);

cout << A.Get( ) << " " << A.Get( ) << "\n";

cout << B.Get( ) << " " << B.Get( ) << "\n";

}

Очередь 1 инициализирована

Очередь 2 инициализирована

10 30

20 40

Очередь 2 разрушена

Очередь 1 разрушена

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

9.2 Значения аргументов функции по умолчанию

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

void Func (int x, int y = 0);

Теперь функцию можно вызывать 2 способами:

main

{

Func (10, 20); // x = 10, y = 20

Func (10); // x = 10, y = 0

}

void Func (int x, int y)

{

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

}

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

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

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

2) все параметры по умолчанию должны располагаются правее параметров, которые не имеют значений по умолчанию;

3) при перегрузке функции каждая ее версия может включать в себя новые значения по умолчанию.

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

Queue :: Queue (int n = 0)

{ Ins = Del = 0;

Number = n;

cout << "Очередь " << Number <<

"инициализирована \n";

}

Тогда в результате объявления

Queue A, B(1);

создаются два объекта. Значение Number для объекта A равно 0, а для объекта B равно 1.

Корректное использование аргументов по умолчанию является мощным средством разработки программ. Однако не исключено его неправильное использование. Аргументы со значениями по умолчанию должны позволять функции выполнять ее задачи эффективно и легко, обеспечивая в то же время значительную гибкость. Для этого значения аргументов по умолчанию должны соответствовать наиболее частому способу использования данной функции. Например, аргументы по умолчанию имеют смысл, если их значения по умолчанию используются в 90 процентах случаев. Однако если общие значения появляются только в 10 процентах их вызовов, то использование аргументов со значениями по умолчанию не будет в этом случае целесообразным. Если не существует единственного значения, которое обычно соответствует параметру, то нет смысла в использовании аргумента со значением по умолчанию.

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

9.3 Дружественные функции

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

1) функция не является членом класса;

2) функция имеет доступ ко всем членам класса.

Например, пусть заданы два графических объекта Circle – окружность и Line – линия. Некоторая функция должна сравнивать цвета этих объектов и возвращать истину в случае совпадения и ложь в случае несовпадения цветов. Цвет окружности и линии задается частными данными обоих классов.

class TCircle;

class TLine {

int Color; // цвет линии

int Xn, Yn; // координаты начала линии

int Xk, Yk; // координаты конца линии

public:

void SetColor(int C);

void SetPosition(int X1, int Y1, int X2, int Y2);

void Show();

friend int CompareColor (TLine L, TCircle C);

};

class TCircle {

int Color;

int X, Y, Radius;

public:

void SetColor(int C);

void SetPosition(int X1, int Y1, int R);

void Show();

friend int CompareColor (TLine L, TCircle C);

};

В данном случае необходимо предварительное объявление класса TCircle, поскольку его имя используется в классе TLine до того, как будет объявлен класс TCircle.

Опишем функции-члены обоих классов:

void TLine :: SetColor(int C)

{

Color = C;

}

void TLine::SetPosition(int X1, int Y1, int X2, int Y2)

{

Xn = X1; Yn = Y1;

Xk = X2; Yk = Y2;

}

void TLine :: Show()

{

// рисование линии

}

void TCircle :: SetColor(int C)

{

Color = C;

}

void TCircle::SetPosition(int X1, int Y1, int R)

{

X = X1; Y = Y1; Radius = R;

}

void TCircle :: Show()

{

// рисование окружности

}

Описание дружественной функции:

int CompareColor(TLine L, TCircle C)

{

if( L.Color == C.Color )

return1;

return 0;

}

Поскольку функция не является членом класса, в описании отсутствует имя класса и оператор области видимости.

void main()

{

TLine Line;

TCircle Circle;

Line.SetPosition(10, 10, 20, 20);

Line.SetColor(3);

Line.Show( );

Circle.SetPosition(40, 40, 50);

Circle.SetColor(2);

Circle.Show( );

if ( CompareColor(Line, Circle) )

cout << "Цвета одинаковы \n";

else

cout << "Цвета разны \n";

}

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

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

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

9.4 Дружественные классы

Функция-член одного класса может быть объявлена как дружественная по отношению к другому классу, например:

class X {

. . .

void Func ();

};

class Y {

. . .

friend void X :: Func ();

};

В этом случае функция-член класса X имеет доступ к частным членам класса Y.

Все функции одного класса можно объявить как дружественные по отношению к другому классу. Для этого можно использовать следующую сокращенную запись:

class Y {

. . .

friend class X;

};

В этом случае все функции-члены класса X имеет доступ к частным членам класса Y. Однако, функции-члены класса Y не имеют доступа к частным членам класса X.

Если класс X является дружественным по отношению к классу Y, а класс Y является дружественным по отношению к классу Z, то это вовсе не означает, что класс X является дружественным по отношению к классу Z.

9.5 Взаимосвязь классов и структур

В C++ структура определена таким образом, что может включать в себя как данные, так и код. Структура также может содержать конструкторы и деструкторы. Таким образом, структура является разновидностью класса. Единственное отличие между ними связано с тем, что по умолчанию члены класса имеют в качестве спецификатора доступа private, а члены структуры – public. Например:

struct MyType {

void Put(int i);

int Get();

private:

int j;

};

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

class MyType {

int j;

public:

void Put(int i);

int Get();

};

В C++ принять использовать структуры для определения объектов, содержащих только данные, а классы – для объектов, содержащих данные и код.

9.6 Связь объединений и классов

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

Объединения могут содержать конструкторы и деструкторы, а также функции-члены и дружественные функции. Подобно структурам, члены объединения по умолчанию имеют в качестве спецификатора доступа public. Ключевые слова private, protected и public не могут использоваться в объединениях. Например, следующая программа использует объединение для вывода символов, соответствующих старшему и младшему байтам целого числа:

union UType {

int A;

char B[2];

UType(int i);

void Show();

};

UType :: UType (int i)

{

A = i;

}

void UType :: Show ( )

{

cout << B[0] << " " << B[1] << "\n";

}

main()

{

UType X(1000);

X.Show();

}

Имеется несколько ограничений при использовании объединений в C++:

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

2) объединение не может иметь виртуальные функции-члены;

3) статические переменные не могут быть членами объединения;

4) объединение не может иметь в качестве члена какой-либо объект, перегружающий оператор =;

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

В C++ разрешено использовать анонимные объединения, т.е. объединения, у которых нет имени. Не существует объекта, имеющего это объединение в качестве своего типа. Таким образом, имя поименованного объединения определяет тип, а анонимное объединение определяет объект, а не тип. Имена членов анонимного объединения доступны непосредственно без использования оператора «точка».

int main()

{

union {

int A;

char B[2];

};

A = 100;

cout << A << " " << B[0] << " " << B[1] << "\n";

}

Ограничения на использование анонимных объединений:

1) имена членов анонимного объединения должны отличаться от других имен в области видимости объединения;

2) анонимные объединения не могут содержать функций-членов.

Переменные типа Variant (варьируемые) могут принимать значения любых типов данных. По сути, они являются объединениями. Часто используются при работе с базами данных. Поддерживаются языками Basic, средой визуального проектирования на Object Pascal – Delphy, а также версией С++ Builder.

9.7 Inline-функции

Другое название таких функций – встраиваемые функции.

Использование inline-функций может ускорить выполнение программы.

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

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

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

- если функция является типизированной, то создается временная переменная для хранения возвращаемого значения;

- когда значение возвращено, временная переменная уничтожается.

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

В С++ имеются два способа создания inline-функций:

Первый способ – использование модификатора inline, который предшествует объявлению функции:

inline объявление_функции;

class MyClass {

int Num;

public:

void Put (int i);

int Get();

};

inline void MyClass :: Put(int i)

{

Num = i;

}

inline int MyClass :: Get()

{

return Num;

}

main()

{

MyClass Ob;

Ob.Put(10); // Ob.Num = 10;

cout << Ob.Get(); // cout << Ob.Num;

}

Ключевое слово inline не является командой, оно представляет лишь запрос к компилятору сгенерировать подставляемый код, который не обязательно будет удовлетворен. Например, inline-функции не могут быть рекурсивными или содержать статические переменные. Некоторые компиляторы не подставляют функции, содержащие циклы, операторы switch и goto. Borland С не подставляет функции, использующие исключения или имеющие в качестве параметра объект, содержащий деструктор, или функции, возвращающие объекты, имеющие деструкторы. Если какое-либо из ограничений нарушено, то компилятор генерирует обычную функцию.

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

class MyClass {

int Num;

public:

void Put (int i) { Num = i; }

int Get() { return Num; }

};

9.8 Передача объектов в функции

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

Однако тот факт, что создается копия, означает, что создается новый объект. В результате возникает вопрос, исполняется ли конструктор объекта при создании копии и исполняется ли деструктор, когда копия уничтожается. Рассмотрим пример:

class MyClass {

int Num;

public:

MyClass(int i = 0);

~MyClass();

void Set (int i) { Num = i; }

int Get() { return Num; }

};

MyClass :: MyClass (int i)

{

Num = i;

cout << “Конструктор объекта” << Num << “\n”;

}

MyClass :: ~MyClass ()

{

cout << “Деструктор объекта” << Num << “\n”;

}

Рассмотрим функцию, которой передается объект через параметр-копию

void Func(MyClass Ob)

{

Ob.Set(2);

cout << “Объект в Func ” << Ob.Get() << “\n”;

}

main()

{

MyClass Object(1);

Func(Object);

cout << “Объект в main” << Object.Get() << “\n”;

}

Эта программа выведет на экран следующий текст:

Конструктор 1

Объект в Func 2

Деструктор 2

Объект в main 1

Деструктор 1

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

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

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

9.9 Присваивание объектов

К объектам одного и того же типа можно применять операцию присваивания как к обычным переменным. По умолчанию присваивание выполняется путем побитового копирования данных объекта-источника в данные объекта-приемника. Например:

main()

{ MyClass Ob1, Ob2;

Ob1.Set(100);

Ob2 = Ob1;

cout << Ob2.Get( ) << “\n”; // 100

}

При присваивании объектов важно, чтобы не только типы объектов были физически одинаковыми, а чтобы одинаковыми были также имена типов, иначе при компиляции появится сообщение об ошибке.

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

class Base {

protected:

int A, B;

public:

void SetAB(int x, int y) { A = x; B = y;}

void ShowAB() {cout << A << “ ” << B << “\n”;}

};

class Proizv : public Base {

int C, D;

public:

void SetCD(int x, int y) { C = x; D = y;}

void ShowCD() {cout<< C << “ ” << D << “\n”;}

};

main()

{ Base Ob1;

Proizv Ob2;

Ob2.SetAB(1, 2);

Ob2.SetCD(3, 4);

Ob1 = Ob2;

Ob1.ShowAB(); // 1 2

Ob2 = Ob1; // Ошибка !

}

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

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

Присваивание объектов различных типов, не полученных путем наследования, запрещено.

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

9.10 Возвращение объектов функциями

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

class MyClass {

int Num;

public:

MyClass(int i = 0);

{ Num = i;

cout << “Конструктор объекта” << Num << “\n”;

}

~MyClass();

{ cout << “Деструктор объекта” << Num << “\n”; }

void Set (int i) { Num = i; }

int Get() { return Num; }

};

Опишем функцию, которая возвращает объект, используя способ возврата по копии.

MyClass Func()

{

MyClass Temp;

Temp.Put(1);

return Temp;

}

main()

{ MyClass Object;

Object = Func(); // Object = копия Temp;

cout << “Объект ” << Object.Get() << "\n";

}

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

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

2) уничтожается локальный объект с вызовом деструктора;

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

4) по окончании возврата временный объект уничтожается с вызовом деструктора.

Эта программа выдаст на экран:

Конструктор объекта 0 // Object

Конструктор объекта 0 // Temp

Деструктор объекта 1 // Temp

Деструктор объекта 1 // копия Temp

Объект 1 // Object

Деструктор объекта 1 // Object

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

9.11 Массивы объектов

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

main()

{

MyClass Mas[3];

for(int i = 0; i < 3; i++)

Mas[i].Put(2*i+5);

for(i = 0; i < 3; i++)

cout << Mas[i].Get() << "\n";

}

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

Инициализация массивов объектов

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

class MyType1 {

int Num;

public:

MyType1(int i) { Num = i;}

void Show() { cout << Num << “\n”; }

};

main()

{ MyType1 Mas[3] = { 1, 2, 3 };

for(int i = 0; i < 3; i++)

Mas[i].Show();

}

Эта программа выводит на экран числа 1, 2 и 3.

Если конструктор объекта требует два или более аргументов, тогда необходимо использовать полную форму инициализации:

class MyType2 {

int Num1, Num2;

public:

MyType2(int i, int j) { Num1 = i; Num2 = j; }

void Show() {

cout << Num1 << “ ” << Num2 << “\n”; }

};

main()

{ MyType2 Mas[3] = { MyType2 (1, 2),

MyType2 (3, 4),

MyType2 (5, 6) };

for(int i = 0; i < 3; i++)

Mas[i]. Show();

}

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

Создание инициализированных и неинициализированных массивов

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

class MyType {

int Num;

public:

MyType(int i = 0) { Num = i;}

};

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

MyType Mas1[ 3 ] = { 1, 2, 3 };

MyType Mas2[ 5 ];

9.12 Указатели на объекты

В языке С можно получить доступ к полям структуры или через имя структурной переменной и оператор «точка», или путем разадресации указателя на эту структуру. Аналогично в C++ можно получать доступ к данным и функциям объекта, только в случае доступа через указатель принято использовать операцию «» вместо операции разадресации «*».

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

class MyClass {

int Num;

public:

void Set (int i) { Num = i; }

int Get() { return Num; }

};

main()

{ MyClass Ob, *Ptr;

Ptr = &Ob;

Ptr->Set(100); // доступ через указатель

cout << Ptr->Get() << “\n”;

}

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

main()

{

MyClass Mas[2] = {100, 200};

MyClass *Ptr;

Ptr = &Mas[0]; // или Ptr = Mas;

Ptr->Show(); // 100

Ptr++;

Ptr->Show(); // 200

Ptr--;

Ptr->Show(); // 100

}

9.13 Указатель this

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

class Complex {

double a; // действительная часть числа

double b; // мнимая часть числа

public:

// сложение комплексных чисел

void Summa (Complex P1, Complex P2);

. . .

};

void main ( )

{

Complex Z1, Z2, Z3;

// ввод исходных данных Z1 и Z2

Z3.Summa(Z1, Z2); // вызов функции сложения

}

void Complex::Summa( Complex P1, Complex P2 )

{

a = P1.a + P2.a;

b = P1.b + P2.b;

}

На самом деле передача всех трех объектов функции Summa происходит следующим образом:

Z3.Summa (Z1, Z2);

void Complex::Summa( Complex *this, Complex P1, Complex P2 )

{

this->a = P1.a + P2.a;

this->b = P1.b + P2.b;

}

Поскольку непосредственный доступ к полям объекта Z3, который вызывает функцию Summa(), подразумевается, то имя указателя this при этом обычно опускается.

Классы и объекты