Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

class Base

{ public:

virtual void f1();

virtual void f2();

virtual void f3();

void f();

};

class Derived: public Base

{ public:

void f1();

void f2(int); // Скрывает Base:: f2()

char f3(); // Ошибка – различие только в типе возвращаемого значения!

void f();

};

 

Derived *dp = new Derived;

Base *bp = dp; // Преобразование указателя на производный класс в указатель на базовый класс

bp-> f(); // Вызов Base:: f

dp-> f(); // Вызов Derived:: f

dp-> Base:: f(); // Вызов Base:: f

dp-> f1(); // Вызов Derived:: f1

bp-> f1(); // Всё равно вызов Derived:: f1!!!

bp-> Base:: f1(); // Вызов Base:: f1 (использование явного квалификатора блокирует механизм виртуальности)

bp-> f2(); // Вызов Base:: f2

dp-> f2(0); // Вызов Derived:: f2

dp-> f2(); // Ошибка, т.к. не задан параметр

dp-> Base:: f2(); // Вызов Base:: f2

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

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

Этот механизм делает производные классы и виртуальные функции ключевыми понятиями при разработке программ на С++. Базовый класс определяет интерфейс, для которого производные классы обеспечивают набор реализаций. Указатель на объект класса может передаваться в контекст, где известен интерфейс, определённый одним из его базовых классов, но этот производный класс неизвестен. Механизм виртуальных функций гарантирует, что этот объект всё равно будет обрабатываться функциями, определёнными для него, а не для базового класса.

Абстрактный класс

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

Чисто виртуальной функцией называется виртуальная функция, указанная с инициализатором =0

Например:

virtual void F1(int) =0;

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

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

 

Дружественные функции.

5.1 “Дружественные” (friend) функции

Функция, объявленная в производном классе, может иметь доступ только к защищенным (protected) или общим (public) компонентам базового класса.

Функция, объявленная вне класса, может иметь доступ только к общим (public) компонентам класса и обращаться к ним по имени, уточненному именем объекта или указателя на объект.

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

class X

{ friend void Y:: fprv( int, char*);

/* Другие компоненты класса X */

}

Можно объявить все функции класса Y дружественными в классе X;

class Y;

class X

{ friend Y;

/* Другие компоненты класса X */

}

class Y

{ void fy1(int, int);

int fy2( char*, int);

/* Другие компоненты класса Y */

}

Дружественной может быть и функция, не являющаяся компонентой какого-либо класса, например,

class XX

{ friend int printXX ( );

/* Другие компоненты класса ХХ */

}

Здесь функция printXX имеет доступ ко всем компонентам класса XX, независимо от закрепленного за ними уровня доступа.

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

5.2. Переопределение операторов с помощью дружественных функций.

Иногда для переопределения операций прибегают к услугам дружественных функций. Хотя дружественные функции и имеют доступ к приватным данным класса, но указатель this они не получают. Поэтому, например, с их помощью нельзя переопределить операцию '='. А другие унарные или бинарные операции такому переопределению поддаются легко, надо только передавать в дружественные функции на один параметр больше:

class Tpoint {

int x, y;

public

Tpoint(){x=0; y=0; } //конструктор по умолчанию

Tpoint(int xx, int yy){x=xx; y=yy; } //конструктор инициализации

void GeTpoint(int & xx, int & yy){xx=x; yy=y; } //опрос координат

friend Tpoint operator+(Tpoint P1, Tpoint P2);

};

Tpoint Tpoint:: operator+(Tpoint P1, Tpoint P2)

{ Tpoint q;

q.x=P1.x+P2.x; q.y=P1.y+P2.y; return q;

}

 

- Двуместные операции должны иметь два параметра, одноместные - один параметр, причем, если операция объявлена как компонента класса, то неявным первым операндом является экземпляр объекта (следовательно при определении двуместной операции будет задаваться один параметр, одноместная операция объявляется с пустым списком параметров). Если операция переопределяется вне класса (с описателем friend ), то для двуместной операции должны быть заданы два параметра, для одноместной операции - один параметр.

 

Шаблоны функций и классов

Шаблоны функций

Часто встречаются функции, реализующие одни и те же действия для аргументов различных типов. Например, сортировка массива по возрастанию его элементов может выполняться одним и тем же методом и для данных типа int и для данных типа double. Различие состоит только в типах параметров и некоторых внутренних переменных.

В более поздние версии С++ включено специальное средство, позволяющее параметризовать определение функции, чтобы компилятор мог построить конкретную реализацию функции для указанного типа параметров функции. Параметризованное определение функции строится по схеме:

template < class имя_класса >

Заголовок функции

{ /* Тело функции */ }

 

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

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

template < class T >

void sort ( T a[ ], int n )

{ T temp;

int sign;

for ( int k = 0; k > n; k++)

{ sign = 0;

for ( i = 0; i < n - k; i++)

if ( a [ i ] > a [ i + 1])

{ temp = a [ i ];

a[ i ] = a[ i + 1 ];

a[ i + 1 ] = temp; sign++;

}

if ( sign == 0 ) break;

}

return;

}

 

Если в программе будут объявлены массивы

int aint [10];

double afl [20];

 

и установлены значения элементов этих массивов, то вызов функции

sort ( aint, 10 );

 

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

sort ( afl, 20 )

 

обеспечит вызов sort для упорядочения массива с элементами типа double.

Если элементами массива являются объекты какого-либо определенного программистом класса, для которого определена операция отношения >, то функция sort может быть вызвана и для такого массива. Разумеется, в объектном коде программы будут присутствовать все варианты реально вызывамой функции sort. Параметризация функции сокращает объем исходного текста программы и повышает его надежность.

В описателе template можно указывать несколько параметров вида class имя_типа, а также параметры базовых типов. Например, функция

template < class T1, class T2 >

void copy ( T1 a[ ], T2 b[ ], int n)

{ for ( int i = 0; i < n; i++)

a[ i ] = b [ i ];

}

 

копирует первые n элементов массива b типа T2 в первые n элементов массива a типа T1. Разумеется, программист несет ответственность за то, чтобы такое копирование было возможным.

Шаблоны классов

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

template < class T >

class описание класса

 

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

В качестве примера приведем описание класса stack, предназначенного для построения стеков фиксированного максимального размера с элементами произволного типа.

enum BOOLEAN ( FALSE, TRUE );

template < class Type >

class stack

{ private:

enum ( EMPTY = -1 );

Type* s; /* Указатель на массив стека */

int max_len; /* Максимальная длина стека */

int top; /* Индекс элемента в вершине стека */

public:

stack ( ): max_len ( 100 ) /* конструктор без параметров */

{ s = new Type [ 100 ]; top = EMPTY; }

stack ( int size ): max_len( size ) /* Второй конструктор */

{ s = new Type [ size ]; top = EMPTY; }

~stack ( ) { delete [ ] s; } /* Деструктор */

void reset ( ) { top = EMPTY; } /* Очистить стек */

void push ( Type c ) { s [ ++top ] = c; }

Type pop ( ) { return (s [top—] }

Type top_of ( ) { return ( s [top ] }

BOOLEAN empty ( ) { return BOOLEAN ( top == EMPTY ) }

BOOLEAN full ( ) { return BOOLEAN ( top == max_len ) }

};

 

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

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

stack < int > stack_of_int (50); /* Стек на 50 элементов типа int */

stack < myClass > stmc (20); /* Стек на 20 элементов типа myClass */

 

В приведенном примере все компоненты-функции определены в описании класса. Когда полное определение функции-члена класса задается вне описания класса, оно должно уточняться описателем template. Например, если бы метод top_of был определен вне описания класса, определение имело бы вид:

template < class Type >

Type top_of ( ) { return s [ top ]; }

 

Отметим некоторые специфические черты описаний параметризованных классов.

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

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

Контрольные вопросы. (какие есть)

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

class Parent {....};

class Child: [модификатор наследования] Parent {....};

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

Модификатор доступа Модификатор наследования

public protected private

Public Public Protected Private

Protected Protected Protected Private

private Нет доступа Нет доступа Нет доступа

С помощью параметра-ссылки или параметра-указателя.

 

 

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

20.

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

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

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

Но:

• Вы не можете получить адрес ссылки, используя оператор адреса C++.

• Вы не можете присвоить ссылке указатель.

• Вы не можете сравнить значения ссылок, используя операторы сравнения C++.

• Вы не можете выполнить арифметические операции над ссылкой, например добавить смещение.

•Вы не можете изменить ссылку.

22.

Любой конструктор копирования имеет следующую форму.

имя_класса (const имя_класса & obj)

{

... // тело конструктора

}

 

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

30.

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

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

Различие между вызовом статического метода и вызовом виртуального метода - это различие между решением немедленным и отложенным, задержанным решением.

31.

совместимость имеет три формы:

• между экземплярами объектов;

• между указателями объектов;

• между формальными и фактическими параметрами.

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

Объекту абстрактному классу можно присвоить любой объект-потомок

 


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-07-14; Просмотров: 1333; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.056 с.)
Главная | Случайная страница | Обратная связь