Лекция Принципы ооп. Любая программа набор инструкций процессора. Повышение уровня абстракции программы - polpoz.ru o_O
Главная
Поиск по ключевым словам:
страница 1
Похожие работы
Название работы Кол-во страниц Размер
Архитектура ЭВМ. Лекция 2 1 314.74kb.
Цель программы повышение уровня знаний и профессиональных компетенций... 1 222.82kb.
Рабочей программы дисциплины Теория автоматов Место дисциплины в... 1 13.23kb.
1. Развитие профессионального сообщества, повышение уровня квалификации... 1 183.85kb.
Программа составлена: август 2013г 4 1029.97kb.
Рабочая программа дисциплины «Защита прав человека» 3 441.18kb.
О. П. Киреев «29» августа 2013 г 21 4281.7kb.
Гкц «Пилигрим» по заказу Комитета по культуре Санкт-Петербурга является... 1 52.77kb.
Программа социально экономическогоразвития 6 2010.67kb.
Рабочая программа составлена на основе региональной программы. 1 556.58kb.
Рабочая программа по литературному чтению для базового уровня изучения... 1 220.42kb.
Пробное тестирование по химии за 9-ый класс 1 65.76kb.
1. На доске выписаны n последовательных натуральных чисел 1 46.11kb.

Лекция Принципы ооп. Любая программа набор инструкций процессора. Повышение уровня - страница №1/1

3-й семестр

«Программирование и алгоритмические языки»




  1. ООП на С++


Лекция 1.

Принципы ООП.
Любая программа – набор инструкций процессора.

Повышение уровня абстракции программы:



  • функции;

  • собственные типы данных (typedef тип New_name [размерность]); структуры;

  • объединение в модули описаний типов и функций для их обработки (интрефейс).

Класс – тип данных, определяемый пользователем (поля данных и функции их обработки). Интерфейс класса – заголовки методов класса. Конкретные величины типа «класс» - экземпляры класса, или объекты. Сообщение – запрос на выполнение действия, содержащий набор необходимых параметров (вызов функций). ООП реализует «событийно-управляемая модель», когда данные активны и управляют вызовом того или иного фрагмента кода, программа ожидает действий пользователя (в «директивной модели» код предлагает пользователю делать те или иные действия).

Основные свойства ООП:



  • инкапсуляция;

  • наследование;

  • полиморфизм – возможность использовать в различных классах иерархии одно имя для обозначения сходных по смыслу действий (перегрузка, шаблон).


Описание классов.
Данные класса – поля, функции – методы.
class <имя>{

[private:]

<описание скрытых элементов>

public:

<описание доступных элементов>
};
Поля класса:

  • могут иметь любой тип, кроме типа этого же класса;

  • могут быть описаны с модификатором const (инициализируется через конструктор лишь один раз);

  • могут быть описаны с модификатором static.

Классы могут быть глобальными и локальными.



class monstr{

int health,ammo;

public:

monstr(int he=100, int am=10)

{health = he;

ammo = am;}

;

void draw( int x, int y, int scale, int position);

int get_health()

{return health;}

int get_ammo()

{return ammo;}


};
Метод, тело которого определено внутри класса, - встроенный.

Операция доступа к области видимости – « :: » .


void monstr::draw(int x, int y, int scale, int position)

{/*тело метода*/}
Метод может быть определен как встроенный и вне класса:

inline int monstr:: get_ammo()

{return ammo;}


Объекты класса.
monstr Vasia,

Super(200,300),

stado[100],

*beavis = new monstr (10,20),

&butthead = Vasia;

int n= Vasia.get_ammo();

stado[5].draw(1,2,3,4);

cout << '\n'<get_health() <<

'\n' << butthead.get_health()<< '\n'<
У константного объекта значения полей менять нельзя, к нему можно применять лишь константные методы:
class monstr {

int health,ammo;

public:

monstr_const(int he=100, int am=10)

{health = he;

ammo = am;}

void draw( int x, int y, int scale, int position);

int get_health() const

{return health;}

int get_ammo()

{return ammo;}

};
const monstr Dead(0,0);

cout << '\n'<< Dead.get_health();
Константный метод:

  • объявляется с ключевым словом const после списка параметров;

  • не может изменять значения полей класса;

  • может вызывать только константные методы;

  • может вызываться для любых объектов.

Указатель this



хранит константный указатель на вызвавший функцию объект и используется внутри метода для ссылок на элементы объекта.



class monstr{

int health,ammo;



public:

monstr(int he=100, int am=10);

void draw( int x, int y, int scale, int position);

int get_health()

{return health;}

int get_ammo()

{return ammo;}

monstr &the_best( monstr &M)

{ if (health > M.health)

return *this;

return M;

}

};



monstr::monstr(int he, int am)

{health = he;

ammo = am;



}



void main()

{

monstr Vasia, Super(200,300;

monstr Best = Vasia.the_best(Super);

cout << '\n'<< Best.get_health();

}

This применяется для идентификации поля класса (при описании метода, когда имя поля совпадает с именем формального параметра метода). Иначе можно использовать операцию доступа к области видимости:
class monstr{

int health,ammo;

color skin;

char *name;

public:



void cure(int health, int ammo)

{ this->health +=health;

monstr::ammo += ammo;

}
};



monstr Best = Vasia.the_best(Super);

Best.cure(13, 18);

cout << '\n'<< Best.get_health();

}
Конструкторы
Основные свойства:

  • Не возвращает значение. Нельзя получить указатель на конструктор.

  • Класс может иметь несколько конструкторов с разными параметрами.

  • Конструктор, вызываемый без параметров, наз. конструктором по умолчанию.

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

  • Если не указан ни один конструктор, он создается автоматически.

  • Конструкторы не наследуются.

  • Конструкторы нельзя описывать с модификаторами const, virtual, static.

  • Конструкторы глобальных объектов вызываются до вызова main. Локальные объекты создаются, когда становится активной область их действия.

  • Конструктор вызывается, если в прграмме встретилась какая-либо из конструкций:

Конец первой лекции


  1. имя_класса имя_объекта [(список параметров)] // список параметров не должен быть пустым

  2. имя_класса (список параметров) // список параметров может быть пустым, создается объект без имени

  3. имя_класса имя_объекта = выражение // создается объект без имени и копируется



#include

#include

#include
enum color{red, green, blue};

class monstr{

int health,ammo;

color skin;

char *name;

public:

monstr(int he=100, int am=10);

monstr(color sk);

monstr(char *nm);

void draw( int x, int y, int scale, int position);

int get_health()

{return health;}

int get_ammo()

{return ammo;}
};

monstr::monstr(int he, int am) /* конструктор по умолчанию, т.к. его можно вызывать бех параметров */

{health = he;

ammo = am;

skin = red;

name = 0;

}

monstr::monstr(color sk)

{switch (sk)

{ case red: health = 100; ammo = 10; skin = red;

name = 0; break;

case green: health = 100; ammo = 20; skin = green;

name = 0; break;

case blue: health = 100; ammo = 40; skin = blue;

name = 0; break;

}

}

monstr::monstr(char *nm)

{health = 100;

ammo = 10;

skin= red;

name = new char [strlen(nm)+1];

strcpy (name , nm);

}
void monstr::draw(int x, int y, int scale, int position)

{ cout <
void main()

{

monstr Vasia, Super(200,300), stado[100];

cout << '\n'<< Best.get_health();

monstr Green(green);

monstr *m = new monstr("Ork");

}

Можно инициализировать поля в конструкторе с помощью списка инициализаторов, расположенных через запятую между заголовком и телом конструктора:


monstr::monstr(int he, int am): health (he),

ammo(am), skin (red), name (0){}
Конструктор копирования
- это специальный вид конструктора, получающий в качестве параметра указатель на объект этого же класса:
<имя класса>::<имя класса>(const &<имя класса>) {/* тело конструктора*/}
Конструктор вызывается, когда объект копируется копированием существующего:

  • при описании нового объекта с инициализацией други объектом;

  • при передаче объекта в функцию по значению;

  • при возврате объекта из функции.

Если не указано конструктора копирования, он создается автоматически. При этом осуществляется копирование полей, и если класс содержит указатели или ссылки, копирование будет «неправильным» (одна область памяти).


monstr::monstr(const monstr &M)

{health = M.health;

ammo = M.ammo;

skin= M.skin;

if (M.name)

{ name = new char [strlen(M.name)+1];

strcpy (name , M.name);

}

else name = 0;
void main(

{



monstr Vasia(blue);



monstr Super = Vasia; //конструктор копирования

monstr *m = new monstr("Ork");

monstr Green = *m; //конструктор копирования

}
Статические элементы класса
Статические поля – хранят данные общие для всех объектов класса. Они существуют для всех объектов класса в одном экземпляре (не дублируются).

  • Память пот статическое поле выделяется один раз при его инициализации с помощью операции доступа к области действия.

class A{

public:

static int count;



};



int A::count; // инициализируется нулем



// int A::count = 10; // инициализируется другим образом


  • Статические поля доступны как через поля класса, так и через имя объекта:

A *a, b;



cout<< A::count << a->count << b.count;



  • Статические поля, описанные как private, можно изменить лишь с помощью статических методов.

  • Память, занимая статическим полем не учитывается при определении размера объекта с помощью sizeof .


Статические методы – обрабатывают статические поля класса. Они обращаются непосредственно к статическим полям и вызывают только статические методы класса, им не передается указатель this. Обращение к статическим методам осуществляется так же, и к статическим полям
class A{



static int count;

public:

static void inc_count() {count++;}


};



int A::count; // инициализируется нулем

void f()

{

A a;

// a.count ++ - нельзя, поле скрытое

a.inc_count(); //или A::inc_count();

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

Дружественные функции оформляют действия, не представляющие свойств класса, нуждающиеся в доступе к его скрытым полям.

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


  • объявляется внутри класса, к элементам которого нужен доступ, с ключевым словом friend. Параметром передается объект или ссылка на объект;

  • может быть обычной функцией или методом другого ранее определенного класса; на нее не распространяется действие спецификаторов доступа место размещения в классе безразлично;

  • может быть дружественной нескольким классам.



class monstr; //Предварительное объявление класса
class hero{

public:

void kill( monstr &);



};
class monstr{



friend int steal_ammo(monstr &);

friend void hero::kill(monstr &);
};

int steal_ammo(monstr &M)

{return –M.ammo; }

void hero::kill(monstr &M)

{M.health = 0; M.ammo = 0;}
!!!Дружественные функции нарушают принцип инкапсуляции!!!
Конец второй лекции

Если все методы класса должны иметь доступ к скрытым полям другого, весь класс объявляется дружественным:


class hero{



friend class mis;

};
class mis{



void f1();

void f2();
};

Функции f1 и f2 дружественные по отношению к hero имеют доступ ко всем его полям.


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

  • для локальных – при выходе из блока;

  • для глобальных – при выходе из main;

  • для заданных через указатели при использовании delete.

Деструктор начинается с тильды, за которой следует имя класса:



  • не имеет аргументлов и возвращаемого значения;

  • не может быть const/static;

  • не наследуется;

  • может быть виртуальным;

  • если не описан, создается автоматически как пустой.

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


monstr::~monstr()

{delete[] name;

}
void main()

{



monstr *m;



m -> ~monstr();

}

Перегрузка операций

Нельзя перегружать:



  • ?: :: # ## sizeof

Перегрузка операций (функции-операции):



  • сохраняются количество аргументов, приоритеты, правила ассоциации;

  • нельзя использовать для стандартных типов;

  • не могут иметь параметров по умолчанию;

  • не наследуются (за исключением =);

  • не могут быть static ;

  • может определяться как

  1. метод класса (количество параметров на 1 меньше, первым операндом является объект, вызвавший операцию);

  2. дружественная функция класса;

  3. обычная функция.


<тип> operator <знак операции> (<список параметров>) {тело функции}
class Point{

double x, y;

public:

Point operator +(Point&);



};
Point::Point operator +(Point& p)

{return Point(x + p.x, y + p.y};

}
2-й метод
class Point{

double x, y;

public:

friend Point operator +(Point&, Point& );



};
Point operator +(Point& p1, Point& p2) //т.к. функция дружественная,

//:: - не нужно

{return Point(p1.x + p2.x, p1.y + p2.y};

}


Point p1(0,2), p2(-3,5);

Point p3 = p1 + p2; // p1.operator +(p2), operator +(p1,p2)


Если первый операнд имеет базовый тип, то перегрузка возможна лишь в виде внешней функции.




Перегрузка унарных операций
class monstr{



public:

monstr& operator ++(){++ health; return *this;}



};

monstr Vasia;

cout << (++Vasia).get.health();
class monstr{


friend monstr& operator ++(monstr &M);



};


monstr& operator ++(monstr &M){++M.health; return M;}
если функция недружественная, д.б. доступно изменяемое поле
class monstr{


Void change_health(int he) {health=he;}



};


monstr& operator ++(monstr &M){

int h = M.get_health();

h++;

M.change_health(h);

return M;}

Операции постфиксные должны иметь первый параметр типа int, чтобы отличить их от префиксной формы.


class monstr{



monstr operator ++(int){

monstr M = *this;

health++;

return M;}



};

monstr Vasia;

cout << (Vasia++).get.health();

// распечатывается первоначальное здоровье (сохраненное в M), а в //Vasia здоровье увеличивается на 1


class Point{

double x, y;

public:


// префиксный метод
Point& operator ++()

{x++;

y++;

return *this;

}

};

Возврат значения по ссылке. Предотвращен вызов конструктора копирования для создания возвращаемого значения и последующего вызова деструктора.


постфиксный метод
Point operator ++(int)

{Point old = *this;

x++;

y++;

return old;
}



};
Ссылка не подходит, так как необходимо вернуть первоначальное состояние объекта (в old).

Префиксная запись более эффективна.


Конец третьей лекции

=========================================================
Перегрузка бинарных операций
class monstr{



public:

bool operator >(const monstr &M)

{if( health > M.health)

return true;

return false;}



};

Вне класса имеет 2 параметра типа класса.


class monstr{





};

bool operator >(const monstr &M1, const monstr &M2)

{if(M1.get_health() > M2.get_health())

return true;

return false;}

friend monstr& operator ++(monstr &M){++M.health; return M;}



Перегрузка операции присваивания


по умолчанию как поэлементное копирование. Возвращает ссылку на объект, для которого вызывается. Единственный параметр – ссылка на присваиваемый объект.

  1. Если требуется конструктор копирования, то должна быть перегружена операция присваивания.

  2. Может быть определена только в форме метода класса.

  3. Не наследуется.


const monstr& operator =(const monstr &M)

{

if (&M == this) return *this;

if (name) delete [] name;

if (M.name)

{

name = new char [strlenM.name) + 1];

strcpy(name, M.name);}

else name =0;

health = M.health;

ammo = M.ammo;

skin = M.skin;

return *this;

}



void main()



{

monstr A(10), B, C;

C = B = A;

...

}
Необходимо

  • убедиться, что нет присваивания вида x = x;

  • удалить предыдущее значение в динамически выделенной памяти;

  • выделить память под новые значения полей;

  • скопировать все значения полей;

  • возвратить значение объекта, на которое указывает this.

Конец четвертой лекции

=========================================================
Перегрузка операций new, delete


  • не требуется передавать параметр типа класса;

  • Первым параметром для new, new[] передается размер объекта типа size_t(), при вызове передается в функции неявным образом;

  • new, new[] определяются с типом возвращаемого значения void*;

  • delete определяется с типом возврата void и первый аргумент типа void*;

  • операции – статические элементы класса.

Поведение операций должно соответствовать действиям, выполняемым по умолчанию.


class Obj{

…};

class pObj{



private:

Obj *p;



};


Стандарт:
pObj *p = new pObj;
Для экономии памяти новая операция new класса pObj выделяет большой блок памяти, затем размещать в нем указатели на Obj . Для этого в класс pObj вводятся статические поля для указания размера блока и указатель на свободную ячейку блока. Неиспользуемые ячейки связываются в список. Используем union для размещения в поле или указателя на объект или связи со следующей свободной ячейкой.
class pObj{

public:

static void* operator new(size_t SIZE);

static void operator delete(void * ObjD, size_t SIZE);



private:

union {

Obj *p;

pObj *next;

};
static const int BLOCK_SIZE;

static pObj *headFree;



};



void * pObj:: operator new(size_t SIZE)

{

if (SIZE != sizeof(pObj)) //память задана неверно

return:: operator new(SIZE); // стандартная операция

pObj *з = headFree; // указатель на первую свободную ячейку

if (p)

headFree = p->next;

else

{ //выделение нового блока

pObj *newblock =

static_cast
(::operator new(
BLOCK_SIZE * sizeof(pObj)));


//связь ячеек

for(int i = 1; i < BLOCK_SIZE -1; ++i )

newblock[i].next = &newblock[i+1];

newblock[BLOCK_SIZE -1].next = 0;

p = newblock;

headFree = newblock[1];

}

return p;

};

// инициализация статических полей


pObj *pObj::headFree; // =0

const int pObj:: BLOCK_SIZE = 1024;


void pObj::operator delete(void * ObjD, size_t SIZE)

{

if (ObjD == 0) return;

if (SIZE != sizeof(pObj)) //память задана неверно

{:: operator delete(ObjD); // стандартная операция

return;

}

pObj *p = static_cast
(
ObjD);


p->next = headFree;

headFree = p;
};

Конец пятой лекции

=========================================================



Перегрузка операции присваивания приведения типа
operator <имя нового типа> () {тело функции}
Тип возвращаемого значения и параметры не указываются.
monstr:: operator in(){return health;}



monstr Vasia; cout << int(Vasia);


Перегрузка операции вызова функции

Функциональный класс, т.е. тот в котором определена операция вызова функции, не требует наличия других полей и методов, кроме определения вызова функции.
class if_greater {

public:

int operator() (int a, int b)

const { return a>b;}

};



if_greater x;



cout << x(1,5) << x.operator()(1,5)<<

if_greater()(5,1); // конструктор по умолчанию
«операцией » является «()»

Можно записать x(1,5) , а также x.operator()(1,5)


Рекомендации по составу классов

  • консрукторы, определяющие как инициализировать объекты;

  • набор методов, реализующих свойства класса (const указывает, что поля класса не должны меняться);

  • набор операций (копирование, присваивание, сравнение, …);

  • класс исключений (сообщения об ошибке)

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


namespace Nashi{

class monstr{…};

class hero{…};

void change(hero, monstr);



}


Задание из практикума «Реализация класса треугольников»
Библиотека

  • cstring – работа со строками в С;

  • iomanip – манипуляторы, то есть функции, которые можно помещать в цепочку помещения и извлечения для форматирования данных (dec, oct, hex, endl, setprecision, setw).


sprintf – вывод в строку (stdio).
Конец седьмой лекции

=========================================================

Раздел main.cpp. Меню, заглушки. Тестирование, отладка первой версии.

Этап 2. Перемещение точки (перегрузка +), треугольника.

Конец восьмой лекции

=========================================================

Этап 3. Поиск максимального по площади треугольника: перегрузка >, «не совсем правильная» функция FindMax Отладка (сбой): конструктор внутри функции, модификация объекта, копирование по умолчанию. Напоминание о работе конструктора и деструктора. Перегрузка операции присваивания, конструктор копирования.


Конец девятой лекции

=========================================================
Этап 4. Отношение включения. Алгоритм положения точки относительно вектора (5 вариантов) (самостоятельное изучение).
Наследование

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


Ключи доступа

class имя :[private | protected | public] базовый_класс [[,базовый_класс]]

{тело класса}
class A {…};

class B {…};

class C {…};

class D: A, protected B, public C {…};
По умолчанию для классов ключ доступа private, для структур public.

Обозначмм

  • public - 1

  • protected - 2

  • private – 3




Доступ в произ. кл.

Ключ доступа (Y)

1

2

3

Базовый класс (X)

max{X,Y}










1




1

2

3

2




2

2

3

3




-

-

-

Если ключ доступа private можно сделать поля базового класса доступными через доступ к области видимости.


class Base {



public : void f();



};

class D: private Base {



public : Base::void f();



};

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

Один родитель, конструкторы и операции присваивания не наследуются, деструкторы наследуются.


enum color{red, green, blue};

class monstr{

int health,ammo;

color skin;

char *name;

public:

monstr(int he=100, int am=10);

monstr(color sk);

monstr(char *nm);

monstr(monstr &M);

~monstr(){delete [] name;}

void draw( int x, int y, int scale, int position);

int get_health()

{return health;}

int get_ammo()

{return ammo;}

monstr & operator ++()

{ ++health; return *this;

}

monstr operator ++(int)

{monstr M(*this)

health++;

return M;

}

operator int()

{ return health;

}

bool operator >( monstr &M)

{ if (health > M.health)

return true;

return false;

}

const monstr& operator =(const monstr &M)

{

if (&M == this) return *this;

if (name) delete [] name;

if (M.name)

{

name = new char [strlenM.name) + 1];

strcpy(name, M.name);}

else name =0;

health = M.health;

ammo = M.ammo;

skin = M.skin;

return *this;

}



void change_health(int he)

{ health =he;

}



};
monstr::monstr(const monstr &M)

{health = M.health;

ammo = M.ammo;

skin= M.skin;

if (M.name)

{ name = new char [strlen(M.name)+1];

strcpy (name , M.name);

}

else name = 0;

}
monstr::monstr(int he, int am)

{health = he;

ammo = am;

skin = red;

name = 0;

}

monstr::monstr(color sk)

{switch (sk)

{ case red: health = 100; ammo = 10; skin = red;

name = 0; break;

case green: health = 100; ammo = 20; skin = green;

name = 0; break;

case blue: health = 100; ammo = 40; skin = blue;

name = 0; break;

}

}

monstr::monstr(char *nm)

{health = 100;

ammo = 10;

skin= red;

name = new char [strlen(nm)+1];

strcpy (name , nm);

}
void monstr::draw(int x, int y, int scale, int position)

{ cout <
// --------------------------------------

Конец десятой лекции

=========================================================
class daemon : public monstr {

int brain;

public:

daemon (int br=100){brain = br};

daemon (color sk): monstr(sk){brain =10};

daemon (char *nm): monstr(nm){brain =10};

daemon (daemon &M) ): monstr(M){brain =10};

// -----------------

const daemon & operator =(const daemon &M)

{

if (&M == this) return *this;

brain = M.brain;

monstr:: operator = (M);

return *this;

}

//---------------------

void think();

void draw( int x, int y, int scale, int position);

};
void daemon:: think()

{…};

void daemon::draw(int x, int y, int scale, int position)

{ cout <

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




  1. Если в конструкторе производного класса нет явного вызова конструктора базового класса, вызывается конструктор базового класса по умолчанию.

  2. Конструкторы базовых классов вызываются, начиная с верхнего уровня. Конструкторы элементов-объектов вызываются в порядке их объявления. Последним вызывается конструктор класса.

  3. При нескольких базовых классов конструкторы вызываются в порядке объявления.

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

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


Порядок наследования деструкторов:


  1. Наследуются. Если не описан, формируется по умолчанию.

  2. В деструкторе производного класса не требуется явного вызова деструктора базового класса.

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



Пример замещения функций (практикум)


#include

using namespace std;

class Base {

public : void Display(){cout << “Hello, world !”<

};

class Derived: public Base {

public :

void Display(){

Base:: void Display();

cout << “How are you ?” <

};

class SubDerived: public Derived {

public :

void Display(){

Derived:: void Display();

cout << “Bye!” <

};

int main()

{

SubDerived sd;

sd.Display();

return 0;

}

+++++++++++++++++++++
Hello, world!

How are you?

Bye!

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

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


monstr *p;

p = new daemon;

p -> draw(1,1,1,1);
Вызов происходит в соответствии с типом указателя, а не фактическим типом объекта, на которой он ссылается, поэтому будет «нарисован» monstr, а не daemon. Этот процесс - ранее связывание. Явное преобразование типа указателя
(daemon * p) -> draw(1,1,1,1)

не всегда возможно (параметр функции – указатель, список указателей …).



Позднее связывание через виртуальные методы.
virtual void draw( int x, int y, int scale, int position);



  • Если в базовом классе метод – виртуальный, то в производном классе с тем же набором параметров – виртуальный, с отличающимся набором параметров – обычный.

  • Виртуальные методы наследуются. Можно переопределять, при этом права доступа изменить нельзя.

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

  • Виртуальный метод может быть friend , но не может быть static.

  • Если есть описание виртуального метода, то он должен быть определен хотя бы как чисто виртуальный с признаком =0 вместо тела:

virtual void f(int) =0;

и он должен переопределяться в производном классе.


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

monstr *p, *r;

r = new monstr;

p = new daemon;

r -> draw(1,1,1,1);

p -> draw(1,1,1,1);

p -> monstr:: draw(1,1,1,1);
Если объект производного класса вызывает виртуальный метод из другого метода базового класса, то есть косвенно, то будет вызван метод производного класса.

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

Для каждого класса с виртуальными методами создается таблица виртуальных методов (vtbl) с адресами памяти виртуальных методов.



Каждый объект содержит срытое дополнительное поле ссылки (vptr)- заполняется конструктором при создании объекта. При компиляции ссылки на методы – через vptr. При выполнении адрес выбирается в момент обращения к методу.

Рекомендуется делать виртуальными деструкторы (возможны динамические объекты, delete работает с size_t).

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

Абстрактный класс – класс хотя бы с одним чисто виртуальным методом. Объекты такого класса создавать нельзя.

  • Нельзя использовать при явном приведении типов, для описания типа параметра функции, типа возвращаемого функцией значения.

  • Допускается объявлять ссылки и указатели на абстрактный класс, если при инициализации не требуется создавать объект.

  • Если в производном классе не определены все чисто виртуальные методы, он – абстрактный.

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


izumzum.ru