Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

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

Виртуальные функции

 

Мы уже упоминали о полиморфизме - важной особенности объектно-ориентированного программирования. Рассмотрим следующий пример (6):

 

#include <iostream.h>

class X

{

public:

double A(double x) { return x * x; }

double B(double x) { return A(x) / 2; }

};

class Y: public X

{

public:

double A(double x) { return x * x * x; }

};

int main ()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

 

В классе X объявляются функции A и B, причем функция B вызывает функцию А. Класс Y, потомок класса X, наследует функцию B, но переопределяет функцию A. Цель этого примера - демонстрация полиморфного поведения класса Y. Мы должны получить следующий результат: вызов наследуемой функции X::B должен привести к вызову функции Y::A. Что же выдаст нам наша программа? Ответом будет 4.5, а не 13.5! В чем же дело? Почему компилятор разрешил выражение y.B(3) как вызов наследуемой функции X::B, которая, в свою очередь, вызывает X::A, а не функцию Y::A, что должно было бы произойти в случае полиморфной реакции класса?

 

Виртуальные функции объявляются следующим образом (синтаксис):

 

class className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

class className2: public className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

 

Пример 7, показывающий, как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y:

 

#include <iostream.h>

class X

{

public:

virtual double A(double x) { return x * x; }

double B (double x) { return A(x) / 2; }

};

class Y: public X

{

public:

virtual double A(double x) { return x * x * x; }

};

main()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

 

Этот пример выведет вам правильное значение 13.5, потому что в результате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции A во время выполнения программы будет использована замещающая функция Y::A.

 

 

Правило виртуальной функции гласит:

 

"Виртуальная однажды - виртуальна всегда".

 

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

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

Рассмотрим пример 8, иллюстрирующий сказанное.

 

#include <iostream.h>

class A

{

public:

A() {}

virtual void foo(char c)

{ cout << "virtual A::foo() returns " << c << endl; }

};

class B: public A

{

public:

B() {}

void foo(const char* s)

{ cout << "B::foo() returns " << s << endl; }

void foo(int i)

{ cout << "B::foo() retuzns " << i << endl; }

virtual void foo(char c)

{ cout << "virtual B::foo() returns " << c << endl; }

};

class C: public B

{

public:

C() {}

void foo(const char* s)

{ cout << "C::foo() returns " << s << endl; }

void foo(double x)

{ cout << "C::foo() returns " << x << endl; }

virtual void foo(char c)

{ cout << "virtual C::foo() returns " << c << endl; }

};

int main()

{

A Aobj;

B Bobj;

C Cobj;

Aobj.foo('A');

Bobj.foo('B');

Bobj.foo(10);

Bobj.foo("Bobj");

Cobj.foo('C');

Cobj.foo(144.123);

Cobj.foo("Cobj");

return 0;

}

 

В этом примере вводятся три класса - A, B и C - образующих линейную иерархию наследования. В классе A объявляется виртуальная функция foo(char).

Класс B объявляет свою версию виртуальной функции foo(char), но, кроме того, в классе B объявляются невиртуальные перегруженные функции foo(const char*) и foo(int). Класс C объявляет свою версию виртуальной функции foo(char) и невиртуальные перегруженные функции foo(const char*) и foo(double). Обратите внимание на то, что в классе C приходится заново объявлять функцию foo(const char*), поскольку в данном случае функция-элемент B::foo(const char*) не наследуется. Таким образом, в С++ схема наследования отличается от обычной для случая виртуальной и перегруженных функций с одинаковым именем. В функции main объявляются объекты для всех трех классов и вызываются различные версии функции-элемента foo.

 

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

 

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

 

Общий вид (синтаксис) объявления дружественной функции следующий:

 

class className

{

public:

className();

// другие конструкторы

friend returnType friendFunction(<список параметров>);

};

 

Пример 9:

 

class String

{

protected:

char *str;

int len;

public:

String();

~String();

// другие функции-элементы

friend String& append(String &str1, String &str2);

friend String& append(const char* str1, String &str2);

friend String& append(String &str1, const char* str2);

};

 

Дружественные функции могут решать задачи, которые при помощи

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

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

Текст программы FRIEND.CPP представлен в листинге 8.5. Программа следит за памятью, отведенной для хранения массива символов. Эта программа - первый шаг к созданию класса string.

 

<== предыдущая лекция | следующая лекция ==>
Операции и дружественные операции | Исходные тексты примеров. Операции и дружественные операции
Поделиться с друзьями:


Дата добавления: 2014-01-05; Просмотров: 281; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.008 сек.