Презентацию к лекции Вы можете скачать здесь.
При большом количестве никак не связанных классов управлять ими становится невозможным.
Механизм
Классы, находящиеся ближе к началу
При описании класса в его заголовке перечисляются все классы, являющиеся для него базовыми. Возможность обращения к элементам этих классов регулируется с помощью модификаторов наследования , и public:
class имя : [private | protected | public] базовый_класс{тело класса};
Если , а для структур - public.
Если задан модификатор public, оно называется открытым. Использование модификатора делает - закрытым. Это не просто названия: в зависимости от вида
До сих пор мы рассматривали только спецификаторы доступа и public, применяемые к элементам класса. Для любого элемента класса может также использоваться , который для одиночных классов, не входящих в иерархию, равносилен . Разница между ними проявляется при
| Модификатор | Доступ в | |
|---|---|---|
|
|
нет |
|
|
|
public |
|
|
|
|
нет |
|
|
|
public |
|
|
public |
|
нет |
|
|
|
public |
public |
Как видно из таблицы, элементы
Элементы при становятся в , в остальных случаях права доступа к ним не изменяются.
Доступ к элементам public при
Если базовый класс наследуется с ключом , можно выборочно сделать некоторые его элементы доступными в public
class Base{...
public: void f();};
class Derived : private Base{...
public: Base::void f();};
Простым называется
Создадим производный от класса monster класс , добавив полезную в некоторых случаях способность думать:
enum color {red, green, blue};
// ------------- Класс monster -------------
class monster
{
// ------------- Скрытые поля класса:
int health, ammo;
color skin;
char *name;
public:
// ------------- Конструкторы:
monster(int he = 100, int am = 10);
monster(color sk);
monster(char * nam);
monster(monster M);
// ------------- Деструктор:
~monster() {delete [] name;}
// ------------- Операции:
monster operator ++(){++health; return *this;}
monster operator ++(int)
{monster M(*this); health++; return M;}
operator int(){return health;}
bool operator >(monster M)
{
if( health > M.get_health()) return true;
return false;
}
monster operator = (monster M)
{
if (M == this) return *this;
if (name) delete [] name;
if (M.name)
{
name = new char [strlen(M.name) + 1];
strcpy(name, M.name);
}
else name = 0;
health = M.health; ammo = M.ammo; skin = M.skin;
return *this;
}
// ------------- Методы доступа к полям:
int get_health() const {return health;}
int get_ammo() const {return ammo;}
// ------------- Методы, изменяющие значения полей:
void set_health(int he){ health = he;}
void draw(int x, int y, int scale, int position);
};
// ------------- Реализация класса monster -------------
monster::monster(int he, int am):
health (he), ammo (am), skin (red), name (0){}
monster::monster(monster M)
{
if (M.name)
{
name = new char [strlen(M.name) + 1];
strcpy(name, M.name);
}
else name = 0;
health = M.health; ammo = M.ammo; skin = M.skin;
}
monster::monster(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;
}
}
monster::monster(char * nam)
{
name = new char [strlen(nam)+1];
strcpy(name, nam);
health = 100; ammo = 10; skin = red;
}
void monster::draw(int x, int y, int scale, int position)
{ /* ... Отрисовка monster */ }
// ------------- Класс daemon -------------
class daemon : public monster
{
int brain;
public:
// ------------- Конструкторы:
daemon(int br = 10){brain = br;};
daemon(color sk) : monster (sk) {brain = 10;}
daemon(char * nam) : monster (nam) {brain = 10;}
daemon(daemon M) : monster (M) {brain = M.brain;}
// ------------- Операции:
daemon operator = (daemon M)
{
if (M == this) return *this;
brain = M.brain;
monster::operator = (M);
return *this;
}
// ------------- Методы, изменяющие значения полей:
void draw(int x, int y, int scale, int position);
void think();
};
// ------------- Реализация класса daemon -------------
void daemon::draw(int x, int y, int scale, int position)
{ /* ... Отрисовка daemon */ }
void daemon:: think(){ /* ... */ }
В классе введено поле brain и метод think, определены собственные . Все monster, операции (кроме get_health, get_ammo и set_health наследуются в классе , а
Рассмотрим правила наследования различных методов.
Конструкторы не наследуются, поэтому
daemon .ВНИМАНИЕ
Если
Не наследуется и операция присваивания, поэтому ее также требуется явно определить в классе . Обратите внимание на запись функции-операции: в ее теле применен явный вызов функции- вместе со знаком операции можно интерпретировать как
Правила для деструкторов при
Поля, унаследованные из класса monster, недоступны функциям . Если функциям, определенным в , требуется работать с этими полями, можно либо описать их в базовом классе как , либо обращаться к ним с помощью функций из monster, либо явно переопределить их в так, как было показано в предыдущем разделе.
Добавляемые поля в наследнике могут совпадать и по имени, и по типу с полями
Статические поля, объявленные в базовом классе, наследуются обычным образом. Все объекты
Рассматривая описан метод , переопределяющий метод с тем же именем в классе monster (поскольку отрисовка различных персонажей, естественно, выполняется по-разному). Таким образом,
В классе-наследнике можно определять новые методы. В них разрешается вызывать любые доступные методы
Работа с объектами чаще всего производится через указатели. Указателю на базовый класс можно присвоить значение адреса объекта любого
monster *p; // Описывается указатель на базовый класс p = new daemon; /* Указатель ссылается на объект производного класса */
p -> draw(1, 1, 1, 1);
будет вызван monster, а не класса , поскольку ссылки на методы разрешаются во время , можно использовать
((daemon * p)) -> draw(1, 1, 1, 1);
Это не всегда возможно, поскольку в разное время указатель может ссылаться на объекты разных классов
В качестве примера можно привести функцию, параметром которой является указатель на объект
В С++ реализован механизм позднего связывания, когда разрешение ссылок на функцию происходит на этапе выполнения программы в зависимости от конкретного
Для определения виртуального метода используется :
virtual void draw(int x, int y, int scale, int position);
Рассмотрим правила использования
static , но может быть объявлен как дружественная функция.Чисто виртуальный метод содержит признак = 0 вместо тела, например:
virtual void f(int) = 0;
Если определить метод в классе monster как виртуальный, решение о том, метод какого класса вызвать, будет приниматься в зависимости от
monster *r, *p; r = new monster; // Создается объект класса monster p = new daemon; // Создается объект класса daemon r -> draw(1,1,1,1); // Вызывается метод monster::draw p -> draw(1,1,1,1); // Вызывается метод daemon::draw p -> monster::draw(1,1,1,1); // Обход механизма виртуальных методов
Если объект класса будет вызывать метод не непосредственно, а косвенно (то есть из другого метода, который может быть определен только в классе monster ), будет вызван метод класса .
Итак, виртуальным называется метод, ссылка на который разрешается на этапе выполнения программы (перевод красивого английского слова - всего-навсего "фактический", то есть ссылка разрешается по факту вызова).
Рассмотрим механизм позднего связывания.
Для каждого класса (не объекта!), содержащего хотя бы один vtbl ), в которой для каждого vtbl одно и то же смещение для каждого класса в пределах
Каждый объект содержит скрытое дополнительное поле ссылки на vtbl, называемое vptr. Оно заполняется
На этапе vtbl через vptr объекта, а на этапе выполнения в момент обращения к методу его адрес выбирается из таблицы. Таким образом, вызов
Рекомендуется делать виртуальными деструкторы для того, чтобы гарантировать правильное
Четкого правила, по которому метод следует делать виртуальным, не существует. Можно только дать рекомендацию объявлять виртуальными методы, для которых есть вероятность, что они будут переопределены в
Для пояснения последнего тезиса представим себе, что вызов метода осуществляется из метода перемещения объекта. Если текст метода перемещения не зависит от типа перемещаемого объекта (поскольку принцип перемещения всех объектов одинаков, а для отрисовки вызывается конкретный метод), переопределять этот метод в виртуальный, метод перемещения сможет без перекомпиляции работать с объектами любых производных классов - даже тех, о которых при его написании ничего известно не было.
Виртуальный механизм работает только при использовании указателей или ссылок на объекты. Объект, определенный через указатель или ссылку и содержащий
Класс, содержащий хотя бы один
При определении
Таким образом, можно создать функцию, параметром которой является указатель на
class monster
{
public: int get_health();
...
};
class hero
{
public: int get_health();
...
};
class being: public monster, public hero{ ... };
int main(){
being A;
cout << A.monster::get_health();
cout << A.hero::get_health();}
Использование конструкции A.get_health() приведет к ошибке, поскольку компилятор не в состоянии разобраться, метод какого из
Если у
class monster{ ... };
class daemon: virtual public monster{ ... };
class lady: virtual public monster{ ... };
class god: public daemon, public lady{ ... };
Класс god содержит только один экземпляр
С++ Б. Страуструп рекомендует везде, где это возможно, обходиться без него.
Вид вложения, когда в классе описано поле
Структуры ( struct ) и объединения ( ) представляют собой частные случаи классов. Структуры отличаются от классов тем, что доступ к элементам, а также базовый класс при public. Структуры предпочтительнее использовать для объектов, все элементы которых доступны.
Доступ в объединениях также устанавливается public, кроме того, в них вообще нельзя использовать union (свойства).) В
Шаблон класса позволяет задать класс, параметризованный типом данных. Передача классу различных типов данных в качестве параметра создает семейство родственных классов. Наиболее широкое применение шаблоны находят при создании контейнерных классов. Контейнерным называется класс, который предназначен для хранения каким-либо образом организованных данных и работы с ними. Преимущество использования шаблонов состоит в том, что как только алгоритм работы с данными определен и отлажен, он может применяться к любым типам данных без переписывания кода.
Рассмотрим процесс создания шаблона класса на примере
Сначала рассмотрим непараметризованную версию класса. Список состоит из узлов, связанных между собой с помощью указателей. Каждый узел хранит целое число, являющееся ключом списка. Опишем вспомогательный класс для представления одного узла списка:
class Node
{
public:
int d; // Данные
Node *next, *prev; //Указатели на предыдущий и последующий узлы
Node(int dat = 0)
{ d = dat; next = 0; prev = 0; } // Конструктор
};
Поскольку этот класс будет описан внутри класса, представляющего список, поля для простоты доступа из внешнего класса сделаны доступными ( public ). Это позволяет обойтись без функций доступа и изменения полей. Назовем класс списка List:
class List
{
class Node{ ... };
Node *pbeg, *pend; // Указатели на начало и конец списка
public:
List() { pbeg = 0; pend = 0; } // Конструктор
~List(); // Деструктор
void add(int d); // Добавление узла в конец списка
Node * find(int i); // Поиск узла по ключу
Node * insert(int key, int d); /* Вставка узла d после узла с ключом key */
bool remove(int key); // Удаление узла
void print(); // Печать списка в прямом направлении
void print_back(); // Печать списка в обратном направлении
};
Рассмотрим реализацию add выделяет память под новый объект типа и присоединяет его к списку, обновляя указатели на его начало и конец:
void List::add(int d)
{
Node *pv = new Node(d); // Выделение памяти под новый узел
if (pbeg == 0)pbeg = pend = pv; // Первый узел списка
else
{
pv->prev = pend; // Связывание нового узла с предыдущим
pend->next = pv; pend = pv;
} // Обновление указателя на конец списка
}
Метод выполняет поиск узла с заданным ключом и возвращает указатель на него в случае успешного поиска и 0 в случае отсутствия такого узла в списке:
Node * List::find( int d )
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv=pv->next;
}
return pv;
}
Метод вставляет в список узел после узла с ключом key и возвращает указатель на вставленный узел. Если такого узла в списке нет, вставка не выполняется и возвращается значение 0:
Node * List::insert(int key, int d)
{
if(Node *pkey = find(key))
{
// Поиск узла с ключом key
/* Выделение памяти под новый узел и его инициализация */
Node *pv = new Node(d);
/* Установление связи нового узла с последующим */
pv->next = pkey->next;
// Установление связи нового узла с предыдущим
pv->prev = pkey;
// Установление связи предыдущего узла с новым
pkey->next = pv;
if( pkey != pend)
(pv->next)->prev = pv; /* Установление связи последующего узла с новым */
/* Обновление указателя на конец списка, если узел вставляется в конец */
else pend = pv;
return pv;
}
return 0;
}
Метод удаляет узел с заданным ключом из списка и возвращает значение true в случае успешного удаления и false, если узел с таким ключом в списке не найден:
bool List::remove(int key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{ // Удаление из начала списка
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{ // Удаление из конца списка
pend = pend->prev; pend->next = 0;
}
else
{ // Удаление из середины списка
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;}
return false;}
Методы печати списка в прямом и обратном направлении поэлементно просматривают список, переходя по соответствующим ссылкам:
void List::print(){
Node *pv = pbeg;
cout << endl << "list: ";
while (pv){
cout << pv->d << ' ';
pv=pv->next;}
cout << endl;
}
void List::print_back(){
Node *pv = pend;
cout << endl << " list back: ";
while (pv){
cout << pv->d << ' ';
pv=pv->prev;}
cout << endl;}
List::~List(){
if (pbeg != 0){
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}}
Ниже приведен пример программы, использующей класс List. Программа формирует список из 5 чисел, выводит его на экран, добавляет число в список, удаляет число из списка и снова выводит его на экран:
int main()
{
List L;
for (int i = 2; i<6; i++) L.add(i);
L.print(); L.print_back(); L.insert(2,200);
if (!L.remove(5))cout << "not found";
L.print(); L.print_back();}
Класс List предназначен для хранения целых чисел. Чтобы хранить в нем данные любого типа, требуется описать этот класс как шаблон и передать тип в качестве параметра.
Синтаксис описания шаблона:
template <описание_параметров_шаблона> class имя { /* определение класса */ };
Шаблон класса начинается с ключевого слова . В угловых скобках записывают параметры шаблона. При использовании шаблона на место этих параметров шаблону передаются аргументы: типы и константы, перечисленные через запятую.
Типы могут быть как стандартными, так и определенными пользователем. Для их описания в списке параметров используется ключевое слово class. В простейшем случае одного параметра это выглядит как <class T>. Здесь T является параметром-типом. Имя параметра может быть любым, но принято начинать его с T. Внутри класса-шаблона параметр может появляться в тех местах, где разрешается указывать конкретный тип, например:
template <class TData> class List
{
class Node{
public:
TData d;
Node *next;
Node *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
}; ... }
Класс TData можно рассматривать как параметр, на место которого при компиляции будет подставлен конкретный тип данных. Получившийся шаблонный класс имеет тип List<TData>.
Методы шаблона класса автоматически становятся шаблонами функций. Если метод описывается вне шаблона, его заголовок должен иметь следующие элементы:
template <описание_параметров_шаблона>
возвр_тип имя_класса <параметры_шаблона >:: имя_функции (список_параметров функции)
Проще рассмотреть синтаксис описания методов шаблона на примере:
template <class Data> void List <Data>::print()
{ /* тело функции */ }
friend -шаблоны.Если у шаблона несколько параметров, они перечисляются через запятую. Ключевое слово class требуется записывать перед каждым параметром, например:
template <class T1, class T2>
struct Pair { T1 first; T2 second; };
Параметрам шаблонного класса можно присваивать значения по умолчанию, они записываются после знака "=". Как и для обычных функций, задавать значения по умолчанию следует, начиная с правых параметров.
Ниже приведено полное описание параметризованного класса List.
template <class TData> class List
{
class Node
{
public:
TData d;
Node *next, *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
};
Node *pbeg, *pend;
public:
List(){pbeg = 0; pend = 0;}
~List();
void add(TData d);
Node * find(TData i);
Node * insert(TData key, TData d);
bool remove(TData key);
void print();
void print_back();};
//-------------------------
template <class TData> List <TData>::~List()
{
if (pbeg !=0)
{
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}
}
//-------------------------
template <class TData> void List <TData>::print()
{
Node *pv = pbeg;
cout << endl << "list: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->next;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::print_back()
{
Node *pv = pend;
cout << endl << " list back: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->prev;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::add(TData d)
{
Node *pv = new Node(d);
if (pbeg == 0)pbeg = pend = pv;
else
{
pv->prev = pend;
pend->next = pv;
pend = pv;
}
}
//-------------------------
template <class TData> Node * List <TData>::find( TData d)
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv = pv->next;
}
return pv;
}
//-------------------------
template <class TData> Node * List <TData>::insert(TData key, TData d)
{
if(Node *pkey = find(key))
{
Node *pv = new Node(d);
pv->next = pkey->next;
pv->prev = pkey;
pkey->next = pv;
if( pkey != pend)(pv->next)->prev = pv;
else pend = pv;
return pv;
}
return 0;
}
//-------------------------
template <class TData> bool List <TData>::remove(TData key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{
pend = pend->prev; pend->next = 0;
}
else
{
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;
}
return false;
}
Если требуется использовать шаблон List для
При определении синтаксиса шаблона было сказано, что в него, кроме типов, могут передаваться константы. Соответствующим параметром шаблона может быть:
В теле шаблона такие параметры могут применяться в любом месте, где допустимо использовать константное выражение.
В качестве примера создадим шаблон класса, содержащего
template <class Type, int kol> class Block
{
public:
Block(){p = new Type [kol];}
~Block(){delete [] p;}
operator Type *();
protected:
Type * p;
};
template <class Type, int kol>
Block <Type, kol>:: operator Type *()
{
return p;
}
У класса-шаблона могут быть друзья, и шаблоны тоже могут быть друзьями. Класс может быть объявлен внутри шаблона, а шаблон - внутри как класса, так и шаблона. Единственным ограничением является то, что шаблонный класс нельзя объявлять внутри функции. В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. После создания и
Чтобы создать при помощи шаблона конкретный объект конкретного класса, при описании объекта после имени шаблона в угловых скобках перечисляются его
имя_шаблона <фактические параметры> имя_объекта (параметры_конструктора);
Процесс создания конкретного класса из шаблона путем
List <int> List_int; // список целых чисел
List <double> List_double; // список вещественных чисел
List <monster> List_monster; // список объектов класса monster
Block <char, 128> buf; // блок символов
Block <monstr, 100> stado; // блок объектов класса monster
Pair<int, int> a; // объявление пары целых
Pair<int, double> b; // объявление пары "целый, вещественный"
Pair<int, double> b = { 1, 2.1 }; // объявление с инициализацией
Pair<string, Date> d; // аргументы - пользовательские классы
При использовании параметров шаблона по умолчанию
template<class T = char> class String; String<>* p;
Для каждого инстанцированного класса компилятор создает имя, отличающееся и от имени шаблона, и от имен других инстанцированных классов. Тем самым каждый инстанцированный класс определяет отдельный тип. В
После создания объектов с ними можно работать так же, как с объектами обычных классов, например:
for (int i = 1; i<10; i++)List_double.add(i*0.08); List_double.print(); //---------------------------------- for (int i = 1; i<10; i++)List_monster.add(i); List_monster.print(); //---------------------------------- strcpy(buf, "Очень важное сообщение"); cout << buf << endl;
Для упрощения использования шаблонов классов можно применить переименование типов с помощью typedef:
typedef List <double> Ldbl; Ldbl List_double;
Шаблоны представляют собой мощное и эффективное средство обращения с различными типами данных, которое можно назвать
Стандартная библиотека С++ предоставляет большой набор шаблонов для различных способов организации хранения и обработки данных.
На этом данный курс лекций завершается. В него не вошли многие темы, рассмотренные в учебнике [18] и практикуме [19]: шаблоны функций, основы структурной и объектно-ориентированной технологии разработки программ, организация динамических структур данных, обработка исключительных ситуаций, преобразование типов, введение в паттерны проектирования, описание стандартной библиотеки (потоковые классы, строки, контейнеры, итераторы, функциональные объекты, алгоритмы и другие средства). Это связано с формулировкой договора, заключенного автором с издательством ПИТЕР при публикации книги.
Презентацию к лекции Вы можете скачать здесь.
При большом количестве никак не связанных классов управлять ими становится невозможным.
Механизм
Классы, находящиеся ближе к началу
При описании класса в его заголовке перечисляются все классы, являющиеся для него базовыми. Возможность обращения к элементам этих классов регулируется с помощью модификаторов наследования , и public:
class имя : [private | protected | public] базовый_класс{тело класса};
Если , а для структур - public.
Если задан модификатор public, оно называется открытым. Использование модификатора делает - закрытым. Это не просто названия: в зависимости от вида
До сих пор мы рассматривали только спецификаторы доступа и public, применяемые к элементам класса. Для любого элемента класса может также использоваться , который для одиночных классов, не входящих в иерархию, равносилен . Разница между ними проявляется при
| Модификатор | Доступ в | |
|---|---|---|
|
|
нет |
|
|
|
public |
|
|
|
|
нет |
|
|
|
public |
|
|
public |
|
нет |
|
|
|
public |
public |
Как видно из таблицы, элементы
Элементы при становятся в , в остальных случаях права доступа к ним не изменяются.
Доступ к элементам public при
Если базовый класс наследуется с ключом , можно выборочно сделать некоторые его элементы доступными в public
class Base{...
public: void f();};
class Derived : private Base{...
public: Base::void f();};
Простым называется
Создадим производный от класса monster класс , добавив полезную в некоторых случаях способность думать:
enum color {red, green, blue};
// ------------- Класс monster -------------
class monster
{
// ------------- Скрытые поля класса:
int health, ammo;
color skin;
char *name;
public:
// ------------- Конструкторы:
monster(int he = 100, int am = 10);
monster(color sk);
monster(char * nam);
monster(monster M);
// ------------- Деструктор:
~monster() {delete [] name;}
// ------------- Операции:
monster operator ++(){++health; return *this;}
monster operator ++(int)
{monster M(*this); health++; return M;}
operator int(){return health;}
bool operator >(monster M)
{
if( health > M.get_health()) return true;
return false;
}
monster operator = (monster M)
{
if (M == this) return *this;
if (name) delete [] name;
if (M.name)
{
name = new char [strlen(M.name) + 1];
strcpy(name, M.name);
}
else name = 0;
health = M.health; ammo = M.ammo; skin = M.skin;
return *this;
}
// ------------- Методы доступа к полям:
int get_health() const {return health;}
int get_ammo() const {return ammo;}
// ------------- Методы, изменяющие значения полей:
void set_health(int he){ health = he;}
void draw(int x, int y, int scale, int position);
};
// ------------- Реализация класса monster -------------
monster::monster(int he, int am):
health (he), ammo (am), skin (red), name (0){}
monster::monster(monster M)
{
if (M.name)
{
name = new char [strlen(M.name) + 1];
strcpy(name, M.name);
}
else name = 0;
health = M.health; ammo = M.ammo; skin = M.skin;
}
monster::monster(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;
}
}
monster::monster(char * nam)
{
name = new char [strlen(nam)+1];
strcpy(name, nam);
health = 100; ammo = 10; skin = red;
}
void monster::draw(int x, int y, int scale, int position)
{ /* ... Отрисовка monster */ }
// ------------- Класс daemon -------------
class daemon : public monster
{
int brain;
public:
// ------------- Конструкторы:
daemon(int br = 10){brain = br;};
daemon(color sk) : monster (sk) {brain = 10;}
daemon(char * nam) : monster (nam) {brain = 10;}
daemon(daemon M) : monster (M) {brain = M.brain;}
// ------------- Операции:
daemon operator = (daemon M)
{
if (M == this) return *this;
brain = M.brain;
monster::operator = (M);
return *this;
}
// ------------- Методы, изменяющие значения полей:
void draw(int x, int y, int scale, int position);
void think();
};
// ------------- Реализация класса daemon -------------
void daemon::draw(int x, int y, int scale, int position)
{ /* ... Отрисовка daemon */ }
void daemon:: think(){ /* ... */ }
В классе введено поле brain и метод think, определены собственные . Все monster, операции (кроме get_health, get_ammo и set_health наследуются в классе , а
Рассмотрим правила наследования различных методов.
Конструкторы не наследуются, поэтому
daemon .ВНИМАНИЕ
Если
Не наследуется и операция присваивания, поэтому ее также требуется явно определить в классе . Обратите внимание на запись функции-операции: в ее теле применен явный вызов функции- вместе со знаком операции можно интерпретировать как
Правила для деструкторов при
Поля, унаследованные из класса monster, недоступны функциям . Если функциям, определенным в , требуется работать с этими полями, можно либо описать их в базовом классе как , либо обращаться к ним с помощью функций из monster, либо явно переопределить их в так, как было показано в предыдущем разделе.
Добавляемые поля в наследнике могут совпадать и по имени, и по типу с полями
Статические поля, объявленные в базовом классе, наследуются обычным образом. Все объекты
Рассматривая описан метод , переопределяющий метод с тем же именем в классе monster (поскольку отрисовка различных персонажей, естественно, выполняется по-разному). Таким образом,
В классе-наследнике можно определять новые методы. В них разрешается вызывать любые доступные методы
Работа с объектами чаще всего производится через указатели. Указателю на базовый класс можно присвоить значение адреса объекта любого
monster *p; // Описывается указатель на базовый класс p = new daemon; /* Указатель ссылается на объект производного класса */
p -> draw(1, 1, 1, 1);
будет вызван monster, а не класса , поскольку ссылки на методы разрешаются во время , можно использовать
((daemon * p)) -> draw(1, 1, 1, 1);
Это не всегда возможно, поскольку в разное время указатель может ссылаться на объекты разных классов
В качестве примера можно привести функцию, параметром которой является указатель на объект
В С++ реализован механизм позднего связывания, когда разрешение ссылок на функцию происходит на этапе выполнения программы в зависимости от конкретного
Для определения виртуального метода используется :
virtual void draw(int x, int y, int scale, int position);
Рассмотрим правила использования
static , но может быть объявлен как дружественная функция.Чисто виртуальный метод содержит признак = 0 вместо тела, например:
virtual void f(int) = 0;
Если определить метод в классе monster как виртуальный, решение о том, метод какого класса вызвать, будет приниматься в зависимости от
monster *r, *p; r = new monster; // Создается объект класса monster p = new daemon; // Создается объект класса daemon r -> draw(1,1,1,1); // Вызывается метод monster::draw p -> draw(1,1,1,1); // Вызывается метод daemon::draw p -> monster::draw(1,1,1,1); // Обход механизма виртуальных методов
Если объект класса будет вызывать метод не непосредственно, а косвенно (то есть из другого метода, который может быть определен только в классе monster ), будет вызван метод класса .
Итак, виртуальным называется метод, ссылка на который разрешается на этапе выполнения программы (перевод красивого английского слова - всего-навсего "фактический", то есть ссылка разрешается по факту вызова).
Рассмотрим механизм позднего связывания.
Для каждого класса (не объекта!), содержащего хотя бы один vtbl ), в которой для каждого vtbl одно и то же смещение для каждого класса в пределах
Каждый объект содержит скрытое дополнительное поле ссылки на vtbl, называемое vptr. Оно заполняется
На этапе vtbl через vptr объекта, а на этапе выполнения в момент обращения к методу его адрес выбирается из таблицы. Таким образом, вызов
Рекомендуется делать виртуальными деструкторы для того, чтобы гарантировать правильное
Четкого правила, по которому метод следует делать виртуальным, не существует. Можно только дать рекомендацию объявлять виртуальными методы, для которых есть вероятность, что они будут переопределены в
Для пояснения последнего тезиса представим себе, что вызов метода осуществляется из метода перемещения объекта. Если текст метода перемещения не зависит от типа перемещаемого объекта (поскольку принцип перемещения всех объектов одинаков, а для отрисовки вызывается конкретный метод), переопределять этот метод в виртуальный, метод перемещения сможет без перекомпиляции работать с объектами любых производных классов - даже тех, о которых при его написании ничего известно не было.
Виртуальный механизм работает только при использовании указателей или ссылок на объекты. Объект, определенный через указатель или ссылку и содержащий
Класс, содержащий хотя бы один
При определении
Таким образом, можно создать функцию, параметром которой является указатель на
class monster
{
public: int get_health();
...
};
class hero
{
public: int get_health();
...
};
class being: public monster, public hero{ ... };
int main(){
being A;
cout << A.monster::get_health();
cout << A.hero::get_health();}
Использование конструкции A.get_health() приведет к ошибке, поскольку компилятор не в состоянии разобраться, метод какого из
Если у
class monster{ ... };
class daemon: virtual public monster{ ... };
class lady: virtual public monster{ ... };
class god: public daemon, public lady{ ... };
Класс god содержит только один экземпляр
С++ Б. Страуструп рекомендует везде, где это возможно, обходиться без него.
Вид вложения, когда в классе описано поле
Структуры ( struct ) и объединения ( ) представляют собой частные случаи классов. Структуры отличаются от классов тем, что доступ к элементам, а также базовый класс при public. Структуры предпочтительнее использовать для объектов, все элементы которых доступны.
Доступ в объединениях также устанавливается public, кроме того, в них вообще нельзя использовать union (свойства).) В
Шаблон класса позволяет задать класс, параметризованный типом данных. Передача классу различных типов данных в качестве параметра создает семейство родственных классов. Наиболее широкое применение шаблоны находят при создании контейнерных классов. Контейнерным называется класс, который предназначен для хранения каким-либо образом организованных данных и работы с ними. Преимущество использования шаблонов состоит в том, что как только алгоритм работы с данными определен и отлажен, он может применяться к любым типам данных без переписывания кода.
Рассмотрим процесс создания шаблона класса на примере
Сначала рассмотрим непараметризованную версию класса. Список состоит из узлов, связанных между собой с помощью указателей. Каждый узел хранит целое число, являющееся ключом списка. Опишем вспомогательный класс для представления одного узла списка:
class Node
{
public:
int d; // Данные
Node *next, *prev; //Указатели на предыдущий и последующий узлы
Node(int dat = 0)
{ d = dat; next = 0; prev = 0; } // Конструктор
};
Поскольку этот класс будет описан внутри класса, представляющего список, поля для простоты доступа из внешнего класса сделаны доступными ( public ). Это позволяет обойтись без функций доступа и изменения полей. Назовем класс списка List:
class List
{
class Node{ ... };
Node *pbeg, *pend; // Указатели на начало и конец списка
public:
List() { pbeg = 0; pend = 0; } // Конструктор
~List(); // Деструктор
void add(int d); // Добавление узла в конец списка
Node * find(int i); // Поиск узла по ключу
Node * insert(int key, int d); /* Вставка узла d после узла с ключом key */
bool remove(int key); // Удаление узла
void print(); // Печать списка в прямом направлении
void print_back(); // Печать списка в обратном направлении
};
Рассмотрим реализацию add выделяет память под новый объект типа и присоединяет его к списку, обновляя указатели на его начало и конец:
void List::add(int d)
{
Node *pv = new Node(d); // Выделение памяти под новый узел
if (pbeg == 0)pbeg = pend = pv; // Первый узел списка
else
{
pv->prev = pend; // Связывание нового узла с предыдущим
pend->next = pv; pend = pv;
} // Обновление указателя на конец списка
}
Метод выполняет поиск узла с заданным ключом и возвращает указатель на него в случае успешного поиска и 0 в случае отсутствия такого узла в списке:
Node * List::find( int d )
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv=pv->next;
}
return pv;
}
Метод вставляет в список узел после узла с ключом key и возвращает указатель на вставленный узел. Если такого узла в списке нет, вставка не выполняется и возвращается значение 0:
Node * List::insert(int key, int d)
{
if(Node *pkey = find(key))
{
// Поиск узла с ключом key
/* Выделение памяти под новый узел и его инициализация */
Node *pv = new Node(d);
/* Установление связи нового узла с последующим */
pv->next = pkey->next;
// Установление связи нового узла с предыдущим
pv->prev = pkey;
// Установление связи предыдущего узла с новым
pkey->next = pv;
if( pkey != pend)
(pv->next)->prev = pv; /* Установление связи последующего узла с новым */
/* Обновление указателя на конец списка, если узел вставляется в конец */
else pend = pv;
return pv;
}
return 0;
}
Метод удаляет узел с заданным ключом из списка и возвращает значение true в случае успешного удаления и false, если узел с таким ключом в списке не найден:
bool List::remove(int key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{ // Удаление из начала списка
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{ // Удаление из конца списка
pend = pend->prev; pend->next = 0;
}
else
{ // Удаление из середины списка
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;}
return false;}
Методы печати списка в прямом и обратном направлении поэлементно просматривают список, переходя по соответствующим ссылкам:
void List::print(){
Node *pv = pbeg;
cout << endl << "list: ";
while (pv){
cout << pv->d << ' ';
pv=pv->next;}
cout << endl;
}
void List::print_back(){
Node *pv = pend;
cout << endl << " list back: ";
while (pv){
cout << pv->d << ' ';
pv=pv->prev;}
cout << endl;}
List::~List(){
if (pbeg != 0){
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}}
Ниже приведен пример программы, использующей класс List. Программа формирует список из 5 чисел, выводит его на экран, добавляет число в список, удаляет число из списка и снова выводит его на экран:
int main()
{
List L;
for (int i = 2; i<6; i++) L.add(i);
L.print(); L.print_back(); L.insert(2,200);
if (!L.remove(5))cout << "not found";
L.print(); L.print_back();}
Класс List предназначен для хранения целых чисел. Чтобы хранить в нем данные любого типа, требуется описать этот класс как шаблон и передать тип в качестве параметра.
Синтаксис описания шаблона:
template <описание_параметров_шаблона> class имя { /* определение класса */ };
Шаблон класса начинается с ключевого слова . В угловых скобках записывают параметры шаблона. При использовании шаблона на место этих параметров шаблону передаются аргументы: типы и константы, перечисленные через запятую.
Типы могут быть как стандартными, так и определенными пользователем. Для их описания в списке параметров используется ключевое слово class. В простейшем случае одного параметра это выглядит как <class T>. Здесь T является параметром-типом. Имя параметра может быть любым, но принято начинать его с T. Внутри класса-шаблона параметр может появляться в тех местах, где разрешается указывать конкретный тип, например:
template <class TData> class List
{
class Node{
public:
TData d;
Node *next;
Node *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
}; ... }
Класс TData можно рассматривать как параметр, на место которого при компиляции будет подставлен конкретный тип данных. Получившийся шаблонный класс имеет тип List<TData>.
Методы шаблона класса автоматически становятся шаблонами функций. Если метод описывается вне шаблона, его заголовок должен иметь следующие элементы:
template <описание_параметров_шаблона>
возвр_тип имя_класса <параметры_шаблона >:: имя_функции (список_параметров функции)
Проще рассмотреть синтаксис описания методов шаблона на примере:
template <class Data> void List <Data>::print()
{ /* тело функции */ }
friend -шаблоны.Если у шаблона несколько параметров, они перечисляются через запятую. Ключевое слово class требуется записывать перед каждым параметром, например:
template <class T1, class T2>
struct Pair { T1 first; T2 second; };
Параметрам шаблонного класса можно присваивать значения по умолчанию, они записываются после знака "=". Как и для обычных функций, задавать значения по умолчанию следует, начиная с правых параметров.
Ниже приведено полное описание параметризованного класса List.
template <class TData> class List
{
class Node
{
public:
TData d;
Node *next, *prev;
Node(TData dat = 0){d = dat; next = 0; prev = 0;}
};
Node *pbeg, *pend;
public:
List(){pbeg = 0; pend = 0;}
~List();
void add(TData d);
Node * find(TData i);
Node * insert(TData key, TData d);
bool remove(TData key);
void print();
void print_back();};
//-------------------------
template <class TData> List <TData>::~List()
{
if (pbeg !=0)
{
Node *pv = pbeg;
while (pv)
{pv = pv->next; delete pbeg; pbeg = pv;}
}
}
//-------------------------
template <class TData> void List <TData>::print()
{
Node *pv = pbeg;
cout << endl << "list: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->next;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::print_back()
{
Node *pv = pend;
cout << endl << " list back: ";
while (pv)
{
cout << pv->d << ' ';
pv = pv->prev;
}
cout << endl;
}
//-------------------------
template <class TData> void List <TData>::add(TData d)
{
Node *pv = new Node(d);
if (pbeg == 0)pbeg = pend = pv;
else
{
pv->prev = pend;
pend->next = pv;
pend = pv;
}
}
//-------------------------
template <class TData> Node * List <TData>::find( TData d)
{
Node *pv = pbeg;
while (pv)
{
if(pv->d == d)break;
pv = pv->next;
}
return pv;
}
//-------------------------
template <class TData> Node * List <TData>::insert(TData key, TData d)
{
if(Node *pkey = find(key))
{
Node *pv = new Node(d);
pv->next = pkey->next;
pv->prev = pkey;
pkey->next = pv;
if( pkey != pend)(pv->next)->prev = pv;
else pend = pv;
return pv;
}
return 0;
}
//-------------------------
template <class TData> bool List <TData>::remove(TData key)
{
if(Node *pkey = find(key))
{
if (pkey == pbeg)
{
pbeg = pbeg->next; pbeg->prev = 0;
}
else if (pkey == pend)
{
pend = pend->prev; pend->next = 0;
}
else
{
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;
}
delete pkey; return true;
}
return false;
}
Если требуется использовать шаблон List для
При определении синтаксиса шаблона было сказано, что в него, кроме типов, могут передаваться константы. Соответствующим параметром шаблона может быть:
В теле шаблона такие параметры могут применяться в любом месте, где допустимо использовать константное выражение.
В качестве примера создадим шаблон класса, содержащего
template <class Type, int kol> class Block
{
public:
Block(){p = new Type [kol];}
~Block(){delete [] p;}
operator Type *();
protected:
Type * p;
};
template <class Type, int kol>
Block <Type, kol>:: operator Type *()
{
return p;
}
У класса-шаблона могут быть друзья, и шаблоны тоже могут быть друзьями. Класс может быть объявлен внутри шаблона, а шаблон - внутри как класса, так и шаблона. Единственным ограничением является то, что шаблонный класс нельзя объявлять внутри функции. В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. После создания и
Чтобы создать при помощи шаблона конкретный объект конкретного класса, при описании объекта после имени шаблона в угловых скобках перечисляются его
имя_шаблона <фактические параметры> имя_объекта (параметры_конструктора);
Процесс создания конкретного класса из шаблона путем
List <int> List_int; // список целых чисел
List <double> List_double; // список вещественных чисел
List <monster> List_monster; // список объектов класса monster
Block <char, 128> buf; // блок символов
Block <monstr, 100> stado; // блок объектов класса monster
Pair<int, int> a; // объявление пары целых
Pair<int, double> b; // объявление пары "целый, вещественный"
Pair<int, double> b = { 1, 2.1 }; // объявление с инициализацией
Pair<string, Date> d; // аргументы - пользовательские классы
При использовании параметров шаблона по умолчанию
template<class T = char> class String; String<>* p;
Для каждого инстанцированного класса компилятор создает имя, отличающееся и от имени шаблона, и от имен других инстанцированных классов. Тем самым каждый инстанцированный класс определяет отдельный тип. В
После создания объектов с ними можно работать так же, как с объектами обычных классов, например:
for (int i = 1; i<10; i++)List_double.add(i*0.08); List_double.print(); //---------------------------------- for (int i = 1; i<10; i++)List_monster.add(i); List_monster.print(); //---------------------------------- strcpy(buf, "Очень важное сообщение"); cout << buf << endl;
Для упрощения использования шаблонов классов можно применить переименование типов с помощью typedef:
typedef List <double> Ldbl; Ldbl List_double;
Шаблоны представляют собой мощное и эффективное средство обращения с различными типами данных, которое можно назвать
Стандартная библиотека С++ предоставляет большой набор шаблонов для различных способов организации хранения и обработки данных.
На этом данный курс лекций завершается. В него не вошли многие темы, рассмотренные в учебнике [18] и практикуме [19]: шаблоны функций, основы структурной и объектно-ориентированной технологии разработки программ, организация динамических структур данных, обработка исключительных ситуаций, преобразование типов, введение в паттерны проектирования, описание стандартной библиотеки (потоковые классы, строки, контейнеры, итераторы, функциональные объекты, алгоритмы и другие средства). Это связано с формулировкой договора, заключенного автором с издательством ПИТЕР при публикации книги.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.