- •Наследование.
- •Производный класс
- •В файле student2.H
- •В файле student2.H
- •В файле student2.H
- •В файле student2.Cpp
- •Повторное использование кода: класс двоичного дерева
- •В файле gentree2.H
- •В файле gentree2.Cpp
- •Виртуальные функции
- •В файле virt_sel.Cpp
- •В файле virt_err.Cpp
- •В файле shape2.Cpp
- •Virtual double area () const {return 0;} //площадь
- •Абстрактные базовые классы
- •В файле predator.Cpp
- •Шаблоны и наследование
- •Порядок выполнения конструкторов
- •Наследование и проектирование
- •Форма подтипов
- •Идентификация типа на этапе выполнения
- •В файле typied.Cpp
В файле gentree2.H
//Обобщенные деревья двоичного поиска
typedef void* p_gen; //тип обобщенного показателя
int comp (p_gen a, p_gen b);
class bnode { //узел
private:
friend class gen_tree;
bnod* left;
bnod* right;
p_gen data;
int count;
bnod(p_gen d, bnod* l, bnod* r) :
data(d), left(l), right(r), count (1) {}
friend void print(bnod* n);
};
class gen_tree { //дерево
public:
gen_tree() { root = 0; }
void insert(p_gen d);
p_gen find(p_gen d) const { return (find(root, d)); }
void print () const { print(root); }
protected:
bnode* root; //корень
p_gen find(bnode*r, p_gen d) const;
void print(bnode*r) const;
};
Отдельные узлы в этом двоичном дереве хранят обобщенный указатель data и целую count, которая будет подсчитывать повторяющиеся вхождения. Указатель data будет соответствовать типу указателя в производном классе. Дерево будет представлять собой дерево двоичного поиска, в котором узлы с меньшем значением будут храниться слева, а с большим значением – слева. Нам нужен способ для сравнения значений, подходящий для конкретного производного типа. Мы используем дружественную функцию comp(), которая является другом bnode и будет написана надлежащим образом для производного класса.
Функция insert() помещает узлы в дерево; она должна находить в дереве позицию для очередного узла.
void gen_tree::insert(p_gen d)
{
bnode* temp = root;
bnode* old;
//template<class T> void gen_tree<T>::insert(T>::insert(T d)
}
Функция p_gen find(bnode*r, p_gen d) ищет в поддереве к корнем r информацию, представленную d.
p_gen gen_tree::find(bnode*r, p_gen d) const
{
...
}
Функция print() - это стандартная рекурсия. В каждом узле применяется внешняя функция ::print().
void gen_tree:: print(bnod* r) const
{
if (r != 0) {
print(r ->left);
::print r;
print(r ->right);
}
}
Теперь создадим производный класс, который в качестве членов данных сможет хранить указатели на char.
В файле gentree2.Cpp
#include "gentree2.h"
#include <cstring> //для старых систем: string.h
using namespace std;
class s_tree : privat gen_tree { //дерево строк
public:
s_tree() {}
void insert (char* d) { gen_tree::insert(d); }
char* find(char* d) const
{ return static_cast< char*>(gen_tree::find(d));}
void print() const { gen_tree:: print(); }
};
Функция вставки gen_tree::insert из базового класса принимает в качестве аргумента обобщенный указатель. Функция вставки s_tree::insert производного класса принимает в качестве аргумента указатель на char. Таким образом, в производном классе s_tree функция
void insert (char* d) { gen_tree::insert(d); }
использует неявное преобразование сhar* k void*.
Нам нужна функция, выполняющая сравнение:
int comp(p_gen i, p_gen j)
{
return (strcmp(static_cast<char*>(j) );
}
Кроме того, нужна внешняя функция print() для рекурсивного использования s_tree::print() в выводящей дерево целиком; она должна уметь правильно печатать значения, хранящиеся в отдельном узле.
void print(bnode* n)
{
cout << static_cast<char*>(n -> data) <<’\t’;
cout << n -> count << ‘\t’;
}
Заметьте, что там, где соответствующий шаблонный код использовал шаблонный параметр, в этой версии программы для gen_tree применяется тип обобщенного указателя p_gen.
Методика шаблонов проще и более эффективна на этапе выполнения. Она проще, потому что для инстанцирования необходим единственный фактический тип, помещаемый в объявление шаблона. При наследовании же необходимо наследовать весь интерфейс, подставляя надлежащие типы. Методика шаблонов более эффективна на этапе выполнения, потому что часто можно избежать ненужных «косвенностей». С другой стороны, наследование позволяет, если необходимо, разработать специфический код для каждого типа. Наследование не приводит к большим модулям объектного кода. Помните, что каждое инстанцирование шаблона компилируется в объектный код.