Выбрать главу

►Как работает полиморфизм 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 <cstdio>

    #include <cstdlib>

    #include <iostream>

    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. Знакомство с виртуальными функциями-членами: настоящие ли они

    int main( int nNumberofArgs , char* pszArgs[ ] )