Полиморфизм. Полиморфизм - свойство класса, позволяющее использовать одно и то же имя для обозначения различных по сути действий для одного или нескольких классов.
Полиморфизм - свойство класса, позволяющее использовать одно и то же имя для обозначения различных по сути действий для одного или нескольких классов.
Формы полиморфизма:
1. Перегрузка стандартных функций и стандартных операций.
2. Реализация виртуальных функций.
3. Использование шаблонов.
1.2.1) Перегрузка стандартных операций.
Одной из привлекательных особенностей языка Си++ является возможность распространения действия стандартных операций на операнды, для которых эти операции первоначально в языке не предполагались. Например, если S1 и S2 – символьные строки, то их конкатенацию (соединение) удобно было бы обозначить как S1+ S2. Однако бинарная операция + в обычном контексте языка Си++ предназначена для арифметических операндов и не предусматривает строковых операндов. Никакой возможности распространить действие стандартной операции + на строки в виде символьных массивов или строковых констант в языке Си++ нет. Однако, если определить S1 и S2 как объекты некоторого класса, например, введённого в классе stroka, то для них можно ввести операцию +, выполняемую по таким правилам, которые заранее выбрал программист. Для этих целей язык Си++ позволяет распространить действие любой стандартной операции на новые типы данных - механизм перегрузки стандартных операций.
Чтобы появилась возможность использовать стандартную для языка Си++ операцию (например, «+» или «*») с необычными для неё данными, необходимо специальным образом определить её новое поведение. Это возможно, если хотя бы один из операндов является объектом некоторого класса, то есть введённого пользователем типа. В этом случае применяется механизм, во многом схожий с механизмом определения функций. Для распространения действия операции на новые пользовательские типы данных программист определяет специальную функцию, называемую «операция – функция». Формат определения операции – функции:
<тип возвращаемого значения> operator <знак операции>(<спецификация параметров операции – функции>)
{
<операторы тела операции – функции>
}
При необходимости может добавляться и прототип операции – функции с таким форматом:
<тип возвращаемого значения> operator <знак операции>(<спецификация параметров операции – функции>)
И в прототипе, и в заголовке определения операции – функции используется ключевое слово operator,вслед за которым помещён знак операции.
Например, для распространения действия бинарной операции «*» на объекты класса Т может быть введена функция с заголовком
Т operator *(Тx, Тy)
Определённая таким образом операция (в нашем примере операция «звёздочка») называется перегруженной, а сам механизм – перегрузкой или расширение действия стандартной операций языка Си++.
Помимо операций перегружать можно ещё и функции. Функция считается перегруженной, если сигнатуры и типы возвращаемых результатов нескольких одноимённых функций различны. Функция называется переобъявленной, если она объявляется с именем другой функции, но с другим типом возвращаемого результата и другой сигнатурой. Сигнатурой в функции называется множество, включающее: состав параметров функции, последовательность параметров функции и типы параметров функции.
При вызове таких функций происходит обращение к той функции, у которой список формальных параметров совпадает со списком фактических параметров по смыслу и по содержанию. Ля разработки перегруженных функций описывается несколько функций с одним и тем же именем, но разными сигнатурами.
Рассмотрим конкретный пример по перегрузке стандартных операций.
Описать класс одномерных массивов, содержащих 10 элементов.
Свойства класса – массив;
Методы – ввод-вывод массива;
Перегружаемые операции – сложение массивов, операция присваивания
Опишем класс в головном файле mas.h
class m10
{
int a[10];
public:
void get_a ();
void print ();
m10 operator + (m10 v1);
);
};
void m10 : : get_a ()
{
int i;
for (i=0; i<=10-1; i++)
{ printf (“?”); scanf ( “ %i”, &a[i]); }
}
void m10 : : print ()
{
int i;
for (i=0; i<=10-1; i++)
{ printf (“ %i ”, a[i]); }
}
m10 m10 : : operator + (m10 v1)
{
int i; m10 t;
for (i=0; i<=10-1; i++)
t. a[i]= a[i] + v1. a[i]; // t будет содержать сумму двух массивов
return t;
}
m10 m10 : : operator = (m10 v)
{
int i;
for (i=0; i<=10-1; i++)
a[i] = v. a[i];
return *this;
}
гдеthis –это зарезервированное слово, обозначающее указатель на объект, предшествующий знаку перегружаемой операции. В данном случае «возвращается» *this. Это значение по адресу указателя this, то есть возвращается объект, предшествующий знаку операции.
Программа
#include “mas.h”
int main ()
{
m10 A, B, C;
A.get_a();
B.get_a();
C=A+B;
C.print ();
return 1;
}
1.2.2) Виртуальные функции.
К механизму виртуальных функций обращаются в тех случаях, когда в базовый класс необходимо поместить функцию, которая должна по-разному выполняться в производных классах. Точнее, по-разному должна выполняться не единственная функция из базового класса, а в каждом производном классе требуется свой вариант этой функции.
Например, базовый класс может описывать фигуру на экране без конкретизации её вида, а производные классы (треугольник, эллипс и т.п.) однозначно определяют её формы и размеры. Если в базовом классе ввести функцию для изображения фигуры на экране, то выполнение этой функции будет возможно только для объектов каждого из производных классов, определяющих конкретные изображения.
Классы, включающие такие функции, называются полиморфными.
1.2.3) Классы и шаблоны.
Шаблоны, которые иногда называют родовыми или параметризованными типами, позволяют создавать (конструировать) семейства родственных функций и классов.
Шаблон семейства функций определяет потенциально неограниченное множество родственных функций. Он имеет следующий вид:
template <список параметров шаблона> определение функции
Здесь угловые скобки являются неотъемлемым элементом определения. Список параметров шаблона должен быть заключён именно в угловые скобки.
Аналогично определяется шаблон семейства классов:
template <список параметров шаблона> определение класса
Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов.
Определение шаблона может быть только глобальным.
Рассмотрим векторный класс ( в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта.
Следующий шаблон позволяет автоматически формировать классы векторов с указанными свойствами:
// TEMPLATE.VEC - шаблон векторов
template <class T> // Т – параметр шаблона
class Vector
{
T *data; // Начало одномерного массива
int size; // Количество элементов в массиве
public:
Vector (int); // Конструктор класса vector
~Vector () { delete [ ] data; } // Деструктор
// Расширение действия (перегрузка) операции “[ ]”:
T& operator [ ] ( int i ) { return data [i];}
};
// Внешнее определение конструктора класса:
template <class T>
Vector <T>: : Vector (int n)
{
data=new T[n];
size=n;
};
Когда шаблон введён, у программиста появляется возможность определять конкретные объекты конкретных классов, каждый из которых параметрически порождён из шаблона. Формат определения объекта одного из классов, порождаемых шаблоном классов:
<имя параметризованного класса> <фактические параметры шаблона> <имя объекта> (<параметры конструктора>);
В нашем случае определить вектор, имеющий восемь вещественных координат типа double, можно следующим образом:
Vector <double> Z(8);
Проиллюстрируем сказанное следующей программой:
// P11.CPP - формирование классов с помощью шаблона
#include “template.vec” // Шаблон классов «вектор»:
#include <iostream.h>
main ()
{ // Создаём объект класса «целочисленный вектор»:
Vector <int> X (5);
// Создаём объект класса «символьный вектор»:
Vector <char> C (5);
// Определяем компоненты векторов:
for (int i=0; i<5; i++)
{ X[i]=i; C[i]= ‘A’ + i; }
for (int i=0; i<5; i++)
cout << “ “ << X[i] << ‘ ‘ << C[i];
}
Результат выполнения программы:
0 А 1 В 2 С 3 D 4 E
В программе шаблон семейства классов с общим именем Vector используется для формирования двух классов с массивами целого и символьного типов. В соответствии с требованиями синтаксиса имя параметризованного класса, определённое в шаблоне (в примере Vector), используется в программе только с последующим конкретным фактическим параметром (аргументом), заключённым в угловые скобки. Параметром может быть имя стандартного или определённого пользователем типа. В данном примере использованы стандартные типы int и char. Использовать имя Vector без указания фактического параметра шаблона нельзя – никакое умалчиваемое значение при этом не предусматривается.
В списке параметров шаблона могут присутствовать формальные параметры, не определяющие тип, точнее – это параметры, для которых тип фиксирован:
//P12.CPP
#include <iostream.h>
template <class T, int size = 64>
class row
{
T *data;
int length;
public : row ()
{
length = size;
data = new T[size];
}
~ row () { delete [ ] data;}
T& operator { } (int i)
{ return data [i];}
};
void main ()
{
row <float,8> rf;
row <int,8> ri;
for (int i = 0; i<8; i++)
{ rf [i] = i; ri [i] = i * i; }
for (I=0; I<8; i++)
cout << “ “ << rf[i] << ‘ ‘ << ri [i];
}
Результат выполнения программы:
0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49
В качестве аргумента, замеряющего при обращении к шаблону параметр, взята константа. В общем случае может быть использовано константное выражение, однако выражения, содержащие переменные, использовать в качестве фактических параметров шаблонов нельзя.
Дата добавления: 2015-08-08; просмотров: 659;