6.4 Ссылки на объекты базового и производного классов

----------------------------------------------------

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

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

типов ссылок должны быть явнями)


Побочный эффект такого преобразования состоит в том, что транслятор "забывает" об объекте производного класса и вместо переопределенных в нем функций вызывает функции базового класса.


class A

{

public: void f1();

};


class B : A

{

public: void f1(); // Переопределена в классe B

void f2(); //

};


A *pa;

B *pb;

B x;


pa = &x; // Неявное преобразование ссылки

// на объект класса B в ссылку

// на объект класса A

pa->f1(); // Вызов функции из вложенного

// объекта базового класса A::f1(),

// хотя она переопределена


Обратное преобразование от ссылки на базовый класс к ссылке

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


pb = (B*) pa; // Обратное преобразование - явное

pb ->f2(); // Корректно, если под "pa" был

// объект класса "B"


6.5 Принцип объектно-ориентированного программирования

------------------------------------------------------

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

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

новые функции включают вызовы ранее определенных.


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

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

и добавляет новые. В принципе ничто не препятствует на любом

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

классов. Следование же технологии объектно-ориентированного программирования "до конца" предполагает, что прикладная программа

представляет собой класс самого верхнего уровня, в ее выполнение

- создание объекта этого класса или выполнение для него некоторой функции типа "run".

Лекция 7. Виртуальные функции.

-----------------------------

7.1 Понятие виртуальной функции

------------------------------

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


p[] A1

+---+ -b---------¬

¦ --------------------->-a-------¬¦======== b::f()

+---+ ¦L---------¦===¬

¦ ------------¬ L----------- ¦

+---+ ¦ C1 ¦

¦ ----------¬ ¦ -c---------¬ ¦

+---+ ¦ L-------->-a-------¬¦======== c::f()

¦ ¦L---------¦===¦

¦ L----------- ¦

¦ A1 ¦

L---------->-a-------¬ ===¦==== a::f()

L--------

class a

{ ... void f(); };


class b : public a

{ ... void f(); };


class c : public a

{ ... void f(); };


a A1;

b B1;

c C1;


a *p[3]; // Массив ссылок на объекты БК

p[0] = &B1; // Ссылки на объекты БК в

p[1] = &C1; // объектах ПК

p[2] = &A1;


Однако при таком преобразовании типа "ссылка на объект ПК" к

типу "ссылка на объект БК" происходит потеря информации о том,

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

есть


p[0]->f(); // Вызов a::f()

p[1]->f(); // во всех случаях, хотя f()

p[2]->f(); // переопределены


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

- хранить в объекте базового класса идентификатор "окружающего" его производного класса;

- в списке или таблице хранить ссылки на объект базового

класса;

- при вызове функции по ссылке на объект базового класса

идентифицировать тип производного класса и явно вызывать для него

переопределенную функцию;

- идентификатор класса устанавливать при создании объекта ,

то есть в его конструкторе.


class a

{

public: int id; // Идентификатор класса

void f();

void newf(); // Новая функция f() с идентификацией ПК

}


a::a() // Конструкторы объектов

{ ...

id = 0;

}

b::b()

{ ...

id = 1;

}

c::c()

{ ...

id = 2

}


void a::newf()

{

switch (id)

{

case 0: a::f(); break;

case 1: b::f(); break;

case 2: c::f(); break;

}

}


p[0]->newf(); // Вызов b::f() для B1

p[1]->newf(); // Вызов c::f() для C1

p[2]->newf(); // Вызов a::f() для А1


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

Таким образом, если при преобразовании типа "ссылка на ПК" к типу "ссылка на БК" происходит потеря информации об объекте производного класса, то при вызове виртуальной функции происходит обратный процесс неявного восстановления типа объекта.


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

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


class A

{

------> void (**ftable)(); // Ссылка на таблицу адресов

// виртуальных функций

public:

virtual void x();

virtual void y();

virtual void z();

A();

~A();

};

// Таблица адресов функций класса А


------> void (*TableA[])() = { A::x, A::y, A::z };

A::A()

{

------> ftable = TableA; // Установка таблицы для класса А

}


class B : public A

{

public:

void x();

void z();

B();

~B();

};

// Таблица адресов функций класса A

// в классе B


--> void (*TableB[])() = { B::x, A::y, B::z };

¦ L переопределяется в B

B::B() L------ наследуется из A

{

--> ftable = TableB; // Установка таблицы для класса B

}


void main()

{

A* p; // Ссылка p базового класса A

B nnn; // ссылается на объект производp = &nnn; // ного класса B

реализация

p->z(); ------------------> (*(p->ftable[2]))();

}


p nnn TableB B::z()

-----¬ -------->--B-----¬ ----->---------¬ --->----------¬

¦ ------ ftable¦--A---¬¦ ¦ 0+--------+ ¦ ¦ ¦

L----- ¦¦ ------ 1+--------+ ¦ ¦ ¦

¦+-----+¦ 2¦ --------- L--------- ¦¦ ¦¦ L--------



Информация о работе «Объективное программирование»
Раздел: Информатика, программирование
Количество знаков с пробелами: 61028
Количество таблиц: 0
Количество изображений: 0

Похожие работы

Скачать
14044
1
2

... обучающих программ Обучающие программы, построенные на бихевиористской основе, подразделяют на: а) линейные, разработанные Скиннером, б) разветвленные программы Н. Краудера. 1. Линейная система программированного обучения, первоначально разработанная американским психологом Б. Скиннером в начале 60-х гг. ХХ в. на основе бихевиористского направления в психологии. ·  Он выдвинул следующие ...

Скачать
93027
12
0

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

Скачать
109055
0
0

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

Скачать
165676
21
12

... отбора. ГЛАВА II. ЦЕЛЬ, ЗАДАЧИ, МЕТОДЫ И ОРГАНИЗАЦИЯ ИССЛЕДОВАНИЯ   2.1 Цель и задачи исследования Цель исследования - повышение эффективности физической подготовки вратарей учебно-тренировочных групп на соревновательном этапе. В процессе реализации поставленной цели решались следующие основные задачи: 1.     Изучить особенности возрастного развития двигательных способностей футболистов ...

0 комментариев


Наверх