Особенности наследования классов в C++

 

Содержание


Введение

. Абстрактные классы

. Множественное наследование

. Адреса базовых классов

. Виртуальное наследование

Заключение

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



Введение


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

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

Для инициализации элементов производного класса ваша программа должна вызвать конструкторы базового и производного классов. Используя оператор точку, программы могут легко обращаться к элементам базового и производного классов.

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

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


1. Абстрактные классы


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

Employee { // Класс Служащий:name[40]; // Имя служащего:(char* n);

//Чистая виртуальная функцияvoid* promote()=0;


Абстрактный класс не может быть реализован в объекте. Так, следующая строка:

s("My name");


вызовет ошибку, о которой сообщит компилятор.

Однако программа может объявить указатель абстрактного класса, так что следующая строка вполне допустима:

* sPtr;


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

Secretary: public Employee {:moreData;:(char* n): Employee(n) { }void* promote();

};sec("Another Name");


Адрес объекта Secretary может быть передан в функцию, которая ожидает передачи Employee:

fn(Employee*);sec("Another Name");(&sec);


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



2. Множественное наследование


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

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

SubClass: public Base1, private Base2 {

// остальная часть определения класса

}


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

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

(1) конструкторы всех виртуальных базовых классов; если их имеется более одного, то конструкторы вызываются в порядке их наследования;

(2) конструкторы невиртуальных базовых классов в порядке их наследования;

(3) конструкторы всех компонентных классов.

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


#include <iostream.h>Base1 {() { cout<< "Создание Base1"<<endl; }

};Base2 {() { cout<< "Создание Base2"<<endl; }

};Base3 {() { cout<< "Создание Base3"<<endl; }

};Base4 {() { cout<< "Создание Base4"<<endl; }

};Derived: private Base1, private Base2,private Base3 {anObject;() {}

};main() {anObject;

}

На выходе этой программы будет следующее:

Создание Base1

Создание Base2

Создание Base3

Создание Base4

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

Derived: private Base1, private Base2, private Base3 {anObject;(): anObject(), Base3(), Base2(), Base1() {}

};


Изменение порядка наследования классов изменит последова- тельность появления сообщений на выходе программы. Объявив Derived следующим образом:

Derived: private Base3, private Base2, private Base1 {anObject;(): anObject(), Base1(), Base2(), Base3() {}

};


можно получить на выходе программы:

Создание Base3

Создание Base2

Создание Base1

Создание Base4

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

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


3. Адреса базовых классов


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

Base {a;b;f1();Derived: public Base {c;

} object;


Рассматривая размещение объекта производного класса Derived в памяти, например, с помощью команды отладчика Inspect, можно получить несколько упрощенную графическую диаграмму, которая приведена на рис. 1.


Base** Base

Derived



Рис. 1. Схема размещения в памяти простого производного класса


Если Derived* передан в функцию, ожидающую получения Base*, никаких проблем не возникает. Класс Derived совпадает с классом Base во всем, что касается его части, перекрывающейся с Base. То же самое касается и вызова функции object.f1(). Передаваемый указатель this имеет одно и то же значение безотносительно к типу. Однако в отношении класса Derived с множественным наследованием сказанное ранее будет несправедливо.

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

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

struct Base1 {a; float b;f1();

};Base2 {c;d;f2();

};Derived: public Base1, public Base2 {e;

} object;


Примерная схема памяти для этого случая показана на рис. 2.

Класс Base2 более не находится в начале класса Derived. Если попробовать передать Derived* функции, ожидающей поступления Base1*, то проблем не возникнет. Вместе с тем, при вызове функции, ожидающей поступления Base2*, полученный ей адрес окажется неправильным.

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


Base1**

Base 1


* Derived


Base 2

Рис. 2. Схема размещения в памяти класса с множественным наследованием

object;.f2() // Перед передачей в f2() адрес объекта object

// должен быть соответственно скорректирован

По тем же причинам C++ также должен выполнить коррекцию и при обратном приведении типа из Base2* в Derived*.



4. Виртуальное наследование


В следующем примере класс Derived наследует свойства двух копий класса Base: одной через класс FirstBase, а второй - через SecondBase:

Base {object;

};FirstBase: public Base {a;

};SecondBase: public Base {b;Derived: public FirstBase, public SecondBase {dObject;

};


Примерная схема памяти для объекта класса Derived показана на рис. 3.



Base

FirstBase




Base Derived

SecondBase

Рис. 3. Схема размещения в памяти множественного производного класса


Чтобы позволить наследование в таких случаях одной и той же копии Base, в C++ необходимо включить в команду наследования ключевое слово virtual. В этом случае, программу можно переписать следующим образом:

Base {object;

};FirstBase: virtual public Base {a;

};SecondBase: virtual public Base {b;Derived: virtual public FirstBase,public SecondBase {dObject;

};

Такая модификация программы изменит схему размещения объекта класса Derived, как показано на рис. 4.



int object Base


int a FirstBase


float b SecondBase Derived

long

dObject

Рис. 4. Схема размещения в памяти виртуального множественного производного класса


Теперь существует всего одна копия класса Base.



Заключение


Наследование в C++ позволяет вам строить /порождать) новый класс из существующего класса. Строя таким способом один класс из другого, вы уменьшаете объем программирования, что, в свою очередь, экономит ваше время. C++ позволяет вам порождать класс из двух или нескольких базовых классов. Использование нескольких базовых классов для порождения класса представляет собой множественное наследование. В заключении можно сделать следующие выводы:

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

.Производный класс - это новый класс, а базовый класс - существующий класс.

.Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса.

.Для порождения класса из базового начинайте определение производного класса ключевым словом class, за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian: dog.

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

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

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

.Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:: member.



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


1)Т. Сван. Освоение Borland C++ 4.5: Пер. с англ. - Киев: Диалектика, 1996. 544с.

2)Г. Шилдт. Самоучитель C++: Пер. с англ. - Санкт-Петербург: BHV-Санкт-Петербург, 1998. 620с.

)У. Сэвитч. C++ в примерах: Пер. с англ. - Москва: ЭКОМ, 1997. 736с.

)К. Джамса. Учимся программировать на языке C++: Пер. с англ. - Москва: Мир, 1997. 320с.

)В.А. Скляров. Язык C++ и объектно-ориентированное программирование: Справочное издание. - Минск: Вышэйшая школа, 1997. 480с.

)Х. Дейтел, П. Дейтел. Как программировать на C++: Пер. с англ. - Москва: ЗАО "Издательство БИНОМ", 1998. 1024с.



Содержание Введение . Абстрактные классы . Множественное наследование . Адреса базовых классов . Виртуальное наследование Заключение Сп

Больше работ по теме:

КОНТАКТНЫЙ EMAIL: [email protected]

Скачать реферат © 2017 | Пользовательское соглашение

Скачать      Реферат

ПРОФЕССИОНАЛЬНАЯ ПОМОЩЬ СТУДЕНТАМ