В аспекте программирования связь типа СОДЕРЖИТдостаточно очевидна. Разберём следующий пример:
class Vehicle
{
} ;
class Motor
{
} ;
class Car : public Vehicle
{
public :
Motor motor ;
} ;
void VehicleFn( Vehicle & v ) ;
void motorFn( Motor & m ) ;
_________________
238 стр. Часть 4. Наследование
int main( )
{
Car с ;
VehicleFn( с ) ; /* Так можно вызвать */
motorFn( c ) ; /* А так — нельзя */
motorFn( с.motor ) ; /* Нужно вот так */
return 0 ;
}
Вызов VehicleFn( с ) допустим, поскольку с ЯВЛЯЕТСЯ Vehicle . Вызов motorFn( с ) недопустим, поскольку с — не Motor , хотя он и содержит Motor . Если возникает необходимость передать функции только ту часть с , которая является мотором, это следует выразить явно: motorFn( с.motor ) .
_________________
239 стр. Глава 20. Наследование классов
Глава 21. ЗНАКОМСТВО С ВИРТУАЛЬНЫМИ ФУНКЦИЯМИ-ЧЛЕНАМИ: НАСТОЯЩИЕ ЛИ ОНИ...240
ОГЛАВЛЕНИЕ
В этой главе...
►Зачем нужен полиморфизм 243
►Как работает полиморфизм 245
►Когда функция не является виртуальной 246
►Виртуальные особенности 247
Количество и тип аргументов функции включены в её полное или, другими словами, расширенное имя. Это позволяет создавать в одной программе функции с одним и тем же именем ( если различаются их полные имена ):
void someFn( int )
void someFn( char* )
void someFn( char* , double )
Во всех трёх случаях функции имеют одинаковое короткое имя someFn( ) . Полные имена всех трёх функций различаются: someFn( int ) отличается от someFn( char* ) и т.д. С++ решает, какую именно функцию нужно вызвать, рассматривая полные имена слева направо.

«Тип возвращаемого значения не является частью полного имени функции, поэтому вы не можете иметь две функции с одинаковым расширенным именем, отличающиеся только типом возвращаемого объекта.»
[ Атас! ]
Итак, функции-члены могут быть перегружены. При этом помимо количества и типов аргументов расширенное имя функции-члена содержит ещё и имя класса.
С появлением наследования возникает небольшая неувязка. Что, если функция-член базового класса имеет то же имя, что и функция-член подкласса? Попробуем разобраться с простым фрагментом кода:
class Student
{
public :
float calcTuition( ) ;
} ;
class GraduateStudent : public Student
{
public :
float calcTuition( ) ;
} ;
int main( int argcs , char* pArgs[ ] )
{
Student s ;
GraduateStudent gs ;
s.calcTuition( ) ; /* Вызывает Student::calcTuition( ) */
gs.calcTuition( ) ; /* Вызывает GraduateStudent::calcTuition( ) */
return 0 ;
}
_________________
240 стр. Часть 4. Наследование
Как и в любой ситуации с перегрузкой, когда программист обращается к calcTuition( ) , С++ должен решить, какая именно функция calcTuition( ) вызывается. Если две функции отличаются типами аргументов, то нет никаких проблем. Даже если аргументы одинаковы, различий в именах класса достаточно, чтобы решить, какой именно вызов нужно осуществить, а значит, в этом примере нет ничего необычного. Вызов s.calcTuition( ) обращается к Student::calcTuition( ) , поскольку s локально объявлена как Student , тогда как gs.calcTuition( ) обращается к GraduateStudent::calcTuition( ) .
Но что, если класс объекта не может быть точно определён на этапе компиляции? Чтобы продемонстрировать подобную ситуацию, нужно просто немного изменить приведённую выше программу:
//
/* OverloadOverride — демонстрация невозможности */
/* точного определения типа */
//
#include
#include
#include
using namespace std ;
class Student
{
public :
/* Раскомментируйте одну из двух следующих строк; одна выполняет раннее связывание calcTuition( ), а вторая — позднее */
float calcTuition( )
/* virtual float calcTuition( ) */
{
cout << "Функция Student::calcTuition" << endl ;
return 0 ;
}
} ;
class GraduateStudent : public Student
{
public :
float calcTuition( )
{
cout << "Функция GraduateStudent::calcTuition"
<< endl ;
return 0 ;
}
} ;
void fn( Student & x )
{
x.calcTuition( ) ; /* Какая функция calcTuition( ) должна быть вызвана? */
}
_________________
241 стр. Глава 21. Знакомство с виртуальными функциями-членами: настоящие ли они
Читать дальше