Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Множественное наследование




У каждого из представленных дочерних классов CLine, CRect и CEllipse имеется один базовый класс CPos. Вместе с тем язык С++ предоставляет возможность создавать дочерние классы на основе нескольких базовых, что приводит к концепции множественного наследования.

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

#define MAX_WIDTH 4 #define MAX_COLOR 15 class CProp { protected: CProp() {} CProp(int wdt, int clr) {SetProperty(wdt,clr);} ~CProp(); public: void SetProperty(int wdt, int clr) { if(wdt >= 0 && wdt <= MAX_WIDTH) width = wdt; if(clr >= 0 && clr <= MAX_COLOR) color = clr; } protected: int width, color; };

Теперь дочерние классы CLine, CRect и CEllipse можно образовывать от двух базовых CPos и CProp, которые являются не связанными друг с другом. Для того чтобы построить класс на основе двух базовых они указываются друг за другом через запятую.

class CLine: public CPos, public CProp { public: CLine(): CPos(), CProp() {} CLine(int x1, int y1, int x2, int y2, int w, int clr): CPos(x1, y1, x2, y2), CProp(wdt, clr) {} ~CLine() {} void Draw() { printf("Рисование линии (%d, %d): (%d, %d),", X1, Y1, X2, Y2); printf(" толщина линии %d, цвет %d\n", width, color); } };

Конструктор CLine(int, int, int, int, int, int) класса CLine вызывает конструкторы двух базовых классов, которые перечислены через запятую с указанием в них конкретных переменных. Работа с функциями класса CLine через его представитель имеет следующий вид:

CLine line; line.SetProperty(1,0); // вызов метода абстрактного класса CProp line.SetParam(10,10,20,20); // вызов метода абстрактного класса CPos line.Draw();

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

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

Для объявления дружественного класса используется ключевое слово friend, за которым следует имя класса. Следующий пример демонстрирует объявление дружественного класса CRect классам CPos и CProp:

class CRect; // прототип описания (идентификатор) класса class CPos { protected: CPos() {} CPos(int x1, int y1, int x2, int y2) { SetParam(x1,y1,x2,y2); } ~CPos() {} public: friend CRect; //объявление дружественного класса void SetParam(int x1, int y1, int x2, int y2); private: int X1, Y1; int X2, Y2; }; class CProp { protected: CProp() {} CProp(int wdt, int clr) {SetProperty(wdt,clr);} ~CProp(); public: friend CRect; //объявление дружественного класса void SetProperty(int wdt, int clr); private: int width, color; };

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

В результате использования спецификатора friend частные элементы X1, Y1, X2, Y2, width и color классов CPos и CProp оказываются доступными только одному производному классу CRect и никакому другому, что обеспечивает их лучшую защиту по сравнению с уровнем доступа protected.

Дружественными можно объявлять не только классы, но и отдельные функции классов. Рассмотрим пример, в котором для класса CEllipse свойства X1, Y1, X2, Y2, width и color будут доступны только дружественной функции Draw().

class CPos; // идентификатор класса class CEllipse { public: CEllipse () {} ~CEllipse () {} void Draw(CPos* pos); }; class CPos { public: CPos() {} ~CPos() {} friend void CEllipse::Draw(CPos* pos); //объявление дружественного метода private: int X1, Y1; int X2, Y2; }; void CEllipse::Draw(CPos* pos) { printf("Рисование эллипса (%d, %d):(%d, %d)", pos->X1, pos->Y1, pos->X2, pos->Y2); printf(" толщина линии %d, цвет %d\n", width, color);}

Благодаря тому, что функция Draw() является дружественной классу CPos, она может получать доступ к частным элементам этого класса через переданный ей указатель на объект класса CPos.

Виртуальные функции

Функция-член класса может содержать спецификатор virtual. Такая функция называется виртуальной.

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

//абстрактный базовый класс «Животные» class Animal { public: char *Title; //кличка животного Animal(char *t) { Title=t; } //простой конструктор virtual void speak(void)=0; //чистая виртуальная функция }; //класс лягушка class Frog:public Animal { public: Frog(char *Title): Animal(Title) {}; virtual void speak(void) { cout<<Title<<" говорит "<<"ква-ква"<<endl; }; }; //класс собака class Dog:public Animal { public: Dog(char *Title): Animal(Title) {}; virtual void speak(void) { cout<<Title<<" говорит "<<"гав-гав"<<endl;}; }; //класс кошка class Cat:public Animal { public: Cat(char *Title): Animal(Title) { }; virtual void speak(void) { cout<<Title<<" говорит "<<"мяу-мяу"<<endl;}; }; //класс лев class Lion: public Cat { public: Lion(char *Title): Cat(Title) {}; virtual void speak(void) { cout<<Title<<" говорит "<<"ррр-ррр"<<endl;}; // virtual int speak(void) // { cout<<Title<<" говорит "<<"ррр-ррр"<<endl; return 0; }; // virtual void speak(int Х) // { cout<<Title<<" говорит "<<"ооа-ооу"<<endl; }; };

В качестве базового класса создан абстрактный класс Animal. Он имеет единственное свойство Title, описывающий кличку животного. В нем есть единственная чистая виртуальная функция speak(), которая описывает, какие звуки издает животное. Из этого класса выведены все остальные. Кроме одного. Класс Lion порожден от класса Cat (ведь львы это тоже кошки). Это сделано для демонстрации тонкостей применения виртуальных функций.

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

Объявим в основном теле программы массив animals[4] указателей типа Animal*. И сразу же создадим динамические объекты классов, которыми заполним массив указателей. А в цикле for() по указателю будем просто вызывать виртуальную функцию speak() для экземпляров класса.

int main () { //объявим массив указателей на базовый класс Animal //и сразу его заполним указателями, создавая объекты Animal *animals[4] = { new Dog("Бобик"), new Cat("Мурка"), new Frog("Кермит"), new Lion("Кинг") }; // cписок животных for(int k=0; k<4; k++) animals[k]->speak(); return 0; }

В описании класса Lion содержится сразу три виртуальной функции speak(), правда две из них имеют типичные ошибки и закомментированы. Рассмотрим их.

Во второй функции speak() класса Lion производится попытка соорудить виртуальную замещающую функцию с другим типом возвращаемого значения (функция возвращает тип int вместо типа void, который был у функции speak() в базовом классе). Компилятор выдаст сообщение об ошибке:

Error: animals.cpp(42,25):Virtual function 'Lion::speak()' conflicts with base class 'Cat'.

Третья (ошибочная) функции speak() класса Lion не определяется компилятором как ошибка, на сей раз он выдаст предупреждение (и программа будет работать!). Это тот самый случай, когда объявляется замещающая виртуальная функция с тем же самым типом возвращаемого значения, но с другим набором параметров. И, раз в данном классе нет идентично определенной виртуальной функции, то по указателю вызывается виртуальная функция speak() из базового класса Cat.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...