
- •Void main (){
- •Операции, определенные по умолчанию над структурированными объектами
- •Void binar(unsigned char ch)
- •Void main() { int k;
- •Расширение действия (перегрузка) стандартных операций
- •Доступ к компонентам структурированного объекта
- •Void ff(cl1 cl,cl2 c2) { тело_функции }
- •Void f1(...);
- •Void f2(...);
- •Классы и шаблоны
- •Int size; // Количество элементов в массиве
- •Vector(int); // Конструктор класса vector
- •Имя_параиетризованного_класса
- •Int length;
- •Void main () {
- •Наследование и другие возможности классов Наследование классов
- •2. Множественное наследование и виртуальные базовые классы
- •Void show ()
- •Void hide()
- •Void riesquare(void)
- •Void show()
- •Void hide()
- •Void show(void)
- •Void hide()
- •Void main()
- •Void show() // Изобразить на экране эллипс
- •Void hide() { int cc, bk;
- •Int min(int valuel, int value2)
- •Void show()
- •Void hide() // Убрать изображение с экрана дисплея
- •Void main()
- •3. Виртуальные функции и абстрактные классы
- •Void main (void)
- •Имя_проиаводного_класса: : show ()
- •Иня_объекта_производноро_класса. Show ()
- •Void main(void)
- •Void sos (int) ;
- •Void func(char);
- •Void sos (int) ;
- •Void chain::showAll(void) // Изображение элементов списка
- •Void main()
- •4. Локальные классы
- •Void showSeg() // Изобразить отрезок на экране
- •Void showSquare(void) // изобразить квадрат
- •5. Классы и шаблоны
- •Int size; // Количество элементов в массиве
- •Vector(int); // Конструктор класса vector
- •Имя_параиетризованного_класса
Имя_проиаводного_класса: : show ()
либо с использованием имени конкретного объекта:
Иня_объекта_производноро_класса. Show ()
В обоих случаях выбор нужной функции выполняется при написании исходного текста программы и не изменяется после компиляции. Такой режим называется ранним или статическим связыванием.
Большую гибкость (особенно при использовании уже готовых библиотек классов) обеспечивает позднее (отложенное), или динамическое связывание, которое предоставляется механизмом виртуальных функций. Любая нестатическая функция базового класса может быть сделана виртуальной, если в ее объявлении использовать спецификатор virtual. Прежде чем объяснить преимущества динамического связывания, приведем пример. Опишем в базовом классе виртуальную функцию и введем два производных класса, где определим функции с такими же прототипами, но без спецификатора virtual. В следующей программе в базовом классе base определена виртуальная функция void vfun(int). В производных классах dirl, dir2 эта функция подменяется (override), т.е. определена по-другому:
//Р10-06.СРР - виртуальная функция в базовом классе
#include <iostream.h>
#include <conio.h>
struct base {
virtual void vfun(int i) { cout << "\nbase::i - " << i; }
};
struct dir1: public base {
void vfun (int i){ cout << "\ndirl::i - " << i; }
};
struct dir2: public base {
void vfun (int i){ cout << "\ndir2::i = " << i; }
};
Void main(void)
{
base B, *bp = &В;
dir1 D1, *dp1 = &D1;
dir2 D2, *dp2 = &D2;
bp->vfun(l); // Печатает: base::i = 1
dpl->vfun(2); // Печатает: dirl::i = 2
dp2->vfun(3); // Печатает: dir2::i = 3
bp = &D1;
bp->vfun(4) ; // Печатает:dir1::i = 4
bp = &D2;
bp->vfun(5); // Печатает: dir2::i = 5
}
Результат выполнения программы:
base::i = 1
dirl::i = 2 dir2::i = 3 dirl::i = 4
dir2::i = 5
В примере надо обратить внимание на доступ к функциям vfun () через указатель bр на базовый класс. Когда bр принимает значение адреса объекта класса base, то вызывается функция из базового класса. Затем bр последовательно присваиваются значения ссылок на объекты производных классов &Dl, &D2, и выбор соответствующего экземпляра функции vfun () каждый раз определяется именно объектом. Таким образом, интерпретация каждого вызова виртуальной функции через указатель на базовый класс зависит от значения этого указателя, т.е. от типа объекта, для которого выполняется вызов. В противоположность этому интерпретация вызова через указатель невиртуальной функции зависит только от типа указателя (это было показано в предыдущем примере с функцией fun()).
Виртуальными могут быть не любые функции, а только нестатические компонентные функции какого-либо класса. После того как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.
В производном классе нельзя определять функцию с тем же именем и с той же сигнатурой параметров, но с другим типом возвращаемого значения, чем у виртуальной функции базового класса. Это приводит к ошибке на этапе компиляции.
Если в производном классе ввести функцию с тем же именем и типом возвращаемого значения, что и виртуальная функция базового класса, но с другой сигнатурой параметров, то эта функция производного класса не будет виртуальной. В этом случае с помощью указателя на базовый класс при любом значении этого указателя выполняется обращение к функции базового класса (несмотря на спецификатор virtual и присутствие в производном классе похожей функции).
Сказанное иллюстрирует следующая программа:
//Р10-07.СРР - некоторые особенности виртуальных функций
#include <iostream.h>
#include <conio.h>
struct base {
virtual void f1(void)
{ cout << "\nbase::f1"; }
virtual void f2(void)
{ cout << "\nbase::f2"; }
virtual void f3(void)
{ cout « "\nbase::f3"; }
};
struct dir: public base { // Виртуальная функция:
void fl(void) { cout << "\ndir::fl"; } // Ошибка в типе функции:
// int f2(void) { cout << "\ndir::f2"; } // Невиртуальная функция:
void f3(int i) ( cout << "\ndir::f3::i = "<< i; }
};
void main (void) { base B, *pb = &B; dir D, *pd = &D; pb->fl(); pb->f2(); pb->f3(); pd->fl<); pd->f2();
// Ошибка при попытке без параметра вызвать
dir::f3(int): // pd->f3(); pd->f3(0); pb = &D; pb->fl(); pb->f2(); Pb->f3();
// Ошибочное употребление или параметра, или указателя:
//
pb->f3(3); }
Результат выполнения программы:
base::f1 base::f2 base::f3 dir::f1 base::f2 dir::f3::i = 0 dir::f1 base::f2 base::f3
Обратите внимание, что три виртуальные функции базового класса по-разному воспринимаются в производном классе. dir::fl() -виртуальная функция, подменяющая функцию base: :fl(). Функция base::f2() наследуется в классе dir так же, как и функция base::f3 (). Функция dir::f3(int) - совершенно новая компонентная функция производного класса, никак не связанная с базовым классом. Именно поэтому невозможен вызов f3 (int) через указатель на базовый класс. Виртуальные функции base::f2() и base::f3() оказались не переопределенными в производном классе dir. Поэтому при всех вызовах без параметров f3() используется только компонентная функция базового класса. Функция dir: :f3(int) иллюстрирует соглашение языка о том, что если у функции производного класса набор параметров отличается от набора параметров соответствующей виртуальной функции базового класса, то это не виртуальная функция, а новый метод производного класса.
Завершая рассмотрение примера, еще раз подчеркнем, что при подмене виртуальной функции требуется полное совпадение сигнатур, имен и типов возвращаемых значений функций в базовом и производном классах.
Как уже было упомянуто, виртуальной функцией может быть только нестатическая компонентная функция. Виртуальной не может быть глобальная функция. Функция, подменяющая виртуальную, в производном классе может быть описана как со спецификатором virtual, так и без него. В обоих случаях она будет виртуальной, т.е. ее вызов возможен только для конкретного объекта. Виртуальная функция может быть объявлена дружественной (friend) в другом классе.
Механизм виртуального вызова может быть подавлен с помощью явного использования полного квалифицированного имени. Таким образом, при необходимости вызова из производного класса виртуального метода (компонентной функции) базового класса употребляется полное имя. Например,
struct base {
virtual int f(int j) { return j * j ; }
};
struct dir: public base {
int f(int i) { /* Ваш код*/ return base::f(i * 2); }
};
Абстрактные классы. Абстрактным классом называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция. Чистой виртуальной называется компонентная функция, которая имеет следующее определение:
virtual тип имя_функции(список_формальных_параметров) = 0;
В этой записи конструкция "= 0" называется "чистый спецификатор". Пример описания чистой виртуальной функции:
virtual void fpure(void) = 0;
Чистая виртуальная функция "ничего не делает" и недоступна для вызовов. Ее назначение - служить основой для подменяющих ее функций в производных классах. Исходя из этого становится понятной невозможность создания самостоятельных объектов абстрактного класса. Абстрактный класс может использоваться только в качестве базового для производных классов. При создании объектов такого производного класса в качестве подобъектов создаются объекты базового абстрактного класса
Предположим, что имеется абстрактный класс:
class В {
protected:
virtual void func(char) = 0;