Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_Лекции 2010.doc
Скачиваний:
69
Добавлен:
17.03.2015
Размер:
954.37 Кб
Скачать
    1. Наследование

Язык C++ поддерживает механизм наследования, являющийся одной из фундаментальных концепций ООП. Наследованием называют конструирование новых более сложных производных классов из уже имеющихся базовых посредством добавления полей и методов. Это - эффективное средство расширения функциональных возможностей существующих классов без их перепрограммирования и повторной компиляции существующих программ.

По определению компонентами производного класса являются все компоненты базового класса, за исключением конструктора, деструктора и компонентной функции, переопределяющей оператор равно (=), а также все компоненты, перечисленные в теле производного класса. Первое поле производного класса расположено после всех полей базового класса.

В функциональном смысле производные классы или потомки являются более мощными по отношению к исходным (базовым) классам или классам-родителям, так как, имея доступ ко многим функциям и полям базового класса, они обладают еще и своими компонентами.

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

Определение производного класса (потомка) выполняется следующим образом:

class <имя производного класса >:

<вид наследования><имя базового класса>{<тело класса>};

где вид наследования определяется ключевыми словами: private, protected, public.

Видимость полей и функций базового класса из производного определяется видом наследования и представлена в табл. 3.1.

Если вид наследования явно не указан, то по умолчанию принимается private. Однако хороший стиль программирования требует, чтобы в любом случае вид наследования был задан явно.

Таблица 3.1. Видимость компонентов базового класса в производном

Вид наследования

Объявление компонентов в базовом классе

Видимость компонентов в производном классе

private

private

protected

public

не доступны

private

private

protected

private

protected

public

не доступны

protected

protected

public

private

protected

public

не доступны

protected

public

        1. Описание производного класса с видом наследования public.

#include <iostream.h>

#include <conio.h>

class A

{private: //защищенные (закрытые) компоненты класса

int numza;

public: // общедоступные (открытые) компоненты класса

int numoa;

void print(void)

{ cout<< "защищенное поле A = "<<numza<<endl;

cout<< "открытое поле A - "<<numoa<<endl;}

A()

{ numza=20; numoa=50;}

};

class B: public A /* открытое наследование - классу В доступны всеобщие компоненты класса А */

{private:

int numzb;

public:

void print (void)

{ cout<< "защищенное поле В = "<<numzb<<endl;

cout<< "общее поле A = ";

cout< <numoa< < endl;}

B() {numzb=100;}

};

void main()

{ clrscr();

A aa; В bb;

cout << "результаты работы: "<<endl;

aa.print(); // выводит: защищенное поле А = 20 открытое поле А = 50

bb.print(); // выводит: защищенное поле В = 100 общее поле А= 50

getch(); }

Производный класс может по отдельности изменять доступность компонентов секций protected и public базового класса даже при самом жестком режиме наследования private. Однако в этом случае следует помнить, что описание доступности поля нужно помещать в ту же секцию, в которой оно описано в базовом классе.

        1. Описание производного класса с видом наследования private.

#include <iostream.h>

#include <conio.h>

class A

{private: int numza;

protected: int numpra;

public: int numoa;

void print(void)

{ cout<< "закрытое поле класса А = "<<numza<<endl;

cout<< "защищенное поле класса A= "<<numpra<<endl;

cout<< "открытое поле класса А = "<<numoa<<endl;}

A(){numza=20; numpra=30; numoa=50;}

};

class B: private A // все компоненты класса не доступны в классе B

{private: int numzb;

protected:

A::numpra; // защищенное поле класса A, объявляется доступным в классе B

public:

А:: numоа; // общее поле класса А, объявляется доступным в классе В

void print(void)

{ cout<<" закрытое поле класса В = "<<numzb<<endl;

cout<<" открытое поле класса А, доступное в В=";

cout<<numoa<<endl;

cout<< "защищенное поле класса А, доступное в В = ";

cout<<numpra<<endl; }

B(){numzb=100; }

void main()

{ clrscr();

A aa; В bb;

cout<< "результаты работы: "<<endl;

aa.print(); // выводит: 20 30 50

bb.prmt(); //выводит: 100 50 30

getch();}

Производный класс может стать базовым для какого-либо другого класса. Множество связанных по наследованию классов образует иерархию классов.

Работа с объектом производного класса может осуществляться через указатель, объявленный для базового класса. Это возможно благодаря стандартному преобразованию указателя на производный класс в указатель на базовый, предусмотренному синтаксисом языка C++. Однако в этом случае указатель настроен на базовый класс и, как отмечалось в § 1.6, при работе с полями и методами производных классов возможны проблемы видимости этих компонентов.

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

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

        1. Порядок работы конструкторов базового и производного классов.

#include <iostream.h>

#include <conio.h>

class A

{ int a;

public: int c1;

void print(void) {cout<<a<<" "<<c1<<endl;}

A(int v):a(v){c1=10;} //конструктор базового класса А

class B: public A

{ int b;

public:

void print (void) { cout<<b<<" "<<cl<<endl;}

B(int va,int vb):A(va),b(vb){} // конструктор класса В

};

class С: public В

{ int с; public:

void print(void) {cout<<c<<" "<<cl<<endl;}

C(int va,int vb,int vc):B(va,vb),c(vc){} // конструктор класса С

};

void main()

{ clrscr();

A aa(10), *pa; // вызывается конструктор класса A

В bb(10,100); //вызывается конструктор класса A, а затем – B

С cc(10,100,1000): // вызываются конструкторы классов A, B и C

bb.cl=25: // задание начальных значений общей компоненте cl

cc.с1=35;

cout << "результаты работы: "<<endl;

aa.print(); // выводит: 10 10

bb.print(); // выводит: 100 25

cc.print(); // выводит: 1000 35

ра=&аа; pa->print(); // выводит: 10 10

pa=&bb; /* указателю на объект базового класса присваивается адрес объекта производного класса и осуществляетсявызов компонентной функции */

pa->print(); // выводит: 10 25

ра=&сс:

pa->print(); // выводит 10 35

getch(); }

Отличие результатов вывода при обращении к компонентной функции print() непосредственно с использованием полного имени объекта и при обращении через указатель объясняется тем, что указатель настроен на базовый класс и внутренние поля производных классов ему не доступны. По той же причине происходит вызов метода print() базового класса. Для получения правильного результата необходимо использовать механизм сложного полиморфизма (см. § 3.4).

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

        1. Проектирование классов с использованием наследования (классы Целое число и Вещественное число). Пусть требуется разработать классы для реализации объектов Целое число и Вещественное число.

Объект Целое число должен хранить длинное целое в десятичной записи, уметь выводить его значение и определять количество значащих цифр десятичной записи хранимого числа. Объект Вещественное число должен хранить вещественное число, задаваемое в виде ccccc.dddddd, и его символьное представление. Он также должен уметь выводить свое значение, определять количество цифр десятичной записи целой и дробной частей.

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

#include <stdio.h>

#include <stdlib.h>

#include <iostream.h>

#include <string.h>

#include <conio.h>

typedef unsigned long dlong;

class Tlong. // Класс Целое число

{public:

dlong num; // числовое поле класса

Tlong() {cout<<"Неинициализирующий конструктор класса Tlong"<<endl;}

Tlong(dlong an) { cout<< "Конструктор класса Tlong"<<endl; setnum(an);}

void main ()

{clrscr();

cout<<"Введите число ccccc.dddddd"<<endl; cin>>s;

Treal a(s), *pa=new Treal("456789.1234321 "),mask[2J;

cout<< "ПРОСТОЙ ОБЪЕКТ ПРОИЗВОДНОГО КЛАССА "<<endl;

a.printr();

cout<<"Количество цифр целой части числа = "<<a.kolc()<<endl;

cout<<"Количество цифр дробной части числа = "<<a.dkolc()<<endl;

getch();

cout<<" УКАЗАТЕЛЬ НА ОБЪЕКТ ПРОИЗВОДНОГО КЛАССА "<<endl;

pa->printr();

cout<<"Количество цифр целой части числа = "<<pa->kolc()<<endl;

cout<<"Количество цифр дробной части числа "<<pa->dkolc()<<endl;

getch();

cout<<"МАССИВ ОБЪЕКТОВ ПРОИЗВОДНОГО КЛАССА "<<endl;

for(int i=0;i<2;i++)

{cout<<"Введите "<<(i+l)<< "число ccccc.dddddd"<<endl; cin>>s;

mask[i].setnumv(s); }

for(i=0;i<2;i++)

{ cout<< "Элемент массива "<<(i+1)<<": "<<endl;

mask[i].printr();} getch();}

Tlong

Treal

dlong num

dlong drob, char *real

Tlong(), Tlong(dlong an),

~TIong(), setnum(),

print(), kolc()

Treal(), Treal(char *st),

~Treal, setnumv(),

printr(), dkolc()