Базовые и производные классы
Простое наследование.
Обычно классы не существуют сами по себе. Как правило, программа работает с несколькими различными, но связанными структурами данных.
В С++ решением проблемы "похожие, но различные" является разрешение классам наследовать характеристики и поведение от одного или нескольких классов. Этот интуитивный переход и составляет, пожалуй, наибольшее различие между языками С и С++.
Одной из важнейших концепций языка С++ является возможность наследования свойств одного класса другим — это простое наследование, что можно показать так: A<-B (класс В наследник класса А). Класс, на основе которого строится другой класс, называется базовым (base) классом (предок, родитель). Построенный на его основе новый класс называется производным (derived) (потомок). Из одного класса могут порождаться многие классы:
Базовый класс
Производный класс 1 . . . Производный класс N
Производный класс сам может быть базовым для других классов, что порождает иерархию классов:
Базовый класс А
Производный класс В Базовый класс для С
Производный класс C Класс С — производный от А и В
Контроль доступа в классах.
Производный класс наследует из базового данные-элементы и функции-элементы в зависимости от режимов доступа в базовом классе и модификаторов доступа при наследовании.
Шаблон описания производного класса имеет вид:
class Производный_класс :модификатор_доступа Базовый_класс
{
тело производного класса;
} объекты производного класса (не обязательно) ;
например:
class Point :public Location { . . .};
Как известно, в С++ элементы класса могут иметь один из трех режимов доступа: public (общий), private (закрытый), protected (защищенный). Общие элементы могут быть доступны другим функциям программы. Закрытые элементы доступны только функциям-элементам или функциям-друзьям класса. Защищенные элементы также доступны только тем же функциям.
Модификаторы доступа (public, protected, private (по умолчанию)) в производном классе используются для изменения прав доступа к наследуемым элементам базового класса в соответствии с установленными правилами:
Режим доступа в базовом классе: | Модификатор доступа в производном классе: | Права доступа к элементам базового класса из производного: |
private protected public | public | нет доступа protected public |
private protected public | protected | нет доступа protected protected |
private protected public | private | нет доступа private private |
Как видно из таблицы, в производных классах доступ к элементам базовых классов может быть только ужесточен, но не облегчен. Кроме того, производный класс наследует из базового данные и функции-элементы, но не конструкторы и деструкторы.
При создании новых классов необходимо ясно понимать взаимосвязь между базовыми и производными классами и влияние модификатора доступа. С++ позволяет изменять права доступа без раскрытия данных перед методами, не принадлежащими к данному семейству классов или друзьям. То есть элементы базового класса, которые предполагается использовать в производном классе, должны быть либо public, либо protected.
Модификатор доступа public не изменяет уровня доступа, то есть protected элементы базового класса остаются protected и в производном классе, что гарантирует их недоступность везде, кроме других производных классов, объявленных с модификатором public, и функций-друзей.
Наследование типа private используется в классах по умолчанию, являясь одновременно и наиболее распространенным способом наследования. Таким образом, получаем довольно редкую ситуацию, при которой то, что задано по умолчанию является и нормой.
Элементы класса protected — это нечто среднее между открытыми и закрытыми элементами.
При выборе режима доступа к элементам действуют следующие правила:
— закрытые (private) элементы доступны только в классе, в котором они объявлены и функциям-друзьям;
— защищенные (protected) элементы доступны элементам их собственного класса и всем элементам производного класса и функциям-друзьям, но только в объектах производного класса. Вне класса защищенные элементы недоступны;
— открытые (public) элементы доступны во всей программе.
Порядок описания секций доступа произвольный, но если встраиваемая функция-элемент ссылается на другой элемент, он должен быть объявлен до реализации функции-элемента. Во избежание ошибок, рекомендуется всегда явно указывать модификаторы доступа, вне зависимости от установленных по умолчанию.
Пример 25.
Рассмотрим следующий класс.
class X
{ private:
int A; // доступен только элементам этого класса
void fa(); // доступен только элементам этого класса
protected:
int B; // доступен элементам этого класса и производного
void fb(); // доступен элементам этого класса и производного
public:
int C; // доступен всем, кто его использует
void fc(); // доступен всем, кто его использует
};
Внешние операторы не могут вызывать методы fa, fb и использовать элементы А и В.
Когда базовый класс объявлен private для производного класса, то это существенно влияет на наследуемые элементы.
Пример 26.
Рассмотрим следующие классы.
class Base
{ protected: int x; // защищенный элемент х
public: int y; // общий элемент y
};
class Derived: private Base // класс Base закрыт!
{ public: void f();
};
Метод f() класса Derived имеет доступ к элементам х и у, наследованным от класса Base. Но поскольку Base объявлен закрытым классом для Derived, статус х и у изменился на private в классе Derived.
Добавим класс Derived1 производный от класса Derived.
class Derived1: public Derived
{ public: void f1();
};
В этом классе метод void f1() не имеет доступа к элементам х и у, несмотря на то, что в классе они имели статус protected и public .
Пример 27.
Рассмотрим варианты наследования режимов доступа к элементам классов при простом наследовании по иерархии классов X <- Y <- Z.
#include<iostream.h>
#include<conio.h>
class X // базовый класс X для классов Y, Z
{ protected: int i, j; // защищенные переменные доступные в Y, Z
public: // прототипы открытых методов:
void get_ij(); // ввод чисел
void put_ij(); // вывод чисел
};
// Класс Y производный от X с сохранением режимов доступа к элементам Х:
class Y: public X
{ int k; // private по умолчанию в классе Y
public: // прототипы открытых методов класса Y:
int get_k(); // возврат числа k
void make_k(); // вычисление результата k
};
// Класс Z производный от Y и Х имеет доступ к переменным i, j класса Х
// и нет доступа к закрытой переменной k класса Y:
class Z: public Y
{ public:
void f(); // прототип функции, изменяющей i, j
};
// Описание методов классов:
void X::get_ij() // функция ввода чисел i, j
{ cout<<"Введите два числа: ";
cin >> i >> j;
}
void X::put_ij() // функция ввода чисел i, j
{ cout<<"i="<<i<<" j="<<j<<endl;
}
int Y::get_k() // функция возврата числа k
{ return k;
}
void Y::make_k() // функция вычисления числа k
{ k=i*j;
}
void Z::f() // функция, изменяющяя i, j класса Х
{ i=2; j=3;
}
void main() // главная функция
{ clrscr(); // чистка экрана
Y var1; // создан объект var1 класса Y
Z var2; // создан объект var2 класса Z
var1.get_ij(); // ввод чисел i, j
var1.put_ij(); // вывод чисел i, j
var1.make_k(); // вычисление числа k=i*j
cout<<"k=i*j="<<var1.get_k(); // вывод числа k=i*j
cout<<'\n'; // переход на новую строку экрана
var2.f(); // изменение элементов i, j класса Х в классе Z
var2.put_ij(); // вывод чисел i, j
var2.make_k(); // вычисление числа k=i*j
cout<<"k=i*j="<<var2.get_k(); // вывод результата k=i*j
cout<<'\n';
getch();
}
Результаты программы:
Введите два числа: 3 7
i=3 j=7
k=i*j=21
i=2 j=3
k=i*j=6
Если в данной программе заменить модификатор доступа в классе Y при наследовании класса X на private, то функция void Z::f() не будет иметь право доступа к переменным i, j.