Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
18
Добавлен:
27.02.2014
Размер:
11.35 Кб
Скачать
                                               Наследование
Наследование - удобное средство С++, позволяющее избежать переписывание функций и данных из одного класса в другой. Наследование позволяет переходить от классов, обладающих общими свойствами, к все более и более специализированным. Достаточно определить класс производным от другого (базового) класса, объявить и определить в нем новые члены, и он, помимо новых, унаследует все члены базового класса. Использование производных классов позволяет строить целые иерархии классов.

Лучшим вариантом по объему требуемой работы является наследование. Это означает создание нового класса, производного (derived) от класса MyStr (последний в этом случае называется базовым). При наследовании все члены базового класса (в нашем случае MyStr) становятся полноправными членами производного класса.

СОЗДАНИЕ ПРОИЗВОДНОГО КЛАССА

                             Описание функций и операторов

Getptr - возвращает указатель на строку данных, заканчивающуюся нуль-терминатором
Length - возвращает длину строки данных
Copy - копирует в строку данных содержимое параметра типа char*
Plus - дописывает содержимое параметра типа char* в текущую строку данных
+ - записывает две строки в одну друг за другом. По крайней мере, одна из строк должна иметь тип MyStr. Другая может иметь тип MyStr или char*
= копирует строки данных из другого объекта класса MyStr.

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

Создание производного класса путем наследования делает простой процедуру добавления этих функций, даже если нет доступа к тексту функций базового класса.
	
ПРИМЕЧАНИЕ: в некоторых источниках базовый класс называют родительским классом или предком, а производный класс - дочерним или потомком.

Класс NewClass, производный от базового класса BaseClass, объясняется следующим образом:
class NewClass: public BaseClass
{
<объявления>
};

Такое объявление приводит к тому, что класс NewClass, помимо своих данных и функций, содержащихся в объявлениях, автомаически содержит все данные и функции базового класса BaseClass. Программист должет изменять и дополнять их в соответствии со своими потребностями, используя для этого операторы доступа к членам объекта (,) или (->).

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

ВНИМАНИЕ! Прежде чем использовать какой-либо класс в качестве базового, он должен быть полностью определен. Чаще всего для этого достаточно включить в программу соответствующий файл заголовков с помощью дерективы include.

Приведем пример класса MyIOStr, производного от базового класса MyStr.

#include <mystr.h>
class MyStr: public MyStr
{ public:
void input(void);
void output(void);
};

Как только класс объявлен, его можно использовать для определения объектов. Эти объекты имеют доступ ко всем членам базового класса, а также к функциям input и output.

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

#include <myiostr.h>
MyIOStr str1, str2, str3;
str1.copy ("Vvedite stroky");
str2.plus(":");
str2.output();
str3.input();
str2.copy("Vvedrna stroka");
str2.plus (str3.getptr());
str2.output();

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

// определение новых функций класса MyIOStr
#include <stdio.h>
#include "myiostr.h"
// файл myiostr.h включает в себя файл mystr.h
void myStr::output(void)
{
printf("%s", getptr());
}
void MyIOStr::input(void)
{
char buf[256];
gets (buf);
copy (buf);
}

Здесь определены две функции нового класса. Обе они имеют префикс MyIOStr::. Основные функции унаследованы от базового класса MyStr, определены в файле mystr.cpp и имеют префикс MyStr::. Например, определение функции копирования, выглядит так:

void MyStr::copy(char *s)
{//...
}

Таким образом, класс MyIOStr поддерживает два вида функций: функции базового класса MyStr и функции самого класса MyIOStr. Префикс, используемый в определении, меняется в зависимости от того, где эти функции объявлены и определены: в базовом или производном классе.

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

class MyIOStr: public MyStr
{public:
void input(void);
void output(void);
void copy(char *s)
//новое объявление функции копирования
};
void MyIOStr::copy (char *s)
{ // новое определение функции копирования
}

В результате переопределения функции copy в производном классе получилось две функции, различные по исполняемым действиям. но имеющие одинаковые имена: одна, определенная в классе MyIOStr, и другая, определенная в классе MyStr. Обе они могут быть вызваны из функций-членов класса MyIOStr. Но теперь для вызова первоначальной версии функции copy (из базового класса) мы должны использовать оператор уточнения области действия (::), то есть вызывать эту функцию как MyStr::copy. В противном случае будет вызываться функция MyIOStr::copy.

void MyIOStr::input(void)
{
char buf[256];
gets (buf);
MyStr::copy (buf);
//вызов функции сopy базового класса 
}

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

ПРИМЕЧАНИЕ: Если предполагается, что функция может быть переопределена для производного класса, ее следует объявить виртуальной.

ИЕРАРХИЯ НАСЛЕДОВАНИЯ

Подобно базовым классам, которые могут иметь свои базовые классы, производные классы также могут иметь свои производные классы.

Объявим класс MyFontIOStr производным от класса MyIOStr.

#include <mystr.h>
class MyFontIOStr: public MyIOStr
{
int ptsize;
public:
void clr(void) {copy("");}
void setchar (int c, int n);
void setfont(int size);
};

Новый класс MyFontIOStr наследует все члены классов MyIOStr и MyStr, так как каждое последовательное наследование добавляет или переписывает члены базового класса. Ни один из членов не исчезает, хотя права доступа могут иногда изменяться.

В классе MyFontIOStr появились новые члены, которых нет у базовых классов, - это переменная ptsize и три новых функции. Кроме того, он имеет три функции-члена, унаследованных от класса MyIOStr, плюс два данных-члена и более десятка функций-членов, унаследованных от класса MyStr.
Эти три класса, myFontIOStr, MyIOStr, MyStr, создают иерархию классов.

Исходный класс MyStr не является непосредственным базовым классом класса MyFontIOStr, самого младшего в этой иерархии. Но через наследование он передает свои члены этому классу так же, как родители передают свои гены внукам. Возможно создание значительно более сложных иерархий классов.

НАСЛЕДОВАНИЕ И ОБОЛОЧКА ОБЪЕКТА

Теоретически, можно рассматривать создание производного класса и создание класса в виде оболочки вокруг объекта, как совершенно разные методы.

Первый метод, согласно теории ООП, следует применять в ситуации. когда производный класс А является разновидностью (частным случаем) базового класса Б. опираясь на такую трактовку наследования, можно сказать, что класс MyIOStr является разновидностью класса MyStr, поэтому к нему добавляются новые функции. Тем не менее оба класса остаются строками.

При применении второго метода один класс использует объект другого класса. Например. можно создато класс Person, содержищий несколько объектов другого класса (MyStr):

class Person
{
MyStr surname;
MyStr name;
MyStr patronymic;
int age;
};
В данном случае нельзя создавать класс Person  как разновидность класса MyStr. так как он имеет совершенно другой смысл.

Различие методов создания этих двух классов (Person и MyIOStr) очевидно. Однако в некоторых случаях сделать выбор нужного метода для создания класса довольно трудно.

ВИДЫ ДОСТУПА: public, private и protected

C++ имеет три вида спецификатора доступа к членам класса:public, private и protected.

Внутри объявления класса private является спецификатором доступа к членам этого класса по умолчанию, поэтому обычно при объявлении класса он опускается.

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

include <mystr.h>
class MyIOStr: public MyStr
{
public:
void input(void);
void output(void);
};

Этот класс является производным базового класса MyStr, а значит, все члены класса MyStr, в том числе pstr и lstr, являются членами класса MyIOStr. Однако попытка определить функцию display, выполняющую вывод на экран строки pstr, приведет к ошибке, так как строка pstr объявлена по умолчанию со спецификатором доступа private и доступ к ней вне функций-членов класса MyStr невозможен.

void MyIOStr::display(void)
{
printf("%s", pstr);//Ошибка! Нет доступа к str
};

Чтобы избежать этой ошибки, следует воспользоваться функциями getptr и copy.
Другим возможым способом решения этой проблемы является объявление данных-членов класса MyStr pstr и lstr со спецификатором доступа protected. При таком объявлении они становятся доступными не только для функций-членов класса MyStr, но и для функций-членов класса MyIOStr.

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

Однако в приведенном примере можно оставить переменные pstr и lstr закрытыми (private), так как функции getptr, length, copy, plus обеспечивают полный доступ к данным и позволяют избежать возможных ошибок.

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