Вводный курс лекций в ООП

advertisement
3-й семестр
«Программирование и алгоритмические языки»
I.
ООП на С++
Лекция 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'<<beavis->get_health() <<
'\n' << butthead.get_health()<< '\n'<<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 <iostream.h>
#include <stdlib.h>
#include <string.h>
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 <<x<< y<< scale<< position;}
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), а
здоровье увеличивается на 1
в //Vasia
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(<stddef.h>), при вызове передается в функции неявным образом;
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<pObj*>(::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<pObj*>(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
[[,базовый_класс]]
{тело класса}
class
class
class
class
|
protected
|
public]
базовый_класс
A {…};
B {…};
C {…};
D: A, protected B, public C {…};
По умолчанию для классов ключ доступа private, для структур – public.
Обозначмм
 public - 1
 protected - 2
 private – 3
Ключ
Доступ в
доступа
произ. кл.
(Y)
Базовый
max{X,Y}
класс (X)
1
2
3
1
2
3
1
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 <<x<< y<< scale<< position;}
// --------------------------------------
Конец десятой лекции
=========================================================
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 <<x<< y<< scale<< position;…}
Порядок вызова конструкторов:
1. Если в конструкторе производного класса нет явного вызова конструктора базового
класса, вызывается конструктор базового класса по умолчанию.
2. Конструкторы базовых классов вызываются, начиная с верхнего уровня. Конструкторы
элементов-объектов вызываются в порядке их объявления. Последним вызывается
конструктор класса.
3. При нескольких базовых классов конструкторы вызываются в порядке объявления.
4. Если конструктор требует указания параметров, он должен быть явно вызван.
Вызов функций из базового класса предпочтительнее копированию.
Порядок наследования деструкторов:
1. Наследуются. Если не описан, формируется по умолчанию.
2. В деструкторе производного класса не требуется явного вызова деструктора базового
класса.
3. Деструкторы вызываются в порядке обратном вызову конструкторов.
Пример замещения функций (практикум)
#include <iostream>
using namespace std;
class Base {
public : void Display(){cout << “Hello, world !”<<endl;}
};
class Derived: public Base {
public :
void Display(){
Base:: void Display();
cout << “How are you ?” <<endl;}
};
class SubDerived: public Derived {
public :
void Display(){
Derived:: void Display();
cout << “Bye!” <<endl;}
};
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).
Объект, определенный через указатель или ссылку и содержащий виртальные методы –
полиморфный.
Абстрактный класс – класс хотя бы с одним чисто виртуальным методом. Объекты такого
класса создавать нельзя.
 Нельзя использовать при явном приведении типов, для описания типа параметра
функции, типа возвращаемого функцией значения.
 Допускается объявлять ссылки и указатели на абстрактный класс, если при
инициализации не требуется создавать объект.
 Если в производном классе не определены все чисто виртуальные методы, он –
абстрактный.
Можно создавать функцию с параметрами – указателями на абстрактный класс, на место которых
при выполнении программы передается указатель на объект производного класса. Получаются
полиморфные функции.
Download