
Наследование
Механизм наследования позволяет определять новые классы на основе уже имеющихся. Класс, на основе которого создается новый класс, называют базовым (родительским), а новый – производным (наследником). При наследовании важную роль играет статус доступа к компонентам класса.
Используются следующие соглашения:
– private-компоненты доступны только внутри того класса, в котором они определены;
– protected-компоненты доступны внутри того класса, в котором они определены, а также во всех производных классах;
– public-компоненты видны во всех частях программы.
При описании можно изменить статус доступа к наследуемым компонентам (только в направлении ужесточения). Формат описания производного класса с единичным наследованием имеет вид:
class имя_производного_класса: [модификатор] имя_базового_класса
{компоненты_производного_класса};
В качестве модификатора могут использоваться ключевые слова private, protected, public. Статусы доступа производных классов приведены ниже в таблице.
Пример:
class Circle: public Point
{
// элементы производного класса Circle
}
В данном случае мы получаем при использовании класса Circle доступ к элементам класса Point.
Формат описания производного класса с множественным наследованием имеет вид:
class имя_производного_класса: [модификатор] имя_базового_класса_1,…,
[модификатор] имя_базового_класса_n
{компоненты_производного_класса};
Полиморфизм
Полиморфизм можно определить как свойство, позволяющее использовать одно имя для обозначения действий, общих для родственных классов. При этом конкретизация действий определяется в зависимости от типов обрабатываемых данных. К важнейшим свойствам полиморфизма в С++ модно отнести следующее:
– перегрузку функций и операций;
– виртуальные функции;
– обобщенные функции или шаблоны.
В случае использования перегрузки функций в С++ допучкается использование нескольких одноименных функций, выполняющих аналогичные действия над данными разных типов. Например, можно описать
int max(int,int);
double max(double,double);
В этом случае возможно использование программы:
double x,y;
int x1,y1;
x=12.7; y=2.e3;
x1=5; y1=3;
cout<<”Максимум ”<<max(x,y)<<endl;
cout<<”Максимум ”<<max(x1,y1)<<endl;
Если возвращаемые значения и сигнатуры (тип и число параметров) нескольких одноименных функций совпадают, то второе и последующие объявления трактуются как переобъявления первого.
Если сигнатуры нескольких одноименных функций совпадают, а возвращаемые значения отличны, то при компиляции будет сгенерирована ошибка.
Если сигнатуры нескольких одноименных функций различны, тогда функция считается перегружаемой при условии объявления ее экземпляров в одной области видимости.
При выборе экземпляра функции осуществляется сравнение типов и числа параметров и аргументов. При этом возможно и преобразование типов при их вызове. Но, если при преобразовании типов возможен вариант, при котором соответствие достигается более, чем для одного экземпляра функции, то будет сгенерирована ошибка компиляции.
Виртуальная функция представляет собой компонентную функцию базового класса, которая переопределяется в производном классе. Это обеспечивает динамический полиморфизм, реализуемый на этапе выполнения программы. При объявлении виртуальной функции в базовом классе перед ее именем указывается ключевое слово virtual. В производном классе эта функция переопределяется. При этом переопределении слово virtual не указывается.
Виртуальную функцию можно вызвать, как обычную компонентную функцию. Однако для поддержки динамического полиморфизма виртуальные функции вызывают через указатель базового класса, используемый в качестве ссылки на объект производного класса. Если адресуемый таким образом объект производного класса содержит виртуальную функцию и виртуальная функция вызывается с помощью этого указателя, то при компиляции определяется версия вызываемой виртуальной функции с учетом типа объекта, на который ссылается указатель. Определение конкретной версии виртуальной функции осуществляется в процессе выполнения программы.
Если имеется несколько производных классов от содержащего виртуальную функцию базового класса, то при ссылке указателя базового класса на разные объекты этих производных классов будут выполняться различные версии виртуальной функции.
Переопределение виртуальной функции в производном классе имеет существенные отличия от механизма перегрузки функций. Прежде всего, переопределяемая виртуальная функция должна иметь те же тип, число параметров и тип возвращаемого значения, тогда как перегружаемая функция должна иметь отличия в типе и/или числе параметров. Во-вторых, виртуальная функция должна быть компонентом класса, что не относится к перегружаемым функциям.
Чтобы прояснить смысл использования виртуальных функций, приведем пример, иллюстрирующий разницу между обычными и виртуальными функциями:
class parent
{ //объявление базового класса
public:
int k;
parent(int a) // конструктор класса
{
k=a,
}
virtual void fv() // виртуальная функция
{
cout<<”Вызов fv базового класса: ”;
cout<<k<<endl;
}
void f () //обычная функция
{
cout<<"Вызов f () базового класса: “;
cout<<k*k<<endl;
}
};
class childl: public parent
{
public:
childl(int x): parent(x) {}
void fv()
{
cout<<"Вызов fv() из производного класса childl: ";
cout<<(k+1)<<endl;
}
void f() //обычная функция
{
cout<<”Вызов f0 из производного класса childl: “;
cout<<(k-1)<<endl;
}
};
int main()
{
parent *p; // указатель на базовый класс
parent ez(5); // объявление объекта базового класса
childl chezl(15); // объявление объекта производного класса
p = &ez; // указатель ссылается на объект базового класса
p->fv(); // вызов функции fv() базового класса
p->f(); // вызов функции f() базового класса
р = &chezl; // указатель ссылается на объект производного класса
p->fv(); // вызов виртуальной функции fv() производного класса
p->f(); // вызов обычной функции f() производного класса
return 0;
}
При выполнении на экран будут выданы следующие результаты:
Вызов fv() базового класса: 5
Вызов f() базового класса: 25
Вызов fv() из производного класса childl: 16
Вызов f() базового класса: 225
В С++ существует понятие чистых виртуальных функций. Такие функции используются в случае, когда в виртуальной функции базового класса отсутствует значимое действие. При этом в каждом производном классе эта функция должна быть обязательно переопределена. В базовом классе в этом случае описываются прототипы таких функций в виде:
virtual тип имя_функции (список параметров) = 0;
Присвоение имени функции значения 0 указывает на отсутствие тела функции.
Шаблон представляет собой специальное описание родовой функции или родового класса, в которых информация об используемых в реализации типах данных преднамеренно остается незаданной. Типы используемых данных передаются через параметры шаблона. Это позволяет одну и ту же функцию или класс использовать с различными типами данных без необходимости программировать заново каждую версию функции или класса.
Шаблон создается при помощи ключевого слова template и угловых скобок со списком параметров, непосредственно за которым следует описание класса или функции:
template <class фиктивное_имя_1, class фиктивное_имя_2, …> определение;
Вместо ключевого слова class допускается использование ключевого слова typename.
Рассмотрим пример:
template <class T> T max(T x, T y)
{
return (x>y) x : y;
};
void main(void)
{
int j=max(10,5);
cout<<”j=”<<j<<endl;
double d=max(12.09,17.03);
cout<<”d=”<<d<<endl;
}
Результатом работы данной программы будет:
j=10
d=17.03