Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Теория ответы.doc
Скачиваний:
39
Добавлен:
24.12.2018
Размер:
1.61 Mб
Скачать

25.Понятие о наследовании. Управление доступом к членам базового класса. Использование защищенных членов.

Наследование — один из трех фундаментальных принципов объектно-ориентированного программирования, поскольку именно благодаря ему возможно создание иерархических классификаций. Используя наследование, можно создать общий класс, который определяет характеристики, присущие множеству связанных элементов. Этот класс затем может быть унаследован другими, узкоспециализированными классами с добавлением в каждый из них своих, уникальных особенностей.

В стандартной терминологии языка C++ класс, который наследуется, называется базовым. Класс, который наследует базовый класс, называется производным. Производный класс можно использовать в качестве базового для другого производного класса. Таким путем и строится многоуровневая иерархия классов.

Понятие о наследовании

Базовый класс наследуется производным классом.

Язык C++ поддерживает механизм наследования, позволяя в объявление класса встраивать другой класс. Для этого базовый класс задается при объявлении производного. Лучше всего начать с примера. Рассмотрим класс auto, который в самых общих чертах определяет дорожное транспортное средство. Его члены данных позволяют хранить количество колес и число пассажиров, которое может перевозить транспортное средство.

class automob{ int kol; int pass;

public:

void set_kol(int num) { kol = num; }

int get_kol() { return kol; }

void set_pass(int num) { pass = num; }

int get_pass() { return pass; } };

Это общее определение дорожного транспортного средства можно использовать для определения конкретных типов транспортных средств. Например, в следующем фрагменте путем наследования класса auto создается класс grus (грузовых автомобилей).

class grus : public automob{int cargo;

public:

void set_cargo(int size) { cargo = size; }

int get_cargo() { return cargo; }

void show(); };

Тот факт, что класс grus наследует класс auto, означает, что класс grus наследует все содержимое класса auto. К содержимому класса auto класс grus добавляет член данных cargo, а также функции-члены, необходимые для поддержки члена cargo.

Обратите внимание на то, как наследуется класс auto. Общий формат для обеспечения наследования имеет следующий вид.

class имя_производного_класса : доступ имя__базового_класса {

тело нового класса }

Здесь элемент доступ необязателен. При необходимости он может быть выражен одним из спецификаторов доступа: public, private или protected. Подробнее об этих спецификаторах доступа вы узнаете ниже в этой главе. А пока в определениях всех наследуемых классов мы будем использовать спецификатор public. Это означает, что все public-члены базового класса также будут public-членами производного класса. Следовательно, в предыдущем примере члены класса grus имеют доступ к открытым функциям-членам класса auto, как будто они (эти функции) были объявлены в теле класса grus. Однако класс grus не имеет доступа к private-членам класса auto. Например, для класса grus закрыт доступ к члену данных kol.

Рассмотрим программу, которая использует механизм наследования для создания двух подклассов класса auto: grus и legk.

//Шилдт стр 353

// Демонстрация наследования,

#include <iostream.h>

#include <conio.h>

// Определяем базовый класс транспортных средств,

enum type {car, van, wagon};

class automob {int kol; int pass;

public:

void set_kol(int num) { kol = num; }

int get_kol() { return kol; }

void set_pass(int num) { pass = num; }

int get_pass() { return pass; }

};

// Определяем класс грузовиков,

class grus : public automob {int cargo;

public:

void set_cargo(int size) { cargo = size; }

int get_cargo() { return cargo; }

void show();

};

// Определяем класс автомобилей.

class legk : public automob {

enum type car_type;

public:

void set_type(type t) { car_type = t; }

enum type get_type() { return car_type; }

void show();

};

void grus::show(){

cout <<"Koles: "<<get_kol()<<"\n";

cout<<"Passajir: "<<get_pass()<<"\n";

cout<<"Grusopodjemnost: "<<cargo<<"\n"; }

void legk::show () {

cout<<"Koles: "<<get_kol()<<"\n";

cout<<"Passajir: "<<get_pass()<< "\n";

cout<<"Tip kusova: ";

switch(get_type()) {

case van: cout <<"legkovoi\n"; break;

case car: cout <<"sedan\n"; break;

case wagon: cout << "kabriolet\n"; }}

int main () {clrscr();

grus t1, t2;

legk c;

t1.set_kol(18); t1.set_pass(2) ; t1.set_cargo(3200);

t2.set_kol(6); t2.set_pass(3); t2.set_cargo(1200);

t1.show() ; cout<<"\n"; t2.show(); cout<<"\n";

c.set_kol(4); c.set_pass(6); c.set_type(van);

c.show();

getch(); return 0; }

При выполнении эта программа генерирует такие результаты.

колес: 18

пассажиров: 2

грузовместимость в кубических футах: 3200

колес: 6

пассажиров: 3

грузовместимость в кубических футах: 1200

колес: 4 пассажиров: 6 тип: автофургон

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

И еще. Обратите внимание на то, что оба класса grus и legk включают функцию-член show (), которая отображает информацию об объекте. Эта функция демонстрирует еще один аспект объектно-ориентированного программирования — полиморфизм. Поскольку каждая функция show() связана с собственным классом, компилятор может легко "понять", какую именно функцию нужно вызвать для данного объекта.

После ознакомления с общей процедурой наследования одним классом другого можно перейти и к деталям этой темы.

Управление доступом к членам базового класса

Если один класс наследует другой, члены базового класса становятся членами производного. Статус доступа членов базового класса в производном классе определяется спецификатором доступа, используемым для наследования базового класса. Спецификатор доступа базового класса выражается одним из ключевых слов: public, private или protected. Если спецификатор доступа не указан, то по умолчанию используется спецификатор private, если речь идет о наследовании типа class. Если же наследуется тип struct, то при отсутствии явно заданного спецификатора доступа по умолчанию используется спецификатор public. Рассмотрим рамификацию (разветвление) использования спецификаторов public или private. (Спецификатор protected описан в следующем разделе.)

Если базовый класс наследуется как public-класс, его public-члены становятся public-членами производного класса.

Если базовый класс наследуется как public-класс, все его public-члены становятся public-членами производного класса. Во всех случаях private-члены базового класса остаются закрытыми в рамках этого класса и не доступны для членов производного. Например, в следующей программе public-члены класса base становятся public-членами класса derived. Следовательно, они будут доступны и для других частей программы.

//Шилдт стр 355

// Демонстрация наследования,

#include <iostream.h>

#include <conio.h>

class base { int i, j ;

public:

void set (int a, int b) { i = a; j = b; }

void show() { cout << i << " " << j << "\n"; } };

class derived:public base{int k;

public:

derived(int x) { k = x; }

void showk() { cout<<k<<"\n"; } };

int main() { clrscr();

derived ob(3);

ob.set(1, 2); // доступ к членам класса base

ob.show(); // доступ к членам класса base

ob.showk(); // доступ к члену класса derived

getch(); return 0; }

Поскольку функции set() и show() (члены класса base) унаследованы классом derived как public-члены, их можно вызывать для объекта типа derived в функции main(). Поскольку члены данных i и j определены как private-члены, они остаются закрытыми в рамках своего класса base.

Если базовый класс наследуется как private-класс, его public-члены становятся private-членами производного класса.

Противоположностью открытому (public) наследованию является закрытое (private). Если базовый класс наследуется как private-класс, все его public-члены становятся private-членами производного класса. Например, следующая программа не скомпилируется, поскольку обе функции set() и show() теперь стали private-членами класса derived, и поэтому их нельзя вызывать из функции main ().

// Эта программа не скомпилируется.

#include <iostream>

#include <conio.h>

using namespace std;

class base { int i, j;

public:

void set(int a, int b) { i = a; j = b; }

void show() { cout<<i<<" "<<j<<"\n"; } };

// Открытые члены класса base теперь становятся

// закрытыми членами класса derived,

class derived : private base { int k;

public:

derived(int x) { k = x; }

void showk() { cout<<k<<"\n"; }

int main() {clrscr();

derived ob (3);

ob.set(l, 2); // Ошибка, доступа к функции set() нет.

ob.show(); // Ошибка, доступа к функции show() нет.

Getch(); return 0; }

Важно запомнить, что в случае, если базовый класс наследуется как private-класс, его открытые члены становятся закрытыми (private) членами производного класса. Это означает, что они доступны для членов производного класса, но не доступны для других частей программы.

Использование защищенных членов

Член класса может быть объявлен не только открытым (public) или закрытым (private), но и защищенным (protected). Кроме того, базовый класс в целом может быть унаследован с использованием спецификатора protected. Ключевое слово protected добавлено в C++ для предоставления механизму наследования большей гибкости.

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

Спецификатор доступа protected объявляет защищенные члены или обеспечивает наследование защищенного класса.

Как вы знаете, закрытый член базового класса не доступен никаким другим частям программы, включая и производные классы. Однако с защищенными членами все обстоит иначе. Если базовый класс наследуется как public-класс, защищенные члены базового класса становятся защищенными членами производного класса, т.е. доступными для производного класса. Следовательно, используя спецификатор protected, можно создать члены класса, которые закрыты в рамках своего класса, но которые может унаследовать производный класс, причем с получением доступа к ним.

Рассмотрим следующий пример программы.

//Шилдт стр 357

// Демонстрация наследования,

#include <iostream.h>

#include <conio.h>

class base { protected:

int i, j; // Эти члены закрыты в классе base,

// но доступны для класса derived,

public:

void set(int a, int b) { i = a; j = b; }

void show() { cout<<i<<" "<<j<<"\n"; } };

class derived : public base { int k;

public:

// Класс derived имеет доступ к членам класса base i и j.

void setk() { k = i*j; }

void showk() { cout<<k<<"\n"; } };

int main() {clrscr();

derived ob;

ob.set(2, 3); // OK, классу derived это позволено.

ob.show(); // OK, классу derived это позволено.

ob.setk() ; ob.showk() ;

getch(); return 0; }

Поскольку класс base унаследован классом derived открытым способом (т.е. как public-класс), и поскольку члены i и j объявлены защищенными в классе base, функция setk() (член класса derived) может получать к ним доступ. Если бы члены i и j были объявлены в классе base закрытыми, то класс derived не мог бы обращаться к ним, и эта программа не скомпилировалась бы.