Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Наследование. Абстрактные классы. Виртуальные функции





Наследование. Абстрактные классы. Виртуальные функции

Контейнерные классы

Контейнерные классы – это классы, которые содержат в своем описании один или несколько объектов или указатели на объекты. В этом случае имеет место отношение «содержит».

//f1.h

#include <iostream.h>

class Tail

{int length;

public:

Tail(int n) {length=n;}

int GetTail() {return length;}

};

class Dog

{Tail tail;

public:

Dog(int n):tail(n) {};

void DisplayPar() {cout<<tail.GetTail()<<endl; return;}

};

//f1.cpp

#include "f1.h"

void main()

{ Dog d(20);

d.DisplayPar();

}

Сначала инициализируются все поля–объекты, которые содержатся в описании класса, причем в том порядке, в котором они объявлены. Деструкторы вызываются в порядке, обратном инициализации.

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

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

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

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

Простое наследование – это такое наследование, при котором порождаемые классы наследуют методы и свойства одного базового класса.

Производный класс А является базовым для класса Б.

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

Не существует ограничений на количество производных классов.

#include <iostream.h>

class TBase

{private:

int count;

public:

TBase() {count=0;}

int GetCount() {return count;}

void SetCount(int n) {count =n;}

};

class TDerived:public TBase

{public:

TDerived():TBase() {};

void incr(int n) {SetCount(GetCount()+n);} //в этой функции нельзя написать строку count+=n; так как такая строка вызовет ошибку компиляции, потому что у производного класса нет прав доступа к переменным и функциям базового класса с уровнем доступа private

};

 

void main()

{TDerived d;

d.SetCount(15);

d.incr(10);

cout<<d.GetCount(); //на экране будет значение 25;

}

 

Спецификаторы доступа базовых классов

Когда класс Second порождается от First со спецификатором прав доступа private, например:

class First {…};

class Second:First {…};

или

class First {…};

class Second: private First {…};

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

Когда класс Second порождается от First со спецификатором прав доступа protected, например:

class First {…};

class Second: protected First {…};

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

Когда класс Second порождается от First со спецификатором прав доступа public, например:

class First {…};

class Second: public First {…};

то все общедоступные имена базового класса будут общедоступными, а все защищенные будут защищенными в производном классе.

 

Порядок вызова конструкторов

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

Например,

class First {…};

class Second: public First {…};

class Third: public Second {…};

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

First::First()

Second::Second()

Third::Third()

 

Порядок вызова деструкторов

Деструкторы для производных классов вызываются в порядке обратном вызову конструкторов. Таким образом, порядок вызовов деструкторов, сгенерированных для разрушения экземпляра класса Third, будет следующим:

Third::~Third()

Second::~Second()

First::~First()

 

Теперь вызов функции B.fin1() приведет к вызову функции fun() класса A.

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

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

Виртуальные функции – функции, вызов которых зависит от типа объектов. С помощью виртуальных функций объект определяет свои действия.

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

A* aobject; A

­

B* bobject; B

­

C* cobject; C

cobject=&аobject // так нельзя делать

aobject=&cobject.

Пример:

//f1.h

#include <iostream.h>

class Animal

{public:

/* virtual */ char* speak() {return "";}

};

class Dog: public Animal

{public:

char * speak() {return "Gav!!!";}

};

//f1.cpp

#include "f1.h"

void sound (Animal& i)

{cout<<i.speak()<<endl;}

void main ()

{ Dog Sharic;

sound(Sharic); //”” (на экран будет выведена пустая строка)

}

Решение проблемы – позднее связывание. Функцию speak() класса Animal достаточно объявить виртуальной, после чего компилятор запустит механизмы позднего связывания.

//f1.h

#include <iostream.h>

#include <string.h>

class Animal

{

protected:

char *pname;

public:

Animal(char *AnName)

{

pname= new char [strlen(AnName)+1];

strcpy(pname, AnName);

}

virtual char * speak() { return "";}

virtual char *name() { return pname;}

};

 

class Dog: public Animal

{

public:

Dog(char *name):Animal(name) {}

char * speak()

{

char * phrase;

phrase=strdup(pname); //дублирует строку, при этом вызывая функцию malloc()

return strcat(phrase," Say Gav! ");

}

virtual char *sit()

{

char *phrase; phrase=strdup(pname); return strcat(phrase," sits");

}

};

//f1.cpp

#include "f1.h"

void main ()

{

Animal* p[2]={ new Animal("a"), new Dog("Sharic")};

cout<<p[0]->speak()<<endl; //выведет на экран пустую строку

cout<<p[1]->speak()<<endl; //выведет на экран строку “Sharic Say Gav!”

//cout<<p[1]->sit()<<endl; //ошибка компиляции: 'sit': is not a member of 'Animal'

cout<<((Dog*)p[1])->sit()<<endl;

}

Абстрактные базовые классы

Класс становится абстрактным, когда в него добавляется чистая виртуальная функция:

virtual void f() = 0;

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

//f1.cpp

#include <iostream.h>

#include <string.h>

class Animal

{public:

virtual char* speak() =0;

virtual char *eat() =0;

};

char* Animal::speak()

{return "Animal::speak()";}

char* Animal::eat()

{return "Animal::eat()";}

class Dog: public Animal

{public:

char *speak() {return Animal::speak();}

char *eat() {return Animal::eat();}

};

//f1.cpp

#include "f1.h"

void main()

{ Dog Sharik;

cout<<Sharik.speak()<<endl;

cout<<Sharik.eat()<<endl;

 

}

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

Виртуальные деструкторы

В отличие от конструкторов деструкторы могут быть виртуальными.

//f1.h

include <iostream.h>

class Base1

{public:

~Base1(){cout<<"~Base1()"<<endl;}

};

class Derived1: public Base1

{public:

~Derived1(){cout<<"~Direved1()"<<endl;}

};

class Base2

{public:

virtual ~Base2(){cout<<"~Base2()"<<endl;}

};

class Derived2: public Base2

{public:

~Derived2(){cout<<"~Direved2()";}

};

//f1.cpp

#include "f1.h"

void main()

{

Base1* bp1=new Derived1;

delete bp1; //~Base1()

Base2* bp2=new Derived2;

delete bp2; //~Derived2()~Base2()

}

Деструкторы вызываются в порядке обратном вызову конструкторов.

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

Множественное наследование описывает родство между классами, при котором один класс может иметь несколько базовых.

class A {…};

class B {…};

class C {…};

class D: public A, public B, public C {…};

Вызов конструкторов происходит в том порядке, в котором были объявлены базовые классы:

A::A()

B::B()

C::C()

D::D()

//f1.h

Class A

{public:

void Display(void) {…}

};

Class B

{public:

void Display(void) {…}

};

 

class C: public A, public B

{public:

void f(void);

};

Если имеем реализацию:

void C::f(void)

{Display();}

то компилятор не будет знать, какую функцию Display() вызывать. Нужно указывать явно.

void C::f(void)

{ A::Display();

B::Display();

}

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

Class C: public A, public B

{public:

C():A(), B() {};

void f(void);

};

Если через объект класса C попытаться обратиться к методам класса W, то компилятор выдаст ошибку. Выходом из этой проблемы будет использование виртуального базового класса

//f1.h

#include <iostream.h>

class W

{public:

virtual void f() {cout<<"W::f()"<<endl;}

virtual void g() {cout<<"W::g()"<<endl;}

virtual void h() {cout<<"W::h()"<<endl;}

};

class A:public virtual W

{public:

void g() {cout<<"A::g()"<<endl;}

};

class B:public virtual W

{public:

void f() {cout<<"B::F()"<<endl;}

};

class C: public A, public B

{public:

void f() {cout<<"C::f()"<<endl;}

};

//f1.cpp

#include "f1.h"

void main()

{ C* pc = new C;

pc->f(); //C::f()

pc->g(); //A::g()

pc->h(); //W::h()

((A*)pc)->f(); //C::f(). pc – указатель на C, насильственно преобразуем его к указателю на A; в описании класса A функции f нет, она описана в базовом классе W, но как виртуальная; далее происходит анализ класса, на который указывает pc, то есть класса C, в нем обнаруживается реализация функции f(), вызов которой и происходит. Если слово virtual перед f() в W убрать, тогда будет вызвана f из W.

((W*)pc)->f(); //C::f()

B* pb = new B;

pb->f(); //B::f()

pb->g(); //W::g();

pb->h(); //W::h()

((W*) pb)->f(); //B:f()

A* pa = new A;

pa->f(); //W::f()

pa->g(); //A::g()

pa->h(); //W::h()

((W*) pa)->g(); //A::g()

}

Для описания иерархий множественного наследования используется прямой ациклический граф. Виртуальные базовые классы инициализируются перед любыми невиртуальными базовыми классами в том порядке, в котором они появляются в прямом ациклическом графе наследования при просмотре его снизу вверх и слева направо. Для приведенного выше примера порядок вызова конструкторов следующий: W(), A(), B(), C().

class A {};

class B: public virtual A {};

class C: public virtual A {};

class D: public A {};

class E: public B, public C, public D {};

Дерево наследования приведенной выше иерархии изображено на рисунке ниже.

Порядок вызова конструкторов для класса E таков: A(), B(), C(), D(), E().

 

Ключ доступа Спецификатор базового доступа Доступ в производном классе Комментарий
private private protected public private private private все private
protected private protected public private protected protected все protected, кроме private
public private protected public private protected public не изменяется

 


ЧТО НЕОБХОДИМО ЗНАТЬ

1. Наследование представляет собой способность производить новый класс из существующего базового класса.

2. Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса.

3. Для порождения класса из базового начинайте определение производного класса ключевым словом class, за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian: dog.

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

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

6. Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время защищая эти элементы от оставшейся части программы, C++ обеспечивает защищенные (protected) элементы класса. Производный класс может обращаться к защищенным элементам базового класса, как будто они являются общими. Однако для оставшейся части программы защищенные элементы эквивалентны частным.

7. Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:: member.

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

9. В базовом для полиморфного объекта классе вы должны определить одну или несколько функций как виртуальные (virtual).

10. В общем случае полиморфные объекты отличаются использованием виртуальных функций базового класса.

11. Для создания полиморфного объекта вам необходимо создать указатель на объект базового класса.

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

13. Чисто виртуальная функция — это виртуальная функция базового класса, для которой в базовом классе не определены операторы. Вместо них базовый класс присваивает такой функции значение 0.

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

15. Множественное наследование является способностью порожденного класса наследовать характеристики нескольких базовых классов.

16. Для порождения класса из нескольких базовых после имени нового класса и двоеточия вы указываете имена базовых классов, разделяя их запятыми, например class cabbit: public cat, public rabbit.

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


Задания

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

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

3. Разработать программу с использованием наследования классов, реализующую классы: работник больницы; медсестра; хирург. Используя виртуальные функции, не зная с объектом какого класса вы работаете, выведите на экран возраст и название должности.

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

5. Создать базовый класс «треугольник», описав в нём функции задания параметров треугольника, вывода их на экран, нахождения периметра и площади. Породить от него класс «прямоугольный треугольник», переопределив функции задания параметров и нахождения площади. Треугольник задается длинами сторон. При вводе параметров обязательно обеспечить проверку на существование треугольника. При задании параметров для объекта класса «прямоугольный треугольник» проверить, действительно ли задан прямоугольный треугольник.

6. Создать базовый класс «многоугольник», описав в нём функции задания количества и длин сторон, вывода параметров многоугольника на экран, нахождения периметра. Породить от него класс «правильный многоугольник» с числом вершин 3<=N<=6, переопределив в нём функции задания количества и длин сторон и нахождения периметра.

7. Создать базовый класс «прямоугольник», определив в нем функции ввода параметров прямоугольника, вывода их на экран и нахождения площади. Породить от него класс «квадрат», переопределив функции ввода параметров и нахождения площади.

Для предложенных тем выбрать 2-3 поля данных и 2-3 виртуальных метода для отображения и изменения значений полей:

Наследование. Абстрактные классы. Виртуальные функции

Контейнерные классы

Контейнерные классы – это классы, которые содержат в своем описании один или несколько объектов или указатели на объекты. В этом случае имеет место отношение «содержит».

//f1.h

#include <iostream.h>

class Tail

{int length;

public:

Tail(int n) {length=n;}

int GetTail() {return length;}

};

class Dog

{Tail tail;

public:

Dog(int n):tail(n) {};

void DisplayPar() {cout<<tail.GetTail()<<endl; return;}

};

//f1.cpp

#include "f1.h"

void main()

{ Dog d(20);

d.DisplayPar();

}

Сначала инициализируются все поля–объекты, которые содержатся в описании класса, причем в том порядке, в котором они объявлены. Деструкторы вызываются в порядке, обратном инициализации.

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

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

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

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

Простое наследование – это такое наследование, при котором порождаемые классы наследуют методы и свойства одного базового класса.

Производный класс А является базовым для класса Б.

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

Не существует ограничений на количество производных классов.

#include <iostream.h>

class TBase

{private:

int count;

public:

TBase() {count=0;}

int GetCount() {return count;}

void SetCount(int n) {count =n;}

};

class TDerived:public TBase

{public:

TDerived():TBase() {};

void incr(int n) {SetCount(GetCount()+n);} //в этой функции нельзя написать строку count+=n; так как такая строка вызовет ошибку компиляции, потому что у производного класса нет прав доступа к переменным и функциям базового класса с уровнем доступа private

};

 

void main()

{TDerived d;

d.SetCount(15);

d.incr(10);

cout<<d.GetCount(); //на экране будет значение 25;

}

 







ЧТО И КАК ПИСАЛИ О МОДЕ В ЖУРНАЛАХ НАЧАЛА XX ВЕКА Первый номер журнала «Аполлон» за 1909 г. начинался, по сути, с программного заявления редакции журнала...

ЧТО ТАКОЕ УВЕРЕННОЕ ПОВЕДЕНИЕ В МЕЖЛИЧНОСТНЫХ ОТНОШЕНИЯХ? Исторически существует три основных модели различий, существующих между...

Живите по правилу: МАЛО ЛИ ЧТО НА СВЕТЕ СУЩЕСТВУЕТ? Я неслучайно подчеркиваю, что место в голове ограничено, а информации вокруг много, и что ваше право...

Конфликты в семейной жизни. Как это изменить? Редкий брак и взаимоотношения существуют без конфликтов и напряженности. Через это проходят все...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.