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

Технологии программирования.-3

.pdf
Скачиваний:
4
Добавлен:
05.02.2023
Размер:
1.99 Mб
Скачать

190

...

Point x; //создан один объект до входа в основную программу

//значение NumberPointObj равно 1 void func()

{

Point a(2,2),b(3,3),c(4,4), *ptr; //создано еще три объекта

ShowNumberObj() ; //будет выведено значение 5 a.ShowNumberObj() ; // здесь тоже будет выведено значение 5 x.ShowNumberObj() ; // здесь тоже будет выведено значение 5 ptr=new Point(10,20);

ShowNumberObj() ; //будет выведено значение 6 c.ShowNumberObj() ; // здесь тоже будет выведено значение 6 ptr->ShowNumberObj() ; // здесь тоже будет выведено значение 6 delete ptr; //удалить динамический объект

ShowNumberObj() ; //будет выведено значение 5 b.ShowNumberObj() ; // здесь тоже будет выведено значение 5 //при выходе из функции все локальные объекты уничтожаются //значение NumberPointObj станет равно 2

}

////

void main()

{

Point y(1,1); //создан еще один объект

//значение NumberPointObj равно 2

func();

ShowNumberObj() ; //будет выведено значение 2 y.ShowNumberObj() ; // здесь тоже будет выведено значение 2 //удаляется локальный объект y

}

//удаляется статический объект x

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

Статические члены-функции объявляются с помощью ключевого слова static.

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

class Obj

{

int type; public:

191

Obj(int t) { type=t; }

void Show() { cout<<"type "<<type<<"\n"; static void Print(Obj o)

{

cout<<type; //здесь будет ошибка нет доступа к членам объекта Show();//здесь тоже будет ошибка нет доступа к членам объекта cout<<o.type; //здесь все будет нормально

}

};

//использование статических функций void main()

{

Obj x(10); x.Show();

x.Print(x); //вызов статической функции для объекта x

Obj::Print(x); //вызов статической функции через имя класса для объекта x

}

Инициализация статических членов для вложенных классов

class Example { static int x;

static const int size = 5;

class Elem{ //объявление вложенных классов

static float f; //статический член вложенного класса void func(void);

}; public :

char array[size];

};

int Example::x = 1;

float Example::Elem::f = 3.14; //инициализация статического вложенного члена

void Example::Elem::func(void) { /* определение функции-члена вложенного класса */ }

4.1.13 Наследование

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

192

class B { ....};

и мы хотим получить другой класс D добавив туда несколько новых членов. Это расширение можно записать так:

class D: public B { ... };

Эта запись означает, что класс D включает в себя все члены класса B и новые члены, записанные в фигурных скобках. В таком случае говорят о том, что класс D наследует свойства класса B. Класс D может наследовать несколько классов, например,

class D: public B1, private B2 { ... };

Ключевые слова public и private определяет права доступа к членам класса B1 и B2 в классе D.

Рассмотрим примеры наследования:

class Point //базовый класс

{

protected: //эти члены будут доступны для функций из наследуемых классов

int x, int y;

public: //интерфейс класса int& X() { return x;}

int& Y() { return y; }

void Show() { cout<<x<<" "<<y<<"\n"; };

class Pixel: public Point //класс Pixel наследует класс Point

{

int color; //по умолчанию private public: //интерфейс класса

void Set(Point z, int col) { x=z.x; y=z.y; color=col; } void Show() { putpixel(x,y,color); }

};

void main()

{

Pixel t; Point v;

t.x=100; //здесь будет сгенерирована ошибка , т.к. x защищенный член. t.X()=100; //присвоить значение 100 t.x

193

t.Y()=200; //присвоить значение 200 t.y v.Set(t,255); //установить значения для объекта v

cout<<v.X()<<" "<<v.Y()<<"\n"; //вывод координат x и y объекта v. v.Show(); //вывести точку

v.Point::Show() //вывести значения координат точки

}

Как видим из примера, что члены класса Point становятся членами класса Pixel.

Однако видимость некоторых членов класса Point исчезает, например функция Show() класса Point перекрывается функцией Show() класса Pixel. Используя имя класса и операцию разрешения видимости можно организовать доступ к функции Point::Show()

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

Правила определения прав доступа при наследовании

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

public, protected и private.

1. Если при наследовании класс объявлен как public, то:

1)все public-члены базового класса останутся public в наследующем

классе;

2)все protected-члены базового класса останутся protected в наследующем классе;

3)все private-члены базового класса останутся private в наследующем классе.

2. Если при наследовании класс объявлен как protected, то:

1)все public-члены базового класса останутся protected в наследующем классе;

2)все protected-члены базового класса останутся protected в наследующем классе;

3)все private-члены базового класса останутся private в наследующем классе.

3. Если при наследовании класс объявлен как private, то все public и protected члены базового класса в наследующем классе будут private.

194

Иерархия классов

Иерархия классов это совокупность классов порожденных от одного. Например

class Base { ... };

//первый уровень наследования class D1: public Base { ... }; class D2: protected Base { ... };

//второй уровень наследования class D11: public D1 { ...}; class D12: public D1 { ... }; class D21: public D2 { ...}; class D22: public D2 { ... };

//Третий уровень наследования это классы, порожденные от классов второго уровня.

Количество уровней неограниченно

Виртуальный класс

Выше дан пример простейшей иерархии классов. В реальной ситуации наследование может быть сложнее. Например

class Base { ... };

class D: public Base { ... }; class C: public Base { ... };

class Next: public D, public C { ... };

В этом случае класс Base входит и в D и в C. Получается две копии класса B. Для того чтобы избежать такой ситуации класс, который в механизме наследования может входить не один раз можно объявить виртуальным (ключевое слово virtual) тогда в производных классах будет храниться одна копия. Тогда можно записать:

class Base { ... };

class D: virtual public Base { ... }; class C: virtual public Base { ... };

195

Преобразования типов объектов для иерархии классов

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

для описанной выше иерархии можно записать:

Base *pb; D1 *pd;

D1 o1; D2 o2; D11 o11; D22 o22;

pb=&o1; //через указатель pb типа Base имеем доступ к членам объекта o1, которые принадлежат классу Base (механизм виртуальных функций будет описан ниже)

pb=&o11; // через указатель pb имеем доступ к членам объекта o11, которые принадлежат классу Base.

pd=&o11; // через указатель pd типа D1 имеем доступ к членам объекта o11, которые принадлежат классу D1

Возможно и обратное преобразование. Например,

D11 *pd11=(*D11) pd;

Однако узнать точно, что указатель pd ссылается объект данного производного класса в общем случае нельзя. Поэтому программист должен железно знать, что указатель ссылается на объект производного класса или использовать механизмы проверки типов во время выполнения программы (Например, RTTI — описание этого механизма смотри ниже.)

Все вышесказанное касается и ссылок.

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

Конструкторы при наследовании

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

class B {

...

B(..) { ...} //конструктор B

....

};

class D: public B {

...

D(..) :B(..) { .... } //конструктор класса D

...

};

196

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

Запишем пример

class OneD //базовый класс

{

double x; public:

OneD() { x=0; cout<<"OneD\n"; } OneD(double z) { x=z; cout<<"OneD\n"; } Show() { cout<<"OneD::Show()\n"; }

};

class TwoD: public OneD //наследуем базовый

{

double y; public:

TwoD(): OneD() { y=0; cout<<"TwoD\n";} //вызов конструктра базового класса

TwoD(double t, double v) :OneD(t) { y=v; cout<<"TwoD\n"; } Show() { cout<<"TwoD::Show()\n");

};

class ThreeD: public TwoD

{

double z; public:

ThreeD(): TwoD() { z=0; cout<<"ThreeD\n"}

ThreeD(double t, double v, double w) :ThreeD(t) { z=w; cout<<"ThreeD\n";} Show() { cout<<"ThreeD::Show()\n");

};

void main()

{

TreeD xyz(1,2,3); xyz.Show();

}

Листинг

OneD

TwoD

ThreeD ThreeD::Show()

197

Конструкторы для виртуальных классов

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

1) первыми всегда вызываются конструкторы для виртуальных клас-

сов;

2)затем вызываются конструкторы для базовых классов;

3)последними вызываются конструкторы производных классов. Например,

class Alpha: public Beta, virtual public Gamma { ... }; Alpha x;

последовательность вызова конструкторов следующая:

Gamma() //всегда первым

Beta()

Alpha()

Деструкторы при наследовании

Деструкторы при наследовании вызываются автоматически строго в обратном порядке вызовов конструкторов (см. Порядок вызова конструкторов).

Вызов конструкторов для объектов членов класса:

Например

class Point //базовый класс

{

protected: //эти члены будут доступны для функций из наследуемых классов

int x, int y;

public: //интерфейс класса

Point() { x=0; y=0;}

Point(int x, int y) { Point::x=x; Point::y=y; } Point(Point& t) { x=t.x; y=t.y; }

};

class Line

{

Point t1, t2;

198

public:

Line(int x1, int y1, int x2, int y2):t1(x1,y1),t2(x2,y2) { cout<<"Line::Line(1)\n"; }

Line(Point v,Point z):t1(v),t2(z) { cout<<"Line::Line(2)\n"; } Line(Line& a):t1(a.t1),t2(a.t2) { cout<<"Line::Line(3)\n"; }

...

};

4.1.14 Дружественные функции и классы

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

4.1.15 Полиморфизм

Начнем рассмотрение понятия полиморфизма с примера. Предположим, у нас имеется некоторый класс X и некоторая функция Func

class X

{

int x; public: X() { x=0;}

X(int i) {x=i;}

void Show() { cout<<"X::Show()\n"; } };

void Func(X *x) { ... x->Show(); ...}

void main()

{

X x1;

Func(&x1); //здесь будет выведено X::Show()

}

Теперь используя свойство наследования построим класс Y class Y: public X

{

public: Y():X() {} Y(int c):X(c){}

void Show() { cout<<Y::Show()\n"; } };

void main()

{

Y y1;

199

Func(&y1); //здесь будет выведено X::Show()

}

Нам бы хотелось чтобы в функции Func была вызвана член класса Y::Show(), не делая никаких изменений ни в классе X, ни в функции Func. В языке имеется специальный механизм виртуальных функций. Этот механизм позволяется через указатель или ссылку на объект базового класса подставлять объект наследного класса и при этом вызывить функции члены наследного класса. Для этого необходимо, что бы функции базового класса были объявлены виртуальными (ключевое слово virtual), а прототипы соответствующих членов наследного класса полностью совпадали. Например,

В базовом классе X необходимо функцию Show объявить следующим образом:

virtual void Show() { ... }

Рассмотрим еще пример

#include <iostream.h> class Enimal

{

public:

virtual void Name() { cout<<"животное\n"; } virtual void Speak() { cout<<"неизвестно\n"; }

};

class Cat: public Enimal

{

//...//члены данные public:

virtual void Name() { cout<<"Кошка\n"; } virtual void Speak() { cout<<"Мяу-мяу\n"; } //...//другие члены функции

};

class Dog: public Enimal

{

//...//члены данные int paw;

public:

virtual void Name() { cout<<"Собака\n"; } virtual void Speak() { cout<<"Гав-Гав\n"; }

virtual int Paw() { cout<<"Лапы\n"; } //новая виртуальная функция

//...//другие члены функции

};

class Bobik: public Dog

{

//...//члены данные public: