WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     | 1 |   ...   | 7 | 8 || 10 | 11 |   ...   | 17 |

// Сейчас будет использован конструктор копирования по умолчанию prim obj2=obj1;

// а сейчас будет использован явно определенный конструктор, // причем копируется лишь часть объекта:

prim obj3(obj,12);

} Отметим, что модификатор const используется для предотвращения изменения копируемого объекта.

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

struct s1{int i; float x;

s1(int j, float y){i = j; x = y;} };

struct s2{int i; float x;

s2 (const s1& a){ // Это не конструктор копирования! i =a.i; x=a.x;} };

void main(){ s1 obj1 (1, 3.7);

s2 obj2 (obj1);

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

class cl{... };

void f(){ cl obj1; cl obj2 = obj1; // Используется конструктор копирования.

cl obj3;

obj3 = obj1; // Присваивание! } Не всегда требуется при выполнении присваивания просто создавать копию. Если требуется нечто иное, то нужно переопределить операцию присваивания для класса.

18.10. Дружественные функции Могут встретиться ситуации, когда желательно иметь доступ к личным данным класса, минуя функции-члены. Наиболее распространена ситуация, когда функция-член одного класса должна иметь доступ к личным членам другого.

Рассмотрим снова пример с классами coord и triang.

class coord {double x, y, z;

public:

coord ();

coord (double, double, double = 0);

coord (coord & c);

};

class triang {coord vert1, vert2, vert3;

public:

triang ();

triang (coord &v1, coord &v2, coord &v3);

};

Пусть нам необходимо добавить в класс triang функцию-член, вычисляющую координаты центра треугольника.

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

Говорят также, что класс X является дружественным классу Y.

Для объявления привилегированной функции используется служебное слово friend. Чтобы функция стала привилегированной в классе Y, она должна быть объявлена в этом классе как дружественная функция.

Напишем три функции-члена класса triang, вычисляющие координаты центра треугольника по каждой из осей:

double triang::midx (){ return (vert1.x+vert2.x+vert3.x)/3;} и аналогично triang::midy (), triang::midz ().

Для того чтобы компилятор не выдал сообщение об ошибке, необходимо добавить в объявление класса coord, в любой его части, следующие объявления:

class coord { … friend triang::midx ();

friend triang::midy ();

friend triang::midz ();

} Достаточно распространенным является случай, когда все функции-члены одного класса являются привилегированными в другом; предусмотрена даже упрощённая форма записи:

class coord {… friend triang;

… };

или class coord {… friend class triang;

… };

В этом случае говорят, что класс triang является дружественным классу coord.

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

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

class cl {int numb;

// f_func () – не личный член класса, хотя объявляется в privateчасти.

friend void f_func (cl*, int);

public:

void m_func (int);};

void f_func (cl* cpt, int i){ cptr->numb = i; // Нужен явный указатель на объект, // так как указатель this не определён! } void cl::m_func(int i){ numb = i; // То же, что this->numb = i;

} void main (){ cl obj;

f_func (&obj, 10);

obj.m_func (10);

// Сравните способы вызова и аргументы функций! …} Следующий пример демонстрирует возможность доступа к статическим личным членам класса ещё до создания хотя бы одного объекта этого класса.

class cl {static int num;

public:

void set (int i){num = i;} void m_show () {cout<

int cl::num = 8;

void main(){ cout <<”Объектов типа cl нет.\n”;

cout <<”Статический член класса = ”;

// Пока можно использовать только дружественную функцию:

f_show ();

cl obj;

obj.set (200);

cout <<”Создан объект типа cl.\n”;

cout <<”Статический член класса = ”;

// Теперь можно использовать и функцию-член.

obj.m_show ();

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

class integer {int i;};

void main () {integer *ptr = new integer;...} Если же конструктор класса integer имеет аргументы, то список аргументов помещается там же, где при работе со стандартными типами данных находится инициализирующее выражение.



class integer {int i;

public:

integer ();

integer (int j): i(j) {} };

void main(){ int *ip = new int (10);

integer *iptr = new integer (30);

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

integer *ip1 = new integer ();

integer *ip2 = new integer;

Если конструктор без аргументов для класса X не определён, то при попытке выполнить оператор X *xp = new X;

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

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

Рассмотрим пример с указателем на автоматический объект абстрактного типа:

class cl{int num;

public:

cl (int i){ num = i;} ~cl (){} };

void main (){ // Создание объекта obj типа cl:

cl obj (1);

// Создание указателя ptr на объект класса cl и его инициализация // адресом, создаваемой здесь же безымянной переменной типа cl:

cl *ptr = &cl (2);

{// Указатель в блоке относится к тому же объекту, что и ptr.

cl *tmp = ptr;

} } В этом случае как конструктор, так и деструктор будут вызываться дважды. Сначала вызывается конструктор для объекта obj, затем конструктор для безымянного объекта, на который указывает ptr. При выходе из внутреннего блока указатель tmp теряется, однако сам объект сохраняется. При завершении main () в первую очередь вызывается деструктор для безымянного объекта, а затем – деструктор для obj.

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

class cl{int num;

public:

cl (int i){num = i;} ~cl (){cout<<”Деструктор класса cl. \n ”;

};

void main (){ cl *ptr = new cl (1);

… delete ptr;} Перед удалением из памяти объекта, на который указывает ptr, для него будет вызван деструктор. В результате на экране появится строка деструктор класса cl.

Вызов деструктора можно осуществить явно по его полному имени:

class cl{int num;

public:

cl (int i){ num = i;} ~cl(){} };

void main(){cl obj (1);

cl *ptr = &cl (2);

obj.cl::~cl ();

ptr -> cl::~cl ();

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

cl *ptr = new cl (1);

ptr -> cl::~cl ();

Отмети, что явный вызов деструктора не отменяет его автоматический вызов.

class X{ int *ip;

public:

X (int y){ip = new int(y);} ~X(){cout <<"Деструктор класса X;\n";

delete ip;} };

void main(){ X *xp = new X(5);

xp->X::~X(); // Явный вызов деструктора.

delete xp; // Вызов деструктора из delete.

} В результате получим два сообщения, если не произойдёт зацикливания при повторной операции delete, применённой к одному и тому же указателю ip:

Деструктор класса X;

Деструктор класса X;

Пользоваться явным вызовом деструктора надо очень осторожно.

19. Производные классы 19.1. Построение производного класса Рассмотрим класс с конструктором и деструктором:

class Base { int *bmember;

public:

Base (int arg = 0){bmrmber = new int(arg);} ~Base (){delete bmember;} };

Предположим, что нам нужно изменить этот класс так, чтобы объект такого типа содержал не один, а два указателя. Вместо того, чтобы изменять класс Base, можно поступить иначе. Ничего не меняя в Base, построить на его основе новый класс Derived:

class Derived: public Base{ int *dmember;

public:

Derived (int arg){ dmember = new int(arg); } ~Derived (){ delete dmember; } };

Запись вида class Derived: public Base говорит о том, что класс Derived является таким заново создаваемым классом, который построен на основе класса Base. При этом класс Derived наследует все свойства класса Base. Говорят, что Derived является классом, производным от класса Base, а класс Base является базовым классом для Derived.

Если в программе будет создан объект типа Derived, то он будет содержать два указателя на две области динамической памяти – bmember, как подобъект типа Base и dmember. Процесс создания объекта типа Derived будет проходить в два этапа: сначала будет создан "подобъект" типа Base, причём это сделает конструктор класса Base. Затем будет выполнен конструктор класса Derived. Вызов деструкторов осуществляется в обратном порядке. Поскольку конструктор класса Base может требовать наличия одного аргумента при обращении к нему, то этот аргумент необходимо передать. Чтобы передать список аргументов конструктору базового класса, этот список должен быть помещён в определении конструктора производного класса, подобно тому, как это делалось при инициализации данных абстрактного типа, являющихся членами некоторого класса:

Derived::Derived (int arg): Base (arg) { dmember = new int (arg);





} Если конструктор базового класса не имеет аргументов или использует аргументы по умолчанию, помещать пустой список в конструктор производного типа не надо.

19.2. Защищенные члены класса Для регулирования уровня доступа к членам классов используются служебные слова public и private. Для этих же целей введено ключевое слово protected (защищенный).

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

class Base{ private:

int privatem;

protected:

int protectedm;

};

class Derived: public Base{ memberF (){ cout<

} };

void main(){ Base b;

cout<

cout<

} 19.3. Управление уровнем доступа к членам класса В предыдущих примерах базовый класс являлся общим базовым классом для производного класса:

class Derived: public Base{…};

Это означает, что уровень доступа к членам класса Base для функций-членов класса Derived и просто пользователей класса Derived остался неизменным: личные члены класса Base не доступны в классе Derived, общие и защищенные члены класса Base остались соответственно общими и защищенными в Derived. Если не указать, что базовый класс является общим, то по умолчанию он будет личным:

class Derived: Base{…}; // Base – личный базовый класс.

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

Базовый класс не может быть защищенным базовым классом.

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

class Base { private: int privm;

protected: int protm;

public: int pubm;

};

class Derived: Base{// Личный базовый класс.

public:

Base::pubm; // Теперь pubm – общий член класса Derived;

Base::protm; // ошибка – изменение уровня доступа.

protected:

Base::protm; //Теперь protm – защищенный член класса Derived;

Base::pubm; // ошибка – изменение уровня доступа.

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

struct B: A {…};

Эквивалентно class B: public A{…};

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

struct B: A{…};

эквивалентна сlass B: public A {public: …};

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

class string{...

public:

string (char*);

~string ();

… };

class Base{… public:

Base (int);

~Base ();

...

};

class Derived: public Base { Base b;

string s;

public:

Derived (char*, int);

~Derived ();

...

};

Перед обращением к собственно конструктору класса Derived необходимо, во-первых, создать подобъект типа Base, во-вторых, члены b и s. Поскольку для их создания нужно обратиться к конструкторам соответствующих классов, мы должны им всем передать необходимые списки аргументов:

Derived::Derived (char *st, int len): Base (len), b (len+1), s (str) {…} В этом случае при создании объекта типа Derived сначала будет создан подобъект типа Base. При этом будет вызван конструктор Base::Base() с аргументом len. Затем будут созданы объекты b и s в том порядке, в котором они указаны в определении класса Derived. После этого будет выполнен конструктор Derived::Derived(). Деструкторы будут вызваны в обратном порядке.

19.5. Преобразования типов Объект производного типа может рассматриваться как объект его базового тип. Обратное неверно. (Кошка есть млекопитающее, но не любое млекопитающее есть кошка.) Компилятор может неявно выполнить преобразование объекта производного типа к объекту типа базового:

class Base {…};

class Der: public Base {…};

Der derived;

Base b = derived;

Обратное преобразование – Base к Der – должно быть определено программистом:

Der tmp = b; // Ошибка, если для Der // не определён конструктор Der (Base).

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

Если базовый класс является общим (public) базовым классом, т.е.

мы имеем дело с отношением вида class Base {…};

class Der: public Base {…};

Pages:     | 1 |   ...   | 7 | 8 || 10 | 11 |   ...   | 17 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.