Лекция
Привет, сегодня поговорим про включение классов, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое включение классов, наследование классов , настоятельно рекомендую прочитать все из категории С++ (C plus plus).
Пусть класс В есть производный класс от класса D.
class B{...};
class D:public B{...};
Слово public в заголовке класса D говорит об открытом наследовании. Открытое наследование означает что производный класс D является подтипом класса В, т.е. объект D является и объектом В. Такое наследование является отношением is-a или говорят, что D есть разновидность В. Иногда его называют также интерфейсным наследованием. При открытом наследовании переменная производного класса может рассматривается как переменная типа базового класса. Указатель, тип которого - “указатель на базовый класс”, может указывать на объекты, имеющие тип производного класса. Используя наследование мы строим иерархию классов.
Рассмотрим следующую иерархию классов.
class person{
protected:
char* name;
int age;
public:
person(char*,int);
virtual void show() const;
//...
};
class employee:public person{
protected:
int work;
public:
employee)char*,int,int);
void show() const;
//...
};
class teacher:public employee{
protected:
int teacher_work;
public:
teacher(char*,int,int,int);
void show() const;
//...
};
Определим указатели на объекты этих классов.
person* pp;
teacher* pt;
Создадим объекты этих классов.
person a(“Петров”,25);
employee b(“Королев”,30.10);
pt=new teacher(“Тимофеев”,45.23,15);
Просмотрим эти объекты.
pp=&a;
pp->show(); //вызывает person::show для объекта а
pp=&b;
pp->show(); // вызывает employee::show для объекта b
pp=pt;
pp->show(); // вызывает teacher::show для объекта *pt
Здесь указатель базового класса pp указывает на объекты производных классов employee, teacher, т.е. он совместим по присваиванию с указателями на объекты этих классов. При вызове функции show с помощью указателя pp, вызывается функция show того класса, на объект которого фактически указывает pp. это достигается за счет объявления функции show виртуальной, в результате чего мы имеем позднее связывание.
Пусть теперь класс D имеет член класса В.
class D{
public:
B b;
//...
};
В свою очередь класс В имеет член класса С.
class В{
public:
С с;
//...
};
Такое включение называют отношением has-a. Используя включение мы строимиерархию объектов.
На практике возникает проблема выбора между наследованием и включением. Рассмотрим классы “Самолет” и “Двигатель”. Новичкам часто приходит в голову сделать “Самолет” производным от “Двигатель”. Об этом говорит сайт https://intellect.icu . Это не верно, поскольку самолет не является двигателем, он имеет двигатель. Один из способов увидеть это- задуматься, может ли самолет иметь несколько двигателей? Поскольку это возможно, нам следует использовать включение, а не наследование.
Рассмотрим следующий пример:
class B{
public:
virtual void f();
void g(); };
class D{
public:
B b;
void f(); };
void h(D* pd){
B* pb;
pb=pd; // #1 Ошибка
pb->g(); // #2 вызывается B::g()
pd->g(); // #3 Ошибка
pd->b.g(); // #4 вызывается B::g()
pb->f(); // #5 вызывается B::f()
pd->f(); // #6 вызывается D::f()
}
Почему в строках #1 и #3 ошибки ?
В строке #1 нет преобразования D* в B*.
В строке #3 D не имеет члена g().
В отличие от открытого наследования, не существует неявного преобразования из класса в один из его членов, и класс, содержащий член другого класса, не замещает виртуальных функций того класса.
Если для класса D использовать открытое наследование
class D:public B{
public:
void f();};
то функция
void h(D* pd){
B* pb=pd;
pb->g(); // вызывается B::g()
pd->g(); // вызывается ::g()
pb->f(); // вызывается D::f()
pd->f(); // вызывается D::f()
}
не содержит ошибок.
Так как D является производным классом от B, то выполняется неявное преобразование из D в B. Следствием является возросшая зависимость между B и D.
Существуют случаи, когда вам нравится наследование, но вы не можете позволить таких преобразований.
Например, мы хотим повторно использовать код базового класса, но не предполагаем рассматривать объекты производного класса как экземпляры базового. Все, что мы хотим от наследования- это повторное использование кода. Решением здесь является закрытое наследование. Закрытое наследование не носит характера отношения подтипов, или отношения is-a. Мы будем называть его отношением like-a(подобный) или наследованием реализации, в противоположность наследованию интерфейса. Закрытое(также как и защищенное) наследование не создает иерархии типов. С точки зрения проектирования закрытое наследование равносильно включению, если не считать вопроса с замещением функций. Важное применение такого подхода- открытое наследование из абстрактного класса, и одновременно закрытое(или защищенное) наследование от конкретного класса для представления реализации.
Пример 2.6.1
Пример 2.6.1
// Бинарное дерево поиска
//Файл tree.h
// Обобщенное дерево
typedef void* Tp; //тип обобщенного указателя
int comp(Tp a,Tp b);
class node{ //узел
private:
friend class tree;
node* left;
node* right;
Tp data;
int count;
node(Tp d,Tp* l,Tp*r):data(d),left(l),right(r),count(1){}
friend void print(node* n);
};
class tree{//дерево
public:
tree(){root=0;}
void insert(Tp d);
Tp find(Tp d) const{return(find(root,d));}
void print() const{print(root);}
protected:
node* root; //корень
Tp find(node* r,Tp d) const;
void print(node* r) const;
};
Узлы двоичного дерева содержат обобщенный указатель на данные data. Он будет соответствовать типу указателя в производном классе. Поле count содержит число повторяющихся вхождений данных. Для конкретного производного класса мы должны написать функцию compдля сравнения значений конкретного производного типа. Функция insert() помещает узлы в дерево.
void tree::insert(TP d)
{node* temp=root;
node* old;
if(root==0){root=new node(d,0,0);return;}
while(temp!=0){
old=temp;
if(comp(temp->data,d)==0){(temp->count)++;return;}
if(comp(temp->data,d)>0)temp=temp->left;
else temp=temp->right;}
it(comp(old->data,d)>0)old->left=new(d,0,0);
else old->right=new node(d,0,0);
}
Функция Tp find(node* r,Tp d) ищет в поддереве с корнем r информацию, представленную d.
Tp tree::find(node* r,Tp d)const
{if(r==0) return 0;
else if(comp(r->data,d)==0)return(r->data);
else if (comp(r->data,d)>0)return(find(r->left,d));
else return(find(r->right,d));
}
Функция print()-стандартная рекурсия для обхода бинарного дерева
void tree::print(node* r) const
{if(r!=0){
print(r->left);
::print(r);
print(r->right);
}
В каждом узле применяется внешняя функция ::print().
Теперь создадим производный класс, который в качестве членов данных хранит указатели на char.
//Файл s_tree.cpp
#include “tree.h”
#include
class s_tree:private tree{
public:
s_tree(){}
void insert(char* d){tree::insert(d);}
char* find(char* d) const {return(char*)tree::find(d));
voif print() const{tree::print();}
};
В классе s_tree функция insert использует неявное преобразование char* к void*.
Функция сравнения comp выглядит следующим образом
int comp(Tp a,Tp b)
{return(strcmp((char*)a,(char*)b));}
Для вывода значений, хранящихся в узле, используется внешняя функция
print(node* n){
cout<<(char*)(n->data)<
cout<cout<
}
Здесь для явного приведения типа void* к char* мы используем операцию приведения типа (имя_типа)выражение. Более надежным является использование оператора static_cast<char*>(Tp)
В общем, мой друг ты одолел чтение этой статьи об включение классов. Работы впереди у тебя будет много. Смело пиши комментарии, развивайся и счастье окажется в твоих руках. Надеюсь, что теперь ты понял что такое включение классов, наследование классов и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории С++ (C plus plus)
Комментарии
Оставить комментарий
С++ (C plus plus)
Термины: С++ (C plus plus)