Скачать презентацию Использование виртуальных функций В С полиморфизм поддерживается Скачать презентацию Использование виртуальных функций В С полиморфизм поддерживается

Использование виртуальных функций.ppt

  • Количество слайдов: 109

Использование виртуальных функций Использование виртуальных функций

В С++ полиморфизм поддерживается и во время компиляции и во время выполнение программы. Перегрузка В С++ полиморфизм поддерживается и во время компиляции и во время выполнение программы. Перегрузка функций и операций – это пример полиморфизма во время компиляции. Поддержка полиморфизма во время выполнения программы достигается использованием указателей на базовые классы и виртуальные функции.

Указатели на производные типы Указатели на производные типы

Указатели на базовый тип и на производный тип зависимы. Пусть имеем базовый тип B_class Указатели на базовый тип и на производный тип зависимы. Пусть имеем базовый тип B_class и производный от B_class тип D_class. В С++ всякий указатель, объявленный как указатель на B_class, может быть также указателем на D_class.

B_class *p; // указатель на объект типа B_class B_obj; // объект типа B_class D_ob; B_class *p; // указатель на объект типа B_class B_obj; // объект типа B_class D_ob; // объект типа D_class После этого можно использовать следующие операции: p=&B_ob; // указатель на объект типа B_class p=&D_ob; // указатель на объект типа D_class

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

Например, это можно сделать так: ((D_class *)p)->f(); Здесь функция f() – член класса D_class. Например, это можно сделать так: ((D_class *)p)->f(); Здесь функция f() – член класса D_class. Внешние круглые скобки необходимы. Хотя указатель на базовый класс может быть использован как указатель на производный класс, обратное, неверно: нельзя использовать указатель на производный класс для присваивания ему адреса объекта базового класса. Кроме того, указатель увеличивается или уменьшается при операциях ++ и – относительно его базового типа.

Пример использования указателей на базовый класс. При этом каждый класс будет содержать функцию void Пример использования указателей на базовый класс. При этом каждый класс будет содержать функцию void show(void), свою в каждом классе.

#include <iostream. h> class Base { public: void show(void) { cout<<”В базовом классе Basen”; #include class Base { public: void show(void) { cout<<”В базовом классе Basen”; } }; class Derive: public Base { public: void show(void) { cout<<”В производном классе Deriven”; } }; class Derive 1: public Base { public: void show(void) { cout<<”В производном классе Derive 1n”; } }; class Derive 2: public Base { public: void show(void) { cout<<” В производном классе Derive 2n”; } };

void main(void) { Base bobj, *pb; Derive dobj, *pd; Derive 1 d 1 obj, void main(void) { Base bobj, *pb; Derive dobj, *pd; Derive 1 d 1 obj, *pd 1; Derive 2 d 2 obj, *pd 2; pb=&bobj; bobj. show(); // вызов функции pb->show(); // show() класса Base pd=&dobj; dobj. show(); // вызов функции pd->show(); // show() класса Derive pd 1=&d 1 obj; d 1 obj. show(); // вызов функции pd 1 ->show(); // show() класса Derive 1 pd 2=&d 2 obj; d 2 obj. show(); // вызов функции pd 2 ->show(); // show() класса Derive 2

pb=&dobj; // указателю на базовый класс присвоен адрес производного класса Derive pb->show(); // вызов pb=&dobj; // указателю на базовый класс присвоен адрес производного класса Derive pb->show(); // вызов show() класса Base!!!!! pb=&d 1 obj; // указателю на базовый класс присвоен адрес производного класса Derive 1 pb->show(); // вызов show() класса Base!!!!! ((Derive 1 *)pb)->show(); // Вызов функции show() класса Derive 1 pd 1=&d 2 obj; // указателю на базовый класс Derive 1 // присвоен адрес производного класса Derive 2 pd 1 ->show(); // вызов функции show() класса Derive 1 pd 1 ->Base: : show(); // вызов функции show() класса Base! pd 1 ->Derive: : show(); // вызов функции show() класса Derive! pd 1 ->show(); // вызов функции show() класса Derive 1 ((Derive 2 *)pb)->show(); // Вызов функции show() класса Derive 2 }

Виртуальные функции Виртуальные функции

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

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

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

Пример Пример

class Base { public: virtual void show(void) { cout << “В базовом класее Basen; class Base { public: virtual void show(void) { cout << “В базовом класее Basen; } }; class Derive: public Base{ void show(void) { cout<<”В производном классе Deriven”; } }; class Derive 1: public Derive{ void show(void) { cout<<”В производном классе Derive 1n”; } }; class Derive 2: public Derive 1{ void show(void) { cout<<”В производном классе Derive 2n”; } };

void main(void) { Base bobj, *pb; Derive dobj, *pd; Derive 1 d 1 obj, void main(void) { Base bobj, *pb; Derive dobj, *pd; Derive 1 d 1 obj, *pd 1; Derive 2 d 2 obj, *pd 2; pb=&bobj; pb->show(); // вызов функции show() класса Base pd=&dobj; // вызов функции pd->show(); // show() класса Derive виртуальность функции не исп. pd 1=&d 1 obj; // вызов функции pd 1 ->show(); // show() класса Derive 1 pd 2=&d 2 obj; // вызов функции pd 2 ->show(); // show() класса Derive 2

pb=&dobj; // указателю на базовый класс присвоен // адрес производного класса Derive pb->show(); // pb=&dobj; // указателю на базовый класс присвоен // адрес производного класса Derive pb->show(); // вызов show() класса Derive, исп. Механизм вирт. Функций pb=&d 1 obj; // указателю на базовый класс присвоен // адрес производного класса Derive 1 pb->show(); // вызов show() класса Derive 1 pd 1=&d 2 obj; // указателю на базовый класс Derive 1 присвоен // адрес производного класса Derive 2 pd 1 ->show(); // вызов функции show() класса Derive 2 // работает механизм виртуальных функций pd 1 ->Base: : show(); // Явный вызов функции show() класса Base! pd 1 ->Derive: : show(); // Явный вызов функции show() класса Derive! ((Derive 2 *)pb)->show(); // Вызов функции show() класса Derive 2 }

Замечания к использованию виртуальных функций Замечания к использованию виртуальных функций

Виртуальная функция должна быть членом класса. Она не может быть дружественной для класса, в Виртуальная функция должна быть членом класса. Она не может быть дружественной для класса, в котором определена, но дружественной к другому классу. Функция, которая объявлена виртуальной, остается виртуальной не зависимо от того, сколько производных классов построено. Если в рассмотренном примере класс Derive 1 будет производным классом для Derive, а не для Base, то функция show() в классе Derive 1 остается виртуальной. Если в производном классе функция не замещает виртуальную, так как она не объявлена или имеет другой прототип, то вызывается функция базового класса.

Виртуальные функции удобны для использования, так как в общем случае, базовый класс задает основной Виртуальные функции удобны для использования, так как в общем случае, базовый класс задает основной интерфейс, который будут иметь производные классы, а производные классы задают свой метод. Для описания полиморфизма часто используется понятие “один интерфейс, много методов”. Так как ООП позволяет создавать сложные программы, то при корректном построении производных классов все объекты, начиная с базового класса, доступны с помощью одного и того же метода, то нужно помнить только интерфейс. Отделение интерфейса от наполнения функций позволяют создавать классы библиотек (class libraries).

Вызов виртуальной функции реализуется как непрямой вызов по таблице виртуальных функций класса. Таблицу создает Вызов виртуальной функции реализуется как непрямой вызов по таблице виртуальных функций класса. Таблицу создает компилятор, а связывание происходит во время выполнения. Термин позднее связывание (late binding)

Пример использования виртуальных функций Пример использования виртуальных функций

Введем класс figure, который описывает плоскую фигуру, для вычисления площади которой достаточно двух измерений. Введем класс figure, который описывает плоскую фигуру, для вычисления площади которой достаточно двух измерений. В этом классе есть виртуальная функция show_area(), выводящая значение площади фигуры. На основе этого класса строятся другие классы triangle, rectangle, circle, для которых определена конкретная формула вычисления площади фигуры.

#include <iostream. h> class figure { protected: double x, y; public: void set_dim(double i, #include class figure { protected: double x, y; public: void set_dim(double i, double j=0) { x=i; y=j; } virtual void show_area() { cout<<”n Площадь не определена для этого класса n”; } };

class triangle: public figure{ public: void show_area() { cout<<”n треугольник с высотой “<< x<<” class triangle: public figure{ public: void show_area() { cout<<”n треугольник с высотой “<< x<<” и основанием “<

class circle: public figure{ public: void show_area() { cout<<”n круг с радиусом “<< x; class circle: public figure{ public: void show_area() { cout<<”n круг с радиусом “<< x; cout<<” имеет площадь “<< 3. 14159*x*x; <

void main(void) { figure f, *p; // объявление указателя на базовый тип triangle t; void main(void) { figure f, *p; // объявление указателя на базовый тип triangle t; // создание объектов производного типа rectangle s; circle c; p=&f; p->set_dim(1, 2); p->show_area(); p=&t; p->set_dim(3, 4); p->show_area(); p=&s; p->set_dim(5, 6); p->show_area(); p=&c; p->set_dim(7); p->show_area(); }

Чистые виртуальные функции и абстрактные типы Чистые виртуальные функции и абстрактные типы

Когда виртуальные функции вызываются из производного класса, но не замещаются, то вызывается соответствующая функция Когда виртуальные функции вызываются из производного класса, но не замещаются, то вызывается соответствующая функция базового класса (типа). Но часто во многих функциях нет смыслового определения виртуальной функции в базовом классе. При создании библиотек классов для виртуальных функций еще не известно, будет ли смысловое значение в контексте базового класса. Есть два способа решения этой проблемы. Первый способ – выдача предупреждающего сообщения (не всегда приемлем). Другое решение проблемы – использование в С++ чистых виртуальных функций.

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

Форма определения чистой виртуальной функции следующая: virtual тип имя-функции(список параметров) = 0; тип – Форма определения чистой виртуальной функции следующая: virtual тип имя-функции(список параметров) = 0; тип – возвращаемый тип функции; =0 - признак чистой виртуальной функции.

Пример, содержащий использование чистой виртуальной функции Пример, содержащий использование чистой виртуальной функции

#include <iostream. h> class figure { protected: double x, y; public: void set_dim(double i, #include class figure { protected: double x, y; public: void set_dim(double i, double j=0) { x=i; y=j; } virtual void show_area() =0; }; class triangle: public figure{ public: void show_area() { cout<<”n треугольник с высотой “<< x<<” и основанием “<

class rectangle: public figure{ public: void show_area() { cout<<”n прямоугольник со сторонами “<< x<<” class rectangle: public figure{ public: void show_area() { cout<<”n прямоугольник со сторонами “<< x<<” и “<

void main(void) { figure *p; // объявление указателя на базовый тип, создать объект нельзя!! void main(void) { figure *p; // объявление указателя на базовый тип, создать объект нельзя!! triangle t; // создание объектов производного типа rectangle s; circle c; p=&f; p->set_dim(1, 2); p->show_area(); p=&t; p->set_dim(3, 4); p->show_area(); p=&s; p->set_dim(5, 6); p->show_area(); p=&c; p->set_dim(7); p->show_area(); }

Объявление функции show_area() чистой виртуальной требует от всех производных классов собственных наполнений этой функции. Объявление функции show_area() чистой виртуальной требует от всех производных классов собственных наполнений этой функции. Класс, имеющий по крайней мере одну чистую виртуальную функцию, называется абстрактным классом (abstract class). Абстрактные классы имеют одну важную особенность: может не быть объектов этого класса.

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

Производные классы и их конструкторы и деструкторы Производные классы и их конструкторы и деструкторы

Базовый и производные классы могут иметь конструкторы и деструкторы. Рассмотрим, в каком порядке вызываются Базовый и производные классы могут иметь конструкторы и деструкторы. Рассмотрим, в каком порядке вызываются эти функции. Этот вопрос возникает и при множественном наследовании.

Пример Пример

#include <iostream. h> class Base{ public: Base () { cout<<”n Конструктор класса Base n”; #include class Base{ public: Base () { cout<<”n Конструктор класса Base n”; } ~Base () { cout<<”n Деструктор класса Base n”; } }; class Derive 1: public Base{ public: Derive 1 () { cout<<”n Конструктор производного класса Derive 1 n”; } ~Derive 1 () { cout<<”n Деструктор производного класса Derive 1 n”; } }; class Derive 2: public Derive 1{ public: Derive 2 () { cout<<”n Конструктор производного класса Derive 2 n”; } ~Derive 2 () { cout<<”n Деструктор производного класса Derive 2 n”; } };

void main() { Derive 1 d 1; cout<<”n”; Derive 2 d 2; cout<<”n”; } void main() { Derive 1 d 1; cout<<”n”; Derive 2 d 2; cout<<”n”; } В программе создаются объекты типа Derive 1 и Derive 2, при этом выполняются конструкторы и деструкторы в следующей последовательности.

На экране: Конструктор класса Base Конструктор производного класса Derive 1 Конструктор производного класса Derive На экране: Конструктор класса Base Конструктор производного класса Derive 1 Конструктор производного класса Derive 2 Деструктор производного класса Derive 1 Деструктор производного класса Base

Конструкторы и деструкторы при множественном наследовании Конструкторы и деструкторы при множественном наследовании

В С++ разрешено при создании производного класса пользоваться несколькими базовыми классами. При объявлении производного В С++ разрешено при создании производного класса пользоваться несколькими базовыми классами. При объявлении производного класса базовые классы перечисляются через запятую. При создании объекта конструкторы выполняются в порядке следования базовых классов слева направо.

Пример Пример

#include <iostream. h> class Base 1{ public: Base 1() { cout<<”n Конструктор класса Base #include class Base 1{ public: Base 1() { cout<<”n Конструктор класса Base 1 n”; } ~Base 1 () { cout<<”n Деструктор класса Base 1 n”; } }; class Base 2{ public: Base 2() { cout<<”n Конструктор класса Base 2 n”; } ~Base 2 () { cout<<”n Деструктор класса Base 2 n”; } }; class Derive: public Base 1, public Base 2{ public: Derive () { cout<<”n Конструктор производного класса Derive n”; } ~Derive () { cout<<”n Деструктор производного класса Deriven”; } };

void main() { Base 1 b 1; cout<<”n”; Base 1 b 2; cout<<”n”; Derive void main() { Base 1 b 1; cout<<”n”; Base 1 b 2; cout<<”n”; Derive d; cout<<”n”; } Деструкторы выполняются в порядке, обратном по отношению к конструкторам.

Виртуальные базовые классы Виртуальные базовые классы

При множественном наследовании базовый класс не может быть задан в производном классе более одного При множественном наследовании базовый класс не может быть задан в производном классе более одного раза: class Derive: Base, Base {…}; // ошибка В то же время базовый класс может быть передан производному классу более одного раза косвенно: class X: public Base{…}; class Y: public Base{…}; class Derive: public X, public Y{…};

Этот пример соответствует схеме наследования, когда каждый объект класса Derive будет иметь два подобъекта Этот пример соответствует схеме наследования, когда каждый объект класса Derive будет иметь два подобъекта класса Base. Чтобы избежать неоднозначности при обращении к членам базового объекта Base, можно объявить этот базовый класс виртуальным. Для этого используется то же зарезервированное слово virtual, что и при объявлении виртуальных функций. class X: virtual public Base{…}; class Y: virtual public Base{…}; class Derive: public X, public Y{…}; Теперь класс Derive имеет только один подобъект класса Base.

Операции динамического выделения памяти new и delete Операции динамического выделения памяти new и delete

В С для динамического выделения и освобождения памяти используются функции malloc() и free(), а В С для динамического выделения и освобождения памяти используются функции malloc() и free(), а для определения размера необходимой памяти – операция sizeof. В C++ используются две операции – new и delete. Их форма использования: pointer_var = new var_type; delete pointer_var; pointer_var – указатель - типа var_type;

Операция new выделяет соответствующее место для переменной в соответствующей области памяти и возвращает адрес Операция new выделяет соответствующее место для переменной в соответствующей области памяти и возвращает адрес выделенного места. Неуспешная попытка – возвращение нулевого указателя NULL. Операция delete освобождает соответствующую память, на которую указывает pointer_var. Удобство операции new состоит в том, что операция сама определяет размер переменной var_type автоматически и возвращает указатель, уже преобразованный к этому типу.

Пример использования этих операций Пример использования этих операций

#include <iostream. h> void main(void) { int *p; p=new int; // выделение памяти под #include void main(void) { int *p; p=new int; // выделение памяти под целое if(!p) { cout<<”n Недостаточно памяти!n”; return 1; } *p=20; cout<

Пример работы с динамически выделенной памятью под массив Пример работы с динамически выделенной памятью под массив

#include <iostream. h> void main(void) { int *p; unsigned int size; cout<<”n Введите размер #include void main(void) { int *p; unsigned int size; cout<<”n Введите размер массива: ”; cin>>size; p=new int[size]; if(!p) { cout<<”n Недостаточно памяти!n”; return 1; } for(int i=0; i

Динамическое выделение памяти целесообразно для выделения памяти под большие объекты, массивы, особенно под массивы Динамическое выделение памяти целесообразно для выделения памяти под большие объекты, массивы, особенно под массивы неизвестного заранее размера. Часто динамическое выделение памяти используется в конструкторах класса, элементом которого является массив (массивы). При этом для выделения памяти под объект также может использоваться динамическое выделение памяти.

Пример класса queue с динамическим выделением памяти под массив Пример класса queue с динамическим выделением памяти под массив

#include <iostream. h> class queue { int *q; int sloc, rloc; unsigned size; public: #include class queue { int *q; int sloc, rloc; unsigned size; public: queue(int sz); // Конструктор ~queue(void); // Деструктор void qput(int i); int qget(void); }; queue: : queue(int sz) { size=sz; if(!(q=new int[size])) { cout<, ”n Недостаточно памяти!n”; return 1; } queue: : ~queue(void) { delete q; cout<<”n Очередь разрушена!”; }

void queue: : qput(int i) { if(sloc==size) { cout<<”n Очередь полна!n”; return; } q[sloc++]=i; void queue: : qput(int i) { if(sloc==size) { cout<<”n Очередь полна!n”; return; } q[sloc++]=i; } int queue: : qget(void) { if(sloc==rloc) { cout<<”n Очередь пуста!n”; return 0; } return q[rloc++]; }

void main() { queue a(5), b(100); // объявили два объекта a. qput(10); b. qput(19); void main() { queue a(5), b(100); // объявили два объекта a. qput(10); b. qput(19); a. qput(20); b. qput(1); cout<

pq = new queue (s); // динамическое создание объекта // именно сейчас вызывается конструктор pq = new queue (s); // динамическое создание объекта // именно сейчас вызывается конструктор queue if (!pq) { cout<<”n недостаточно памяти n”; return 0; } else cout<<”n Объект класса queue создан n”; for(int i=0; iqput(2*i+1); // заполнение очереди for(i=0; iqget()<<” “; cout<

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

При программировании на С++ также типичной является ситуация, когда динамически создается объект производного класса, При программировании на С++ также типичной является ситуация, когда динамически создается объект производного класса, указатель используется базового класса.

Пример Пример

#include <iostream. h> // динамическое выделение памяти через указатель на базовый класс class Base #include // динамическое выделение памяти через указатель на базовый класс class Base { public: Base() { cout<<”n Конструктор класса Basen”; } ~Base() { cout<<”n Деструктор класса Basen”; } }; class Derive 1: public Base { public: Derive 1() { cout<<”n Конструктор производного класса Derive 1n”; } ~Derive 1() { cout<<”n Деструктор производного класса Derive 1n”; } }; class Derive 2: public Derive 1 { public: Derive 2() { cout<<”n Конструктор производного класса Derive 2n”; } ~Derive 2() { cout<<”n Деструктор производного класса Derive 2n”; } };

void main(void) { Base *pb=new Derive 2; if(!pb) { cout<<”n Недостаточно памяти!n”; return 1; void main(void) { Base *pb=new Derive 2; if(!pb) { cout<<”n Недостаточно памяти!n”; return 1; } cout<

В программе было вызвано три конструктора и всего один деструктор. При удалении объекта через В программе было вызвано три конструктора и всего один деструктор. При удалении объекта через указатель на базовый класс вызывается лишь деструктор базового класса. Если бы в конструкторах выделялась динамическая память при создании объекта, эта память при разрушении объекта не освобождалась бы корректно. Эта проблема решается через использование виртуального деструктора.

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

Пример Пример

#include <iostream. h> // динамическое выделение памяти через указатель на базовый класс class Base #include // динамическое выделение памяти через указатель на базовый класс class Base { public: Base() { cout<<”n Конструктор класса Basen”; } virtual ~Base() { cout<<”n Деструктор класса Basen”; } }; class Derive 1: public Base { public: Derive 1() { cout<<”n Конструктор производного класса Derive 1n”; } ~Derive 1() { cout<<”n Деструктор производного класса Derive 1n”; } }; class Derive 2: public Derive 1 { public: Derive 2() { cout<<”n Конструктор производного класса Derive 2n”; } ~Derive 2() { cout<<”n Деструктор производного класса Derive 2n”; } };

void main(void) { Base *pb=new Derive 2; if(!pb) { cout<<”n Недостаточно памяти!n”; return 1; void main(void) { Base *pb=new Derive 2; if(!pb) { cout<<”n Недостаточно памяти!n”; return 1; } cout<

Шаблоны классов и функций Шаблоны классов и функций

В языке С++ предусмотрена еще одна реализация полиморфизма – шаблоны функций (Function Templates) и В языке С++ предусмотрена еще одна реализация полиморфизма – шаблоны функций (Function Templates) и шаблоны классов (Class Templates).

Шаблоны функций Шаблоны функций

При перегрузке функций рассматривали семейство функций sqr_it() с различными типами аргументов. Эти функции возвращали При перегрузке функций рассматривали семейство функций sqr_it() с различными типами аргументов. Эти функции возвращали квадрат аргумента и тип возвращаемого значения совпадал с типом аргумента. Для каждого типа описывалось свое тело функции, причем отличались функции только типом аргумента и типом возвращаемого значения. Шаблоны функций позволяют использовать в качестве аргумента тип переменной. template T sqr_it(T x) { return x*x; }

Любой тип данных, а не только определенный как класс может использоваться применении этих функций. Любой тип данных, а не только определенный как класс может использоваться применении этих функций. Над типом, для которого будет вызываться функция, определенная шаблоном, должны быть определены операции над переменными типа Т, которые используются в теле функции. Шаблонные функции могут использоваться совместно с функциями, определенными обычным образом, с тем же именем.

Пример Пример

#include <iostream. h> template <class T> T sqr_it(T x) { return x*x; }; void #include template T sqr_it(T x) { return x*x; }; void main(void) { int i=10; float f=1. 1; long l=2345; long double id=123. 1 e+123; cout<<”int”<

В шаблоне функции может использоваться необязательно один тип как параметр шаблона. Например, функции max В шаблоне функции может использоваться необязательно один тип как параметр шаблона. Например, функции max (a, b) с различными типами аргументов могут быть определены следующим образом: template T max(T 1 x, T 2 y) { return (x>y)? x: y; }; Возвращаемый тип T 1. Для того, чтобы функции возвращали результат без потери значения, при вызове функции желательно переменную или константу “старшего” типа использовать в качестве первого аргумента. ы

Пример Пример

include <iostream. h> template <class T 1, class T 2> T maximum(T 1 x, include template T maximum(T 1 x, T 2 y) { if(x>=y) return x; return y; }; void main(void) { int i=10; double d=1. 2345; long l=123456; long double ld=1. 1 e+123; cout<<”максимум из “<

Шаблоны классов Шаблоны классов

Шаблоны классов позволяют построить отдельные классы аналогично шаблонам функций. Шаблон класса задает параметризованный тип. Шаблоны классов позволяют построить отдельные классы аналогично шаблонам функций. Шаблон класса задает параметризованный тип. Имя шаблона класса должно быть уникальным и не может относиться к какому-либо шаблону, классу, функции, объекту и т. д. В качестве параметра при задании шаблона класса может использоваться не только тип, но и другие параметры, например целочисленные значения (но эти параметры должны быть константными выражениями).

Преобразуем класс queue, задав этот класс шаблоном. Параметрами шаблона будет тип массива, входящего в Преобразуем класс queue, задав этот класс шаблоном. Параметрами шаблона будет тип массива, входящего в класс queue, и размер size этого массива.

#include <iostream. h> template <class T, int size> class queue { T *q; int #include template class queue { T *q; int sloc, rloc; public: queue(void); // конструктор ~queue(void); // деструктор void qput(T i); T qget(void); }; template // следует обратить внимание на синтаксис queue: : queue(void) // описание функции-конструктора { if (!(q=new T[size])) { cout<<”Недостаточно памяти n”; return; } sloc=rloc=0; cout<<”Очередь размера ”<

template <class T, int size> queue<T, size>: : ~queue(void) // описание функции-деструктора { delete template queue: : ~queue(void) // описание функции-деструктора { delete q; cout<<” Очередь разрушена n”; } template void queue: : qput(T i) { if(sloc= =size) { cout<<” Очередь полна n”; return; } q[sloc++]=i; } template T queue: : qget(void) { if(rloc= =sloc) { cout<<” Очередь пуста n”; return 0; } return q[rloc++]; }

void main(void) { queue<int, 5> a; queue<double, 200> b; // объявление двух объектов класса void main(void) { queue a; queue b; // объявление двух объектов класса queue с массивами разных типов и разных размеров a. qput(10); b. qput(1. 234); a. qput(1); b. qput(2. 3456); cout< *pq; pq = new queue;

// Динамическое создание объекта, именно сейчас вызывается конструктор queue if((!pq) { cout<<”n Недостаточно памяти // Динамическое создание объекта, именно сейчас вызывается конструктор queue if((!pq) { cout<<”n Недостаточно памяти n”; return 0; } else cout<<”n Объект класса queue создан n”; for (int i=0; iqput((i/2. 0+i); // заполнение очереди for ( i=0; iqget()<<” “; cout<

Статические члены класса Статические члены класса

В С++ можно использовать статические члены класса, которые являются общими для всех объектов данного В С++ можно использовать статические члены класса, которые являются общими для всех объектов данного класса. Статические члены класса объявляются с атрибутом памяти static. Изменив значение статического члена класса в одном объекте, получаем изменившееся значение в других объектах данного класса. Объявление статических членов – данных класса внутри объявления класса не является описанием, т. е. Под эти данные не выделяется память. Описание данных должно быть выполнено дополнительно. При использовании статического элемента все объекты ссылаются на одно и то же место в памяти.

В С++ можно использовать статические членыфункции класса. Они не получают указатель this, соответственно эти В С++ можно использовать статические членыфункции класса. Они не получают указатель this, соответственно эти функции не могут обращаться к нестатическим членам класса. К статическим членам класса статические функции-члены класса могут обращаться посредством операции точка или ->. К статическим данным – членам класса и статическим функциям-членам класса можно обращаться, даже если не создано ни одного объекта данного класса, надо только использовать полное имя члена класса.

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

#include <iostream. h> class st { static int count 1; static int count 2; #include class st { static int count 1; static int count 2; public: static void show_count(void); st(void); // конструктор ~st(void); // деструктор }; st: : st(void) { count 1++; count 2++; } st: : ~st(void) { count 2 --; } void st: : show_count(void) { cout<<”Создано объектов: ”<

void main(void) { st: : show_count(); // вызов функции до создания объектов st a, void main(void) { st: : show_count(); // вызов функции до создания объектов st a, b, c, *p; a. show_count(); { st x, y, z; st: : show_count(); // эти два вызова z. show_count(); // дадут одинаковый результат } p=new st; st: : show_count(); delete p; st: : show_count(): }

Локальные классы Локальные классы

Класс объявленный внутри функции называется локальным классом (local class). Функция, в которой объявлен локальный Класс объявленный внутри функции называется локальным классом (local class). Функция, в которой объявлен локальный класс, не имеет специального доступа к членам локального класса. Локальный класс не может иметь статических членов – данных. Объект локального класса может быть создан только внутри функции, области действия объявления класса. Все функциичлены локального класса должны быть объявлены внутри объявления класса, т. е. быть подставляемыми функциями

Пример использования локальных классов Пример использования локальных классов

#include <iostream. h> void f(void); void main(void) { f(); } void f(void) { class #include void f(void); void main(void) { f(); } void f(void) { class local_class { int who; public: local_class(int a) { who=a; cout<<”Конструктор локального класса “<

Вложенные классы Вложенные классы

В С++ допустимо использование вложенных классов (nested). Вложенный класс находится в области действия объемлющего В С++ допустимо использование вложенных классов (nested). Вложенный класс находится в области действия объемлющего класса, соответственно объекты этого класса могут использоваться как члены этого класса или в функциях – членах класса. Функции – члены класса и статические члены вложенного класса могут быть описаны в глобальной области действия.

Пример использования вложенных классов Пример использования вложенных классов

class C { class nested_class { int who; public: nested_class(int a); ~nested_class(void); }; public: class C { class nested_class { int who; public: nested_class(int a); ~nested_class(void); }; public: C(int b); { nested_class obj(b*b); cout<<”Конструктор класса С”<