Производные классы в C++

Тип работы:
Реферат
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Содержание

Введение

1. Простое наследование

2. Доступ к наследуемым компонентам

3. Конструкторы производных классов

4. Объемлющие классы

5. Примеры связных списков

Заключение

Список литературы

Введение

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

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

1. Простое наследование

производный класс программный алгоритм

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

Один класс может наследовать все составляющие другого класса. Класс, передающий свои компоненты другому, называют базовым классом. Класс, принимающий эти компоненты, называется производным классом. Способность класса пользоваться методами, определенными для его предков, составляет сущность принципа наследуемости свойств. Например, можно определить в программе класс «Человек» с компонентами «Фамилия», «Имя», «Отчество» и год рождения. Функции-методы, которые могут понадобиться при работе с объектами этого класса, такие, как: «ЗадатьФамилию», «ДатьГодРождения», «ВывестиНаЭкранФИО» и т. д., также будут определены в этом классе. Если в программе понадобится определить какой-либо другой объект, скажем, «Работник» или «Студент», становится очевидным, что этот класс будет частным случаем класса «Человек», поскольку все компоненты класса «Человек» необходимо полностью переписать в класс «Работник» или «Студент». Намного удобнее в этом случае просто каким-либо образом при определении таких производных классов сослаться на базовый класс, сказав тем самым, что все его компоненты заимствуются. В том же случае, если нужно доопределить в производном классе собственные компоненты, например «Зарплата» или «НомерЗачетнойКнижки», их можно описать в производном классе заново. Такой механизм наследования действительно существует в С++.

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

class Base {

// Элементы базового класса

};

class Derived: [модификатор доступа] Base {

// Элементы производного класса

};

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

Рассмотрим пример.

class Level0 { // Базовый класс

private: int a;

protected: int b;

public: int c;

void f0();

};

class Level1: public Level0 { // Производный класс

private: int d;

protected: int e;

public: int f;

void f1();

};

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

2. Доступ к наследуемым компонентам

Доступность различных составляющих (компонентов) класса в производном классе определяется соответствующим ключем доступа, задаваемым словами private, public или protected.

Частный (private) компонент класса доступен только другим компонентам и друзьям этого класса, тогда как общий (public) компонент доступен и вне данного класса. Частные компоненты базового класса для производных классов являются недоступными.

Программист может позволить производным классам доступ к конкретным компонентам базового. C++ имеет также третью категорию доступности компонентов класса, называемую защищенной (protected). Защищенные компоненты недоступны ни для каких частей программы, за исключением компонентов производных классов.

Класс может быть унаследован как public или как private. При этом модификатор private трансформирует компоненты базового класса с атрибутами доступа public и protected в компоненты private производного класса, в то время как private-компоненты становятся недоступны в производном классе.

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

Разные типы наследования влияют только на доступ по умолчанию компонентов базового класса в производном классе. Правила наследования доступа показаны в табл.4.1.

Таблица 4. 1

Доступ

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

Доступ

компонентов в

базовом классе

Доступность компонентов

базового класса в

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

public

public

protected

private

public

protected

недоступен

private

public

protected

private

private

private

недоступен

При объявлении класса-потомка с помощью ключевого слова class статусом доступа по умолчанию является private, а при объявлении с помощью ключевого слова struct — public, то есть

struct D: B{… }; означает: struct D: public B{ public:… };

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

class Level0 { // Базовый класс

private: int a;

protected: int b;

public: int c;

int e;

void f0();

};

class Level1a: public Level0 {

protected: int d;

public: int f;

void f1();

};

// Обычная функция — имеет доступ только к public-компонентам

void fn () {

Level0 L0;

Level1a L1;

L0.e = 1; // public-компонент

L1.e = 1; // public-компоненты из Level0 являются

// также public и в Level1a

L1.f = 2;

L1. f0();

L1. f1();

}

// Компонентные функции

void Level0: :f0() { // имеет доступ ко всему Level0

a = 1;

b = 2;

c = 3;

}

void Level1a: :f1() {

b = 1; // доступа к a не имеет

c = 2;

d = 3; // имеет доступ ко всему Level1a

e = 4;

f = 5;

f0();

}

В следующих частных производных классах L1. c и L1. f0() внешней функции fn () не доступны, поскольку они являются частными, хотя L0. c и L0. f0() продолжают оставаться доступными. Доступность компонентов для компонентных функций f0() и f1() остается неизменной.

class Level1b: private Level0 {

private: int d;

protected: int e;

public: int f;

void f1();

};

class Level1c: Level0 { // аналогично Level1b

private: int d;

protected: int e;

public: int f;

void f1();

};

// Общая функция

void fn () {

Level0 L0;

Level1b L1;

L0.c = 1;

L0. f0();

L1.f = 1; // доступа к L1. c или L1. f0() теперь нет

L1. f1();

}

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

class Level1d: private Level0 {

public:

Level0: :c; // конкретно объявляет переменную c как public

int f;

void f1();

};

// Общая функция

void fn () {

Level0 L0;

Level1d L1;

L0.c = 1;

L0. f0();

L1.c = 1; // доступ к c теперь возможен, но

// f0 остается недоступной

L1.f = 2;

L1. f1();

}

При объявлении Level1d как private-производного умолчание для доступности переменной c изменяется с public на private. Однако, объявив специальным образом переменную c как public, умолчание можно переопределить, делая L1. c доступной из обычной функции fn (). Level1d не может обеспечить сам себе доступ к компоненту a, который является частным (private) в базовом классе.

3. Конструкторы производных классов

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

Параметры конструктора базового класса указываются в определении конструктора производного класса. Вызов конструктора базового класса следует непосредственно после имени конструктора производного класса, перед открывающей фигурной скобкой.

class Level0 {

private: int a;

protected: int b;

public: int c;

void f0();

Level0(int v0) {

a = b = c = v0;

}

};

class Level1: public Level0 {

private: int d;

protected: int e;

public: int f;

void f1();

Level1(int v0, int v1): Level0(v0) {

d = e = f = v1;

}

};

// Общая функция

void fn () {

Level0 L0(1);

Level1 L1(1,2);

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

Следующий фрагмент программы даст тот же результат, что и предыдущее определение конструктора.

Level1(int v0, int v1): (v0) { // по умолчанию — Level (v0)

d = e = f = v1;

}

Конструкторы объемлемых (см. следующий параграф) классов можно вызывать в той же строке, что и конструктор базового класса. Следующий конструктор Level1 эквивалентен двум предыдущим:

Level1(int v0, int v1): Level (v0), d (v1), e (v1), f (v1) { }

4. Объемлющие классы

Сравним производные классы с классом Level1, который объемлет, а не наследует объект класса Level0. Для таких классов используют название «объемлющие классы», например:

class Level1 {

public: Level0 L0;

private: int d;

protected: int e;

public:

int f;

void f1();

};

// Непривелегированная функция

void fn () {

Level1 L1;

L1. L0. c =1;

L1.f = 2;

L1. L0. f0();

L1. f1();

}

// Компонентная функция

void Level1: :f1() {

L0.c = 1;

d = 2; e = 3; f = 4;

L0. f0();

}

Доступность компонентов производного и объемлющего классов аналогична. Level0: :a недоступен для компонентов класса Level1, а Level0: :c доступен. Защищенный (protected) компонент Level0: :b не доступен для более объемлющего класса.

Основное различие между объемлющим и производным классами состоит в способе доступа к наследуемым элементам. Всякий раз при доступе к элементу Level0 он задается конкретно, например L0. c, L0. f0() и т. д. Производный же класс ссылается к этим компонентам как к собственным.

Производный класс использует компоненты своего базового класса, в то время как объемлющий класс просто предоставляет место компонентам другого класса.

5. Примеры связных списков

Класс связного списка [3] является довольно популярным базовым классом, на котором построено множество других классов. Рассмотрим реализацию класса списка.

//********************************//

// Программа для обработки объектов//

// классов «список», «двусвязный //

// список", «закольцованный список» //

//-------------------------------------------------//

// Автор: Каширин Д. И. //

//------------------------------------------//

// Версия: 07. 12. 02 v. 1. 01.2 //

//

//---------------------------------------------------------------------------

#include < stdio. h>

#include < stdlib. h>

#include < string. h>

#include < iostream. h>

#include < alloc. h>

#include < conio. h>

#define IMAX 4

class List{ // Класс «Список»

protected:

float Value; // Значение эл-та списка

List *Next; // @ след. эл-та списка

public:

void AddElList (char);

void OutpList (char);

void DelElList (int);

void AddElList (float, char);

void CreateList ();

List (const char *Ident) // Конструктор класса

{

cout < < «Lead the value of the first «; //Запрос на ввод

// значения

cout < < «element of list «< <Ident<<'n';

cin > > Value; // Чтение первого эл-та

Next = NULL; // 1-й эл-т ссылается на NULL

}

List () // Конструктор без параметров

{

Value = 0; // Чтение значения нов. эл-та

Next = NULL; // Нов. эл-т ссыл-ся на NULL

}

~List (); // Деструктор класса

};

//--------------------------------//

// Деструктор класса List //

//--------------------------------//

List: :~List ()

{

List *CurrEl, // Текущий эл-т списка

*TNext; // Следующий эл-т

CurrEl = this; // Первый эл-т — Объект

while ((CurrEl-> Next≠ NULL) & &

(CurrEl-> Next≠ this)){

TNext = CurrEl-> Next; // Сохраним адрес след. эл-та

free (CurrEl);

CurrEl = TNext; // След. эл-т сделать текущим

};

free (CurrEl); // Удалить последний эл-т

cout < < «Object deleted» < < 'n';

getch ();

}

//*****************************************************//

// Функция добавления элемента в конец односвязного списка //

//*****************************************************//

void List: :AddElList (char R)

{

List *CurrEl, // Текущий эл-т списка

*NewEl = new List; // Новый эл-т списка

// Выдел-е памяти под нов. эл-т

CurrEl = this; // Текущий эл-т — Объект

List* KeyWord;

KeyWord = R? this: NULL;

while (CurrEl-> Next≠ KeyWord){ // Переход в конец списка

CurrEl = CurrEl-> Next;

}

cout < < «Lead the value of new element of list «;

cin > > NewEl-> Value; // Ввод знач-я нового эл-та

NewEl-> Next = KeyWord; // Новый эл-т ссылается на NULL

CurrEl-> Next = NewEl; // Новый эл-т — в конец списка

}

//----------------------------------------------------------------//

// Функция вывода на экран односвязного списка //

//----------------------------------------------------------------//

void List: :OutpList (char R)

{

int Count = 1; // Счетчик эл-тов списка

List *CurrEl; // Текущий эл-т списка

CurrEl = this; // Текущий эл-т — Объект

void* KeyWord;

KeyWord = R? this: NULL;

while (CurrEl≠ KeyWord){

cout < < Count < < «-th element of list = «; // Вывод эл-та списка

cout < < CurrEl-> Value < < 'n';

CurrEl = CurrEl-> Next;

Count++;

}

}

//-----------------------------------------------------//

// Функция удаления i-го элемента списка //

//-----------------------------------------------------//

void List: :DelElList (int i)

{

int Count = 1; // Счетчик эл-тов списка

List *CurrEl, // Текущий эл-т списка

*PrevEl; // Предыдущий эл-т

CurrEl = this; // Текущий эл-т — Объект

while (Count < i){ // Преход к i-му эл-ту

PrevEl = CurrEl; // Сохранение пред. эл-та

CurrEl = CurrEl-> Next;

Count++;

}

PrevEl-> Next = CurrEl-> Next; // Пред. эл-т ссыл-ся на след.

free (CurrEl);

}

//--------------------------------------------------------------//

// Функция добавления элемента в конец списка //

// с заданием элемента из программы //

//--------------------------------------------------------------//

void List: :AddElList (float Val, char R)

{

List *CurrEl,

*NewEl = new List;

CurrEl = this; // Текущий эл-т — Объект

List* KeyWord;

KeyWord = R? this: NULL;

while (CurrEl-> Next≠ KeyWord){ // Переход в конец списка

CurrEl = CurrEl-> Next;

}

CurrEl-> Next = NewEl; // Новый эл-т — в конец списка

NewEl-> Value = Val; // Ввод знач-я нового эл-та

NewEl-> Next = KeyWord; // Новый эл-т ссылается на NULL

}

//----------------------------------------------------------------//

// Функция создания списка (ввод первого эл-та //

// списка, созд. конструктором без параметров) //

//-----------------------------------------------------------------//

void List: :CreateList ()

{

List *CurrEl;

char ch;

int Ok = 0;

CurrEl = this; // Текущий эл-т — Объект

do

if ((Value == 0)||(Ok == 1)){

cout < < «Lead the value of the first «; //Запрос на ввод

// значения

cout < < «element of new list «< <'n';

cin > > CurrEl-> Value;

break;

}

else

while (1);

}

//---------------------------------------------------------------------------

//-------------------------------------//

// Производный класс: //

// двусвязный список //

//-------------------------------------//

class DLList: public List{

List *Prev; // Адрес пред. эл-та списка

public:

DLList (): List (){

Prev = NULL;

}

void AddElList ();

void DelElList (int);

void AddElList (float);

};

//------------------------------------------------------------------------------//

// Функция добавления элемента в конец двусвязного списка //

//------------------------------------------------------------------------------//

void DLList: :AddElList ()

{

DLList *CurrEl, // Текущий эл-т списка

*NewEl = new DLList; // Новый эл-т списка

// Выдел-е памяти под нов. эл-т

CurrEl = this; // Текущий эл-т — Объект

while (CurrEl-> Next≠ NULL){ // Переход в конец списка

CurrEl = (DLList*) CurrEl-> Next;

}

cout < < «Lead the value of new element of list «;

cin > > NewEl-> Value; // Ввод знач-я нового эл-та

NewEl-> Next = NULL; // Новый эл-т ссылается на NULL

CurrEl-> Next = NewEl; // Новый эл-т — в конец списка

NewEl-> Prev = CurrEl; // Новый эл-т ссыл-ся на пред.

}

//----------------------------------------------------------------------//

// Функция удаления i-го элемента двусвязного списка //

//----------------------------------------------------------------------//

void DLList: :DelElList (int i)

{

int Count = 1; // Счетчик эл-тов списка

DLList *CurrEl, // Текущий эл-т списка

*PrevEl; // Предыдущий эл-т

CurrEl = this; // Текущий эл-т — Объект

while (Count < i){ // Преход к i-му эл-ту

PrevEl = CurrEl; // Сохранение пред. эл-та

CurrEl = (DLList*) CurrEl-> Next;

Count++;

}

PrevEl-> Next = (DLList*) CurrEl-> Next; // Пред. эл-т

// ссыл-ся на след.

PrevEl = (DLList*) PrevEl-> Next;

PrevEl-> Prev = CurrEl-> Prev; // След. эл-т ссыл-ся на пред.

free (CurrEl);

}

//------------------------------------------------------------------//

// Функция добавления элемента в конец списка //

// (двусвязного) с заданием элемента из программы //

//------------------------------------------------------------------//

void DLList: :AddElList (float Val)

{

DLList *CurrEl,

*NewEl = new DLList;

CurrEl = this; // Текущий эл-т — Объект

while (CurrEl-> Next≠ NULL){ // Переход в конец списка

CurrEl = (DLList*) CurrEl-> Next;

}

CurrEl-> Next = NewEl; // Новый эл-т — в конец списка

NewEl-> Value = Val; // Ввод знач-я нового эл-та

NewEl-> Next = NULL; // Новый эл-т ссылается на NULL

}

//---------------------------------------------------------------------------

//---------------------------------------//

// Производный класс: //

// закольцованный список //

//---------------------------------------//

class RLList: public List{

public:

RLList (){

Value = 0;

Next = this;

}

};

//---------------------------------------------------------------------------

// Главная функция программы тестирует работоспособность

// Класса «Список»

int main (int argc, char **argv)

{

List TestL;

int Number;

char ch = 'Y'; // Вспомогательная перем-я

char Key = ' ', *PKey;

// Приветствие

cout < < «Hellow! You have ran the program of»;

cout < < «processing Lists just now.» < < 'n';

cout < < «First part:» < < 'n';

cout < < «Please, enter you choose:» < < 'n';

PKey = & Key;

// Главное меню

do{

cout < < «1 — New List» < < 'n';

cout < < «2 — Adding Element to List» < < 'n';

cout < < «3 — Deleting Element of List» < < 'n';

cout < < «4 — Output List to screen» < < 'n';

cout < < «5 — Exit» < < 'n';

Key = getch ();

switch (Key){

case '1': TestL. CreateList (); break;

case '2': TestL. AddElList (0); break;

case '3': cout < < «Enter the number of element»;

cout < < «you want to delete» < < 'n';

cin > > Number;

TestL. DelElList (Number); break;

case '4': TestL. OutpList (0); break;

case '5': break;

default: cout < < «Input Error»;

}

fread (PKey, 1,1,stdin);

if (Key == '5')

break;

clrscr ();

}

while (1);

clrscr ();

cout < < «Second part:» < < 'n';

List L1(«L1»); // Объект — список

do

if ((ch == 'Y')

while (1); // Бесконечный цикл

L1. AddElList (12, 0);

L1. OutpList (0); // Вывод сп-ка на экран

getch ();

clrscr ();

cout < < «Third part:» < < 'n';

List L2;

int i;

L2. CreateList ();

for (i = 0; i <= IMAX; i++){

L2. AddElList ((float) i+1, 0);

}

L2. OutpList (0); // Вывод сп-ка на экран

getch ();

return 0;

}

//---------------------------------------------------------------------------

В приведенном примере определяется базовый класс односвязного списка List в соответствии с концепцией полны класса содержащий все основные функции работы со списками:

void AddElList (char) — добавление элемента,

void OutpList (char) — вывод содержимого мписка на экран,

void DelElList (int) — удаление элемента,

void CreateList () — первоначальное создание списка.

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

Спецификатор доступа protected позволяет расширить область видимости полей-данных класса List так, чтобы их можно было использовать в классах, производных от класса List — DLList и RLList. Функции-члены класса List описаны с использованием спецификатора protected, что позволяет использовать их имена как при определении производных классов, так и во всех остальных функциях программы. Ссылка на предыдущий элемент списка в описании класса DLList по умолчанию описана как privat, поскольку данный класс не является базовым для других классов.

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

Функция main () в примере предназначена лишь для проверки работоспособности определенных в программе классов и поэтому содержит описание необходимого объекта и меню для его тестирования. При желании число объектов в основной части программы можно пополнить.

Заключение

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

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

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

Список литературы

1. М. Уэйт, С. Прата, Д. Мартин Язык Си: Пер с англ. -М.: Мир, 2007. -463 с., ил.

2. Уинер Р. Язык Турбо Си: Пер с англ. -М.: Мир, 2010. -384 с., ил.

3. В. В. Подбельский. Язык C++: Учебное пособие. — Москва: Финансы и статистика, 199 560с.

4. Ирэ Пол. Объектно-ориентированное программирование с использованием C++: Пер. с англ. — Киев: НИИПФ ДиаСофт Лтд, 199 480с.

5. Т. Фейсон. Объектно-ориентированное программирование на Borland C++ 4. 5: Пер. с англ. — Киев: Диалектика, 1996. 544с.

ПоказатьСвернуть
Заполнить форму текущей работой