- •1. Объектно-ориентированное программирование как методология проектирования программных средств.
- •1.2. Объект
- •Iarray a(10), b(25), c(45);
- •Int main(void)
- •2.1. Перегрузка имен функций
- •Int print(int);
- •Void print(int);
- •Int (*matrix)[10];
- •Void main(void)
- •Void incr(int &aa)
- •3.2 Объявление класса.
- •Имя_класса имя объекта;
- •Имя объекта. Имя элемента
- •Имя_объекта. Имя_метода( );
- •Имя класса *this;
- •Имя_класса (список формальных параметров)
- •Листинг 3.4
- •V_3d( double &X, double &y,double &z)
- •Void main(void)
- •Х:: х(const х&)
- •V_3d::v_3d(const v_3d& v1)
- •Void v_3d::print()
- •Сlass X
- •Void main (void)
- •4. Перегрузка операций
- •V_3d(double x1,double y1,double z1)
- •Void print();
- •Void main(void)
- •Void v_3d::print()
- •4.3.1. Использование дружественных функций при перегрузке операций
- •Void print();
- •Void main(void)
- •Void v_3d::print()
- •4.3.2.Особенности использования дружественных функций
- •Имя объекта. Имя_функции
- •Void f1( X& );
- •Void f2 ( X* );
- •Void main(void)
- •5. Производные классы
- •Int Bfunc( );
- •Int Xfunc( );
- •Int Efunc( X & x1 );
- •Istream
- •Iostream
- •Ifstream
- •С помощью манипуляторов – специальных операций, вставляемых непосредственно в поток вывода.
- •Int width( int w);
- •Int precision( int p);
- •Void main(void)
- •Void open(char* filename, int mode, int access);
- •If(!fout) {
- •7. Виртуальные функции
- •Circle a;
- •A.Build();
- •Virtual double a(double X)
- •Void main (void)
- •Void Graphics Object :: Build() { };
- •8.Шаблоны функций и классов
- •Void main(void)
- •Void Swap ( SwapType &X, SwapType &y)
- •Void main(void)
- •Int a, double b;
- •Int sloc,rloc;//начало и конец очереди
- •Void qput(t I); // помещение элемента в очередь
- •Int main(void)
- •1.Какой принцип ооп реализуется с помощью шаблонных функций и классов?
- •2.Когда целесообразно использовать шаблоны функций? Каков общий вид объявления шаблона функции?
Circle a;
Вызов функции-метода для отображения на экране созданного объекта:
A.Build();
При этом будет вызвана функция Graphics Object::Build() базового класса, а не функция производного класса Circle:: Build(), так как версия базового класса доступна производному. Причиной этого является то, что без использования механизма виртуальных функций вызовы Build(), присутствующие в программе, будут связаны уже на этапе компиляции с определением Build() из базового класса - это называется статическим или ранним связыванием имен.
Для того чтобы при вызове A.Build() вызывался метод из производного класса следует в базовом классе объявить функцию Build() как виртуальную:
class Graphics Object {
// элементы – данные класса
……………
public:
// методы класса, необходимые для работы с любым графическим объектом
virtual void Build(); // построить
virtual void Display(); // показать на экране
// другие методы
…………..
};
В производном классе повторять слово virtual нет необходимости, хотя ошибки при этом не возникает.
7.2 Полиморфизм и позднее связывание
Если функция в базовом классе объявлена как виртуальная, то ее вызовы будут обрабатываться методом “позднего” (динамического) связывания. Ключевое слово virtual предписывает компилятору генерировать некоторую дополнительную информацию о функции.
Реализация метода динамического связывания осуществляется следующим образом. Каждый объект, помимо полей данных, описанных для данного класса, содержит ссылку на таблицу адресов виртуальных методов своего класса. Такую таблицу называют таблицей виртуальных методов или vtable. При вызове виртуального метода его адрес извлекается из соответствующей данному объекту таблицы – таким образом, вызывается то “что надо”.
По существу, виртуальные функции реализуют идею “один интерфейс, множество методов”, которая лежит в основе полиморфизма. Виртуальная функция внутри базового класса задает интерфейс этой функции. Каждое переопределение виртуальной функции в производном классе определяет ее реализацию, связанную со спецификой производного класса.
В листинге 7.1 приведен пример программы, показывающей, как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y.
Листинг 7.1.
#include <iostream.h>
// Базовый класс
class X
{
public:
Virtual double a(double X)
{ return x*x;}
double B(double x)
{ return A(x)/2; }
};
// Производный класс
class Y :public X
{
public :
double A(double x)
{ return x*x*x;}
};
Void main (void)
{
Y y;
cout <<y.B(3) <<endl;
}
Эта программа выведет правильное значение 13.5, потому что в результате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции А во время выполнения программ будет использована замещающая функция Y::A.
7.3 Указатели на классы и виртуальные функции
Специфической особенностью указателей на классы в С++, тесно связанной с виртуальными функциями, является то, что указатель, объявленный в качестве указателя на базовый класс, также может использоваться, как указатель на любой класс, производный от этого базового.
Например, являются правильными следующие операторы:
base *p; // указатель базового класса
base base_ob; // объект базового класса
derived derived_ob; // объект производного класса
p=&base_ob; // р указывает на объект базового класса
p=& derived_ob; // р указывает на объект производного класса
В программе из листинга 7.1 виртуальная функция вызывается так же, как и любая друга функция –метод. Однако виртуальная функция может вызываться и через указатель на класс.
Если указатель базового класса указывает на объект производного класса, который содержит виртуальную функцию, и для которого виртуальная функция вызывается через этот указатель, то компилятор определяет, какую версию виртуальной функции вызвать на основе типа объекта, на который указывает указатель.
Пример программы, в которой вызов виртуальной функции производится через указатель, показан в листинге 7.2.
Листинг 7.2
#include <stdio.h>
//базовый класс
class Base
{
public:
//обьявление виртуальной функции
virtual void Method()
{
printf("Базовый\n");
};
};
//производный класс
class Nas:public Base
{
public:
void Method() // virtual здесь допустимо, но избыточно
{
printf(" Производный \n");
};
};
// Внешняя функция содержит в качестве параметра указатель
// на базовый класс
void fn(Base* basePtr);
void main(void)
{
Base base;
Nas nas;
printf("Базовый -\n");
fn(&base); // вызов из функции fn виртуальной функции из базового класса
printf("Производный -\n");
fn(&nas); // вызов из функции fn виртуальной функции из производного класса
}
// определение внешней функции
void fn(Base* basePtr)
{
basePtr->Method(); // вызов функции через указатель на базовый класс
}
Результаты работы этой программы имеют вид:
Базовый -
Базовый
Производный -
Производный
В приведенной программе при вызове внешней функции fn() в качестве аргумента ей передается первый раз &base – адрес объекта базового класса, а второй раз &nas - адрес объекта производного класса. В определении же функции fn() стоит указатель на базовый класс. Это значит, что объект производного класса рассматривается как объект его базового класса. Таким образом, тип адресуемого через указатель объекта определяет, какая версия виртуальной функции вызывается, причем это решение принимается во время выполнения программы.
7.4. Особенности работы с виртуальными функциями
При использовании механизма виртуальных функций нельзя изменять тип возвращаемого значения и список аргументов функции. Если две функции в базовом и производном классах с одним и тем же именем имеют разные аргументы и разные типы возвращаемых значений, причем в базовом классе функция объявлена как виртуальная, то механизм виртуальных функций игнорируется.
Например:
// Базовый класс
class B {
public:
virtual void vf1();
virtual void vf2();
virtual void vf3();
void f();
};
// Производный класс
class D:public B {
public:
virtual void vf1(); // виртуальная функция, причем слово virtual избыточно
void vf2( int ); // не virtual, т.к. использован другой список параметров
char vf3(); // не virtual, т.к. изменен тип возврата
void f(); // не virtual, т.к. она не объявлена в базовом классе как virtual
}
7.5 Чисто виртуальные функции и абстрактные классы
Виртуальная функция, объявленная в базовом классе, часто никогда не используется для объектов базового класса, т.е. не имеет определения.
Вернемся к классу Graphics Object, определение которого было приведено в п.7.1. В нем была объявлена виртуальная функция Build() для построения геометрической фигуры. Эта функция должна быть первый раз объявлена и, следовательно, определена в базовом классе. Но абстрактную геометрическую фигуру построить нельзя, следовательно определение тела этой функции должно быть пустым:
