Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


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




        struct class
public отсутствует public private
protected отсутствует protected private
private отсутствует. недоступны недоступны
public public public public
protected public protected protected
private public недоступны недоступны
public protected protected protected
protected protected protected protected
private protected недоступны недоступны
public private private private
protected private private private
private private недоступны недоступны

 

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

Чтобы проиллюстрировать некоторые особенности механизма наследования, построим на основе класса Figure производный класс Square (квадрат). При этом Square унаследует следующие компоненты класса Figure:

общедоступные (public, наследование интерфейса):

· show() функция отображения фигуры на экране (строка 1.3);

· hide() функция стирания фигуры с экрана (строка 1.4);

· move(int x, int y) функция изменений координаты фигуры (строка 1.5);

· Figure() конструктор класса (строка 1.2);

· InitGraphic() статическая функция инициализации графики (строка 1.6). защищённые (protected, наследование реализации):

· static HDC hdc статический дескриптор графики;

· display() функция выводит имя класса на экран (строка 1.1);

частные (private):

· static HWND hwnd.

Как видно из определения функций класса Figure, некоторые функции пустые, так как предполагается, что класс Figure описывает абстрактное понятие геометрической фигуры, которое будет конкретизировано с помощью классов наследников. Действительно, как мы видим ниже в программе, класс Figure является базовым для классов Square, ClsEllipse. В этих производных классах переопределены (наполнены конкретным содержанием) интерфейсные функции show(), hide(), move(int x, int y), (строки 1.11 – 1.13, 1.16-1.18) добавлены компонентные данные, необходимые для хранения данных о координатах квадрата и эллипса на экране.

Таким образом, на наследование можно смотреть как на способ добавления компонентных функций и компонентных данных к уже существующему классу. При этом класс наследник как бы уточняет, специализирует базовый класс. Говорят, что при этом мы переходим от общего (обобщённого понятия Figure) к частному (конкретному Square и ClsEllipse).

При наследовании употребляют два термина – наследование интерфейса и наследование реализации. В данном случае, поскольку при определении класса Square перед базовым классом Figure стоит ключевое слово public, то класс Square унаследовал от Figure интерфейс, т.е. все общедоступные (интерфейсные) функции Figure стали интерфейсными функциями и класса Square. Кроме того, класс Square унаследовал от класса Figure реализацию функции display(). Действительно, функция display() не была интерфейсной в классе Figure, но в классе Square мы её переопределяем (строка 1.10) и делаем интерфейсной, причем в её теле вызываем ещё и (Figure::display()) функцию базового класса. Другими словами мы в классе Square воспользовались унаследованной реализацией функции display() из базового класса.

// Figura.cpp:

#include "stdafx.h"

#include "afxwin.h"

#include "iostream"

using namespace std;

 

class Figure{

static HWND hwnd;

protected:

static HDC hdc;

void display(){cout<<" \n Figure ";}                        //1.1

public:

Figure(){/*cout<<"\n Figure()";*/}                    //1.2

void show(){}                                               //1.3

void hide(){}                                               //1.4

void move(int x, int y){}                                   //1.5

static void InitGraphic(){hwnd=FindWindow(_T("ConsoleWindowClass"),_T("C:\\Windows\\system32\\cmd.exe"));hdc=GetWindowDC(hwnd);}                                                //1.6

static void CloseGraphic(){ReleaseDC(hwnd, hdc); CloseHandle(hwnd);} //1.7

~Figure(){/*cout<<"\t ~Figure()";*/}                              //1.8

};

HWND Figure::hwnd = 0;

HDC Figure::hdc = 0;

//

//определение класса Square производного от Figure (наследование)

class Square: public Figure {                                              

POINT pt[5]; //Координаты вершин квадрата на экране

public:

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

Square(POINT* p){for(int i =0; i <5; i++){pt[i].x = p[i].x;pt[i].y = p[i].y;}} //1.9

void display(){cout<<" \n Square ->"; Figure:: display(); } //1.10

void show(){                                                //1.11

       CPen pen(PS_SOLID,2,RGB(255,0,0));

       SelectObject(hdc,pen);

       Polyline(hdc,pt,5);

}    

void hide(){                                                //1.12

       CPen pen(PS_SOLID,2,RGB(0,0,0));

       SelectObject(hdc,pen);

       Polyline(hdc,pt,5);

}

void move(int x, int y){for(int i = 0; i<5;i++){ pt[i].x+=x;pt[i].y+=y;} } //1.13

~Square(){/*cout<<"\t ~Square()";*/}                        //1.14

};

//

//определение класса ClsEllipse производного от Figure (наследование)

class ClsEllipse: public Figure {

CPoint pt1, pt2; //Координаты эллипса на экране   

public:

ClsEllipse(){/*cout<<"\t ClsEllipse()";*/                              //1.15

       pt1.x=100;   pt1.y=100;

       pt2.x=200;   pt2.y=200;

}

void show(){                                                //1.16

       CPen pen(PS_SOLID,2,RGB(0,255,0));

       SelectObject(hdc,pen);

       Arc(hdc,pt1.x,pt1.y,pt2.x,pt2.y,100,200,0,100);

}

void hide(){                                                //1.17

       CPen pen(PS_SOLID,2,RGB(0,0,0));

       SelectObject(hdc,pen);

       Arc(hdc,pt1.x,pt1.y,pt2.x,pt2.y,100,200,0,100);

}

void move(int x, int y){ pt1.x+=x,pt1.y+=y,pt2.x+=x,pt2.y+=y; }        //1.18

~ClsEllipse(){/*cout<<"\t ~ClsEllipse()";*/}          //1.19

};

//

//Определение класса MyObject (включение объектов)

class MyObject{

Square sq1, sq2; //Композиция (агрегирование по значению)   //1.20

ClsEllipse& elp; //Агрегация (агрегирование по ссылке)      //1.21

public:    

MyObject(const Square& p1,const Square& p2,ClsEllipse& el):sq1(p1),sq2(p2), elp(el) //1.22 {/*cout<<"\t MyObject()";*/}                                          

void show(){sq1.show(); sq2.show();elp.show();}             //1.23

void move(int x, int y){sq1.move(x,y); sq2.move(x,y);elp.move(x,y);}//1.24

void hide(){sq1.hide(); sq2.hide(); elp.hide();}            //1.25

~MyObject(){/*cout<<"\n ~MyObject()";*/}                    //1.26

};

//

// Определение класса Heir (множественное наследование)

class Heir: public Square, public ClsEllipse{                     //1.27

public:

Heir(POINT *p):Square(p),ClsEllipse(){/*cout<<"\t Heir()";*/ } //1.28

void show(){Square::show(); ClsEllipse::show();}            //1.29

void move(int x, int y){Square::move(x,y); ClsEllipse::move(x,y);}//1.30

void hide(){Square::hide(); ClsEllipse::hide();}            //1.31

~Heir(){/*cout<<"\n ~Heir()";*/}                            //1.32

};

 

void ShowMyObject(MyObject obj){                                             //1.33

for(int i = 0; i <100; i++){obj.show(); Sleep(24); obj.hide(); obj.move(4,0);}

}

Кроме наследования в программировании используется также приём агрегирования (включения в себя). Например, класс MyObject агрегирует два объекта класса Square по значению (это называется композицией) и один объект класса ClsEllipse по ссылке (строки 1.20, 1.21). Разный способ агрегирования говорит о том, что объекты класса Square будут уничтожены во время уничтожения объекта MyObject, а объект класса ClsEllipse может продолжать существовать.

Способ агрегирования зависит от нашего представления (интерпретации) решаемой задачи. Например, пусть под объектами класса MyObject мы подразумеваем автомобиль, у которого есть две двери (объекты класса Square) и прицеп объект класса ClsEllipse. Двери являются частью автомобиля и без него существовать не могут, поэтому объекты класса Square мы включаем в объект MyObject по значению, а вот прицеп может существовать и без автомобиля, поэтому объект класса ClsEllipse агрегируется в MyObject по ссылке. Она даёт возможность объекту MyObject посылать сообщения (управлять) объектом ClsEllipse, но при уничтожении объекта (машины) MyObject, объект ClsEllipse (прицеп) остаётся целым и возможно будет прицеплен к другому объекту MyObject.

Как мы видим из программы, в классе MyObject также переопределены функции show(), hide(), move(int x, int y) строки 1.23-1.25. В теле этих функций вызываются функции с таким же именем, но для объектов Square и объекта ClsEllipse. Это можно интерпретировать как управление (по средствам посылки сообщений) объектом MyObject его составными частями, чтобы они «двигались» синхронно как единое целое.

Таким образом, агрегирование - это способ взаимодействия классов при возникновении между ними отношения включения части в состав целого.

В программе выше определён ещё один класс Heir, он является производным от классов Square, ClsEllipse (строка 1.27). Поскольку базовых классов несколько, то такое наследование называют множественным. Более подробно множественное наследование мы будем рассматривать в следующем параграфе. Тем не менее, хотелось бы отметить, что множественное наследование стоит применять тогда, когда происходит объединение несколько равноправных понятий в единое производное понятие. Например, пусть под Square мы понимаем рамку картины, а под ClsEllipse холст. Тогда Heir это картина в целом. Поскольку картина на экране монитора должна двигаться как единое целое, то мы в классе Heir также переопределяем функции show(), hide(), move(int x, int y) строки 1.29-1.31, в теле которых происходит вызов этих же самых функций, но для объектов Square и ClsEllipse. Таким образом, Heir управляет своими частями.

Существует мнение, что множественное наследование — это неверная концепция, порождённая неверным анализом и проектированием, и в нашем случае лучше было бы создать класс Heir (картина), который бы агрегировал по значению объекты Square (рамка) и по ссылке ClsEllipse (холст), а не наследовал их.

 

void main(){

POINT pt1[5];                                       //2.1

pt1[0].x = 40;pt1[0].y=40;    

pt1[1].x = 40;pt1[1].y=140;   

pt1[2].x = 140;pt1[2].y=140;  

pt1[3].x = 140;pt1[3].y=40;         

pt1[4].x = 40;pt1[4].y=40;                                //2.2

         

Figure::InitGraphic();                              //2.3

{                                                   //2.4

     Square sq1(pt1); ClsEllipse elp;                 //2.5

     for(int i = 0; i <100; i++){                 //2.6

sq1.show(); elp.show(); Sleep(24); 

sq1.hide(); elp.hide();                   //2.7

           sq1.move(1,1); elp.move(2,2);             //2.8

     }                                              //2.9

}                                               //2.10

ClsEllipse elp;

Square sq2(pt1);

sq2.move(20,20);

MyObject obj(pt1, sq2, elp);                        //2.11

getchar();

ShowMyObject(obj);                                  //2.12

{

     Heir hr(pt1);                                  //2.13

     getchar();

     for(int i = 0; i <100; i++){

hr.show(); Sleep(24); hr.hide(); hr.move(0,3);

}

}

Figure::CloseGraphic();                             //2.14

}

 

Рассмотрим функцию main, в которой создаются объекты классов, определённых выше. В строках с 2.1 по 2.2 создаётся и инициализируется массив pt1 элементов типа POINT, который будет использован в качестве фактического параметра в конструкторах объектов класса Square. В строке 2.3 вызывается статическая функция, которая инициализирует графику.

Строки, начиная с 2.4 по 2.10 Заключены в блок. Это сделано для того, чтобы изучить последовательность вызовов конструкторов при создании объектов класса Square, ClsEllipse в строке 2.5 и деструкторов при выходе из блока. 

В строке 2.5 создаются два объекта sq1, и elp типа Square и ClsEllipse. Созданием объектов занимаются конструкторы (строки 1.9 и 1.15). Поскольку у конструктора Square(POINT* p) один параметр, то он является одновременно и конструктором преобразования типа. Именно в таком качестве он отрабатывает в строке 2.11 для первого фактического параметра pt1 типа POINT при создании объекта obj. Рассмотрим подробно на примере объекта sq1 процесс его формирования. При выполнении оператора Square sq1(pt1), как уже говорилось выше, вызывается конструктор:

Square(POINT* p)/*Точка вызова конструктора по умолчанию Figure() базового класса*/ {for(int i =0; i <5; i++){pt[i].x = p[i].x;pt[i].y = p[i].y;}},

которому в качестве параметра передаётся указатель на массив элементов типа POINT.

Прежде чем начнут выполняться операторы тела конструктора Square, произойдёт вызов конструктора по умолчанию базового класса Figure() (строка 1.2). Таким образом, объект конструируется снизу-вверх, т.е. сначала вызывается конструктор самого нижнего (базового класса), потом поднимаясь выше по иерархии, конструктор следующего класса и т.д. Последним начнёт выполняться конструктор класса создаваемого объекта. Как видно из определения конструктора Square конструктор для базового класса Figure вызывается неявно. Это можно сделать и явно с помощью списка инициализации. В этом случае конструктор Square будет выглядеть следующим образом

Square(POINT* p): Figure() {for(int i =0; i <5; i++){pt[i].x = p[i].x;pt[i].y = p[i].y;}}

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

MyObject(const Square& p1,const Square& p2,ClsEllipse& el):sq1(p1),sq2(p2), elp(el){}

В списке инициализации вызывает конструкторы с параметрами (не по умолчанию) для объектов, которые являются его составными частями. Если бы список инициализации отсутствовал, то код конструктора выглядел бы следующим образом:

 

MyObject(const Square& p1,const Square& p2,ClsEllipse& el)

/*Неявный вызов конструкторов по умолчанию для создания объектов sq1, sq2, elp */

{

/*Поскольку нас не устраивает те значения, которые получат объекты sq1, sq2 и elp при вызове конструкторов по умолчанию, то мы вынуждены написать следующий код */  

 for(int i =0; i <5; i++){ sq1.pt[i] = p1[i]; sq2.pt[i] = p2[i];}

 elp.pt1= el.pt1; elp.pt2= el.pt2;

/*Фактически нам пришлось повторить тело конструкторов с параметрами для объектов sq1, sq2, elp*/

}

Ещё один вариант списка инициализации можно увидеть в конструкторе класса Heir

Heir(POINT *p):Square(p),ClsEllipse(){/*cout<<"\t Heir()";*/ }

Как мы видим, в нём вызываются конструктор и конструктор по умолчанию для заполнения полей данных, унаследованных Heir от классов Square, ClsEllipse. Причём конструктор по умолчанию для ClsEllipse() явно можно было и не вызывать, он вызвался бы и не явно самостоятельно.

С помощью строк программы с 2.6 по 2.9 объекты sq1 и elp плавно передвигаются по экрану монитора. Это организовано с помощью цикла for, в котором последовательно вызываются функции show() для отображения объектов, sleep(24) для задержки выполнения программы на 24мс, hide() для стирания объектов с экрана и move(2,2) для изменения координат объектов, и затем снова вывод на экран на следующей итерации цикла for.

В строке 2.10 мы входим из блока, следовательно, локальные объекты sq1 и elp должны быть уничтожены. Эту операцию выполняют деструкторы. Причём при использовании наследования последовательность вызовов деструкторов противоположна последовательности вызова конструкторов, т.е. сначала вызывается деструктор для производного класса, а потом для его базового класса и аналогично ниже по цепочке иерархии.

Таким образом, для нашего примера в строке 2.10 будет вызван сначала деструктор ~Square(), а затем ~Figure() для уничтожения объекта sq1. Аналогично для уничтожения объекта elp будут вызваны деструкторы ~ClsEllipse(), а затем ~Figure().

В любом классе могут быть в качестве компонентов определены другие классы. В этих классах будут свои деструкторы, которые при уничтожении объекта охватывающего (внешнего) класса выполняются после деструктора охватывающего класса. Например, в строке 2.11 создаётся объект obj типа MyObject, процесс создания которого с помощью вызовов конструкторов мы уже описали. При выходе из тела функции main, локальный объект obj должен быть уничтожен. Для этого неявно вызываются деструктор ~MyObject(), а затем для уничтожения агрегированных в него по значению объектов sq1, sq2 деструкторы ~Square()->~Figure(),~Square()->~Figure(). Для ссылки elp деструктор не вызывается, так как ссылка объектом не является.

Вызовы деструкторов для объектов класса и для базовых классов выполняются неявно и не требуют никаких действий программиста. Однако вызов деструктора того класса, объект которого уничтожается в соответствии с логикой выполнения программы, может быть явным. Например, класс MyObject в своём деструкторе мог бы через ссылку elp на класс ClsEllipse, явно уничтожить объект этого класса. Это выглядело бы так: ~MyObject(){elp.~ ClsEllipse();} Обычно этого не делают, так как, если это сделать, то встаёт вопрос, зачем связывали классы MyObject с ClsEllipse через ссылку elp, а не по значению. 

Ещё один интересным фрагментом программы, является вызов функции ShowMyObject(obj); в строке 2.12. Поскольку из определения функции (строка 1.33) видно, что данные в функцию передаются по значению, то, следовательно, для объекта obj будет вызван конструктор копии, который в свою очередь вызовет конструкторы копии для агрегированных по значению в класс MyObject объектов sq1, sq2 (строка 1.20) класса Square. Для агрегированного с MyObject по ссылке elp класса ClsEllipse (строка 1.21) конструктор копии естественно вызываться не будет.

 





Поделиться с друзьями:


Дата добавления: 2018-10-15; Мы поможем в написании ваших работ!; просмотров: 238 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Велико ли, мало ли дело, его надо делать. © Неизвестно
==> читать все изречения...

4533 - | 4114 -


© 2015-2026 lektsii.org - Контакты - Последнее добавление

Ген: 0.013 с.