Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций ООПро.doc
Скачиваний:
43
Добавлен:
13.04.2015
Размер:
431.62 Кб
Скачать

Абстрактные классы

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

class figure{

public:

void move_to(int x, int y){_x=x; _y=y;}

virtual void draw(HDC);

virtual void rotate(double);//angle measured in degrees

protected:

figure(int x,int y):_x(x),_y(y){}

int _x,_y;

};

Производные от figureклассы переопределяют виртуальные функции и добавляют специфические члены данных.

class circle:public figure{

public:

circle(int x,int y,int r)

:figure(x,y),_r(r){}

virtual void draw(HDC);

virtual void rotate(double){};

private:

int _r;

};

class line:public figure{

public:

line(int x0, int y0,

int x1, int y1)

:figure(x0,y0),_dx(x1-x0),_dy(y1-y0)

{}

virtual void draw(HDC);

void rotate(double);

private:

int _dx,_dy;

};

Для figureневозможно разумно определить виртуальные функции:

void figure::draw(HDC){

assert(false);//этот код не должен быть вызван

}

void figure::rotate(double){

assert(false);//этот код не должен быть вызван

}

Попытка создания фигуры допустима, но неразумна:

figure f1(200,200);//фигура с центром, но без формы

circle c1(100,100,10);

line l1(120,100,130,105);

figure* mas[2]={&c1,&l1};

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

mas[i]->draw(hdc);

mas[i]->move_to(100,110);

mas[i]->draw(hdc);

mas[i]->move_to(100,140);

mas[i]->rotate(40);

mas[i]->draw(hdc); }

Включение

Виды включения: композиция и агрегация.

Шаблоны

В C++ ключевое словоtemplateиспользуется для обеспеченияпараметрического полиморфизма. Он позволяет применять один и тот же исходный код к разным типам, причем тип является параметром тела кода.

Допустим у нас есть класс stack3, который реализует концепцию стека символов (char).

class stack3

{

public:

stack3();

void push(char c);

char pop();

bool is_empty()const;

bool is_full()const;

private:

enum{ max_len=100};

int top;

char s[max_len];

};

Функции реализованы следующим образом:

stack3::stack3():top(0){}

void stack3::push(char c){

assert(top<max_len);

s[top++]=c;

}

char stack3::pop(){

assert(top>0);

return s[--top];

}

bool stack3::is_empty()const {return top==0;}

bool stack3::is_full()const {return top==max_len;}

Мы можем использовать объекты этого класса следующим образом:

stack3 reverse_order(stack3 s){

stack3 tmp;

while(!s.is_empty()){

tmp.push(s.pop());

}

return tmp;

}

Пример шаблона класса:

template <class T> class stack4

{

public:

stack4(); //конструктор

void push(T c);

T pop();

bool is_empty()const;

bool is_full()const;

private:

enum{ max_len=100};

int top;

T s[max_len];

};

template<class T>stack4<T>::stack4():top(0){}

template<class T>void stack4<T>::push(T c){

assert(top<max_len);

s[top++]=c;

}

template<class T>T stack4<T>::pop(){

assert(top>0);

return s[--top];

}

template<class T>bool stack4<T>::is_empty()const {return top==0;}

template<class T>bool stack4<T>::is_full()const

{return top==max_len;}

Префикс template <class T> указывает, что объявлен шаблон (template), и что в объявлении на местеTбудет указан фактический тип.T – имя типа, а не обязательно класса.

Имя шаблона класса, за которым следует тип, помещенный в угловые скобки <>, является именем класса (определяемого шаблона) и его можно использовать точно так же, как имена других классов:

stack4<char> sc;

stack4<int> si;

stack4<char*> sd;

Без использования шаблонов для реализации этого примера пришлось бы писать три определения классов, а с использованием шаблона – только одно определение шаблона.

Функция, работающая со стеком stack4<double>:

stack4<double> reverse_order(stack4<double> s){

stack4<double> tmp;

while(!s.is_empty()){

tmp.push(s.pop());

}

return tmp;

}

Шаблон функции, работающий с любымстеком:

template <class STACK> STACK reverse_order(STACK s){

STACK tmp;

while(!s.is_empty()){

tmp.push(s.pop());

}

return tmp;

}

Исключение ненужных инстанцирований функций-членов

Следует заметить, что для каждого случая инстанцирования шаблона будет генерироваться свой набор функций-членов класса, например,

stack4<char> sc;//генерируется 5 функций

stack4<int> si1, si2;// генерируется 5 функций

//(количество создаваемых объектов не играет роли)

stack4<char> sd;//не генерируется новых функций, так как инстанцирование stack4<char> уже было выше

В нашем классе stackфункцииpushиpopзависят от параметра шаблона, поэтому их генерирование необходимо. Так, функцияpush, сгенерированная для параметраchar, работает не так, как функцияpush, сгенерированная для параметраint. В то же время функцииis_emptyиis_full от параметра шаблона не зависят. Функцияis_emptyдляcharабсолютно идентична функцииis_emptyдляint. Для того, чтобы избежать генерирования лишних (полностью идентичных) функций, применяют следующий прием. Шаблонный класс создаютпроизводным от класса, не являющегося шаблоном. При этом в базовый класс помещают функции, не зависящие от параметра шаблона.

Применим данный прием для нашего стека:

class stack5_base

{

public:

stack5_base();

bool is_empty()const;

bool is_full()const;

protected:

enum{ max_len=100};

int top;

};

stack5_base::stack5_base()

:top(0)

{}

bool stack5_base::is_empty()const {return top==0;}

bool stack5_base::is_full()const {return top==max_len;}

template <class T> class stack5:public stack5_base

{

public:

void push(T c);

T pop();

private:

T s[max_len];

};

template<class T>void stack5<T>::push(T c){

assert(top<max_len);

s[top++]=c;

}

template<class T>T stack5<T>::pop(){

assert(top>0);

return s[--top];

}

Для нашего случая количество сгенерированных функций уменьшается до минимально необходимого:

//к этому моменту сгенерировано 3 функции для stack5_base

stack5<char> sc;//генерируются 2 функции

stack5<int> si1, si2;// генерируются 2 функции

//(количество создаваемых объектов не играет роли)

stack5<char> sd;//не генерируется новых функций, так как инстанцирование stack4<char> уже было выше

Шаблоны с параметрами-значениями

В шаблонах stack3иstack4для хранения элементов используется массив на 100 элементов. В некоторых случаях указанное значение избыточно, и память расходуется нерационально. В некоторых случаях этого количества недостаточно и тогда шаблоныstack3иstack4не подходят для решения задачи. Как же спроектировать шаблон так, чтобы при необходимости получить заданное количество элементов не нужно было писать новый шаблон? Это можно сделать (по крайней мере) двумя путями.

Первый (уже известный вам) – передавать количество необходимых элементов в конструктор и в конструкторе динамически выделять память (а в деструкторе освобождать). При этом затрачивается время на выделение/освобождение памяти.

Второй путь – определить шаблон с параметром-значением, указывающим количество элементов. Перепишем шаблон stack4с учетом этого. При этом для исключения ненужных инстанцирований функций-членов используем технику наследования, приведенную выше:

class stack6_base

{

public:

stack6_base();

bool is_empty()const;

protected:

int top;

};

stack6_base::stack6_base()

:top(0)

{}

bool stack6_base::is_empty()const {return top==0;}

template <int N> class stack6_n:public stack6_base

{

public:

bool is_full()const;

};

template <int N> bool stack6_n<N>::is_full()const {return top==N;}

template <class T, int N> class stack6:public stack6_n<N>

{

public:

void push(T c);

T pop();

private:

T s[N];

};

template <class T, int N> void stack6<T,N>::push(T c){

assert(top<N);

s[top++]=c;

}

template <class T, int N> T stack6<T,N>::pop(){

assert(top>0);

return s[--top];

}

Инстанцирование шаблона выглядит следующим образом:

stack6<char,100> sc;//стек char на 100 элементов

stack6<int,10> si1, si2;//стек int на 10 элементов

stack6<char,5> sd;//стек char на 5 элементов

Следует заметить, что шаблон функции reverse_order, приведенный выше, будет работать также и с классами, сгенерированными по шаблонуstack6:

reverse_order(sс);