Главная | Форум | Реклама | Shop Flash-JePo | Картинки | Регистрация | Вход
[ Новые сообщения · Участники · Правила форума · Поиск · RSS ]
  • Страница 1 из 1
  • 1
Форум » Web программирование » С++ » Перегруженные функции
Перегруженные функции
JeepersДата: Пятница, 25.03.2011, 15:14 | Сообщение # 1




Администратор портала







Главный Админ
Сталин
Группа: Администраторы
Сообщений: 937
Награды: 0
Репутация: 13

Итак, мы уже знаем, как объявлять, определять и использовать функции в программах. В этой главе речь пойдет об их специальном виде – перегруженных функциях. Две функции называются перегруженными, если они имеют одинаковое имя, объявлены в одной и той же области видимости, но имеют разные списки формальных параметров. Мы расскажем, как объявляются такие функции и почему они полезны. Затем мы рассмотрим вопрос об их разрешении, т.е. о том, какая именно из нескольких перегруженных функций вызывается во время выполнения программы. Эта проблема является одной из наиболее сложных в C++. Тем, кто хочет разобраться в деталях, будет интересно прочитать два раздела в конце главы, где тема преобразования типов аргументов и разрешения перегруженных функций раскрывается более подробно.

9.1. Объявления перегруженных функций

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

вызывается операция целочисленного сложения, тогда как вычисление выражения

1.0 + 3.0

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

9.1.1. Зачем нужно перегружать имя функции

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

int i_max( int, int );
int vi_max( const vector<int> & );
int matrix_max( const matrix & );

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

int ix = max( j, k );
vector<int> vec;
//...
int iy = max( vec );

Этот подход оказывается чрезвычайно полезным во многих ситуациях.

9.1.2. Как перегрузить имя функции

В C++ двум или более функциям может быть дано одно и то же имя при условии, что их списки параметров различаются либо числом параметров, либо их типами. В данном примере мы объявляем перегруженную функцию max():

int max ( int, int );
int max( const vector<int> & );
int max( const matrix & );

Для каждого перегруженного объявления требуется отдельное определение функции max() с соответствующим списком параметров.
Если в некоторой области видимости имя функции объявлено более одного раза, то второе (и последующие) объявление интерпретируется компилятором так:

  • если списки параметров двух функций отличаются числом или типами параметров, то функции считаются перегруженными:

    // перегруженные функции
           void print( const string & );
           void print( vector<int> & );

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

    // объявления одной и той же функции
           void print( const string &str );
           void print( const string & );

    Имена параметров при сравнении объявлений во внимание не принимаются;
    если списки параметров двух функций одинаковы, но типы возвращаемых значений различны, то второе объявление считается неправильным (несогласованным с первым) и помечается компилятором как ошибка:

      unsigned int max( int i1, int i2 ); int max( int i1, int i2 );  
           // ошибка: отличаются только типы
           // возвращаемых значений

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

    // объявления одной и той же функции
    int max ( int *ia, int sz );
    int max ( int *ia, int = 10 );

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

    // typedef не вводит нового типа
    typedef double DOLLAR;
    // ошибка: одинаковые списки параметров, но разные типы
    // возвращаемых значений
    extern DOLLAR calc( DOLLAR );
    extern int calc( double );

    Спецификаторы const или volatile при подобном сравнении не принимаются во внимание. Так, следующие два объявления считаются одинаковыми:

    // объявляют одну и ту же функцию
    void f( int );
    void f( const int );

    Спецификатор const важен только внутри определения функции: он показывает, что в теле функции запрещено изменять значение параметра. Однако аргумент, передаваемый по значению, можно использовать в теле функции как обычную инициированную переменную: вне функции изменения не видны. (Способы передачи аргументов, в частности передача по значению, обсуждаются в разделе 7.3.) Добавление спецификатора const к параметру, передаваемому по значению, не влияет на его интерпретацию. Функции, объявленной как f(int), может быть передано любое значение типа int, равно как и функции f(const int). Поскольку они обе принимают одно и то же множество значений аргумента, то приведенные объявления не считаются перегруженными. f() можно определить как

    void f( int i ) { }

    или как

    void f( const int i ) { }

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

    // объявляются разные функции
    void f( int* );
    void f( const int* );

    // и здесь объявляются разные функции
    void f( int& );
    void f( const int& );

    9.1.3. Когда не надо перегружать имя функции

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

    void setDate( Date&, int, int, int );
    Date &convertDate( const string & );
    void printDate( const Date& );

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

    #include <string>
    class Date {
    public:
        set( int, int, int );
        Date& convert( const string & );
        void print();

        // ...
    };

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

    Screen& moveHome();
    Screen& moveAbs( int, int );
    Screen& moveRel( int, int, char *direction );
    Screen& moveX( int );
    Screen& moveY( int );

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

    // функция, объединяющая moveX() и moveY()
    Screen& move( int, char xy );

    Теперь у всех функций разные списки параметров, так что их можно перегрузить под именем move(). Однако этого делать не следует: разные имена несут информацию, без которой программу будет труднее понять. Так, выполняемые данными функциями операции перемещения курсора различны. Например, moveHome() осуществляет специальный вид перемещения в левый верхний угол экрана. Какой из двух приведенных ниже вызовов более понятен пользователю и легче запоминается?

    // какой вызов понятнее?
    myScreen.home(); // мы считаем, что этот!
    myScreen.move();

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


    moveAbs(int, int);
    moveAbs(int, int, char*);

    различаются наличием третьего параметра типа char*. Если их реализации похожи и для третьего аргумента можно найти разумное значение по умолчанию, то обе функции можно заменить одной. В данном случае на роль значения по умолчанию подойдет указатель со значением 0:

    move( int, int, char* = 0 );

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



  • Требуются: Модераторы, Журналист новостей, Дизайнер!
    Shop Flash-JePo
     
    Реклама
    Форум » Web программирование » С++ » Перегруженные функции
    • Страница 1 из 1
    • 1
    Поиск: