Объектно-ориентированное программирование на C++

       

Перегружаемые функции и операторы (overload)


Одна из ключевых черт полиморфизма в С++ - замещение или перегрузка операторов и функций.

Важным расширением, пришедшим из языка АДА, является то, что транслятор С++ различает функции не только по именам, но и по типу аргументов.

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

int power (int x) {return x*x;}

Или другой пример, связанный с некоторыми неудобствами, возникающими при использовании библиотечных функций из языка С: abs(), labs() и fabs() возвращают абсолютное значение, соответственно, целого, длинного целого и числа с плавающей точкой. Из-за того, что для трех типов требуются три типа функции (т.е. три разных имени), ситуация выглядит неоправданно усложненной. В тоже время в С++ можно "перегрузить" одно имя для трех типов данных.

int abs (int x) { return x<0 ? -x:x; }  // 1

long abs (long x) { return x<0 ? -x:x; } // 2

double abs (double x) { return x<0 ? -x:x; } // 3

main()

{

cout << "абсолютная величина -1000" << abs(-1000) << "\n";   // вызов функции 1

cout << "абсолютная величина -1000L" << abs(-1000L) << "\n";   // вызов функции 2

cout << "абсолютная величина -1000.0" << abs(-1000.0) << "\n";   // вызов функции 3

}



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

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

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


 _3d c;

 c.x = x + b.x;

 c.y = y + b.y;

 c.z = z + b.z;

 return c;

}

Мы включили описание прототипа оператора внутрь структуры (инкапсулировали). Определение (реализацию) кода вынесем. Доступ к элементам структуры осуществляется привычным образом - через ".".

Теперь мы вправе написать следующий фрагмент:

_3d A, B, C;

A.x = 1.0;

A.y = 1.0;

A.x = 1.0;

B = A;

C = A + B; // это эквивалентно вызову C = A.operator + (B); /operator +(...) - один из методов объекта A/

Существует еще ряд ограничений на замещение операторов по сравнению с замещением функций:

  • оператор должен уже существовать в языке (нельзя добавлять совершенно новые, выдуманные вами операторы);
  • нельзя переопределять действия встроенных в С++ операторов при работе со встроенными типами данных; так, Вы не можете изменить способ работы оператора "+" при сложении целых чисел;
  • запрещено замещать операторы ".", ".*", "?:", "::" и символы препроцессора "#".


  • И последнее замечание. При переопределении операторов нужно придерживаться простого правила, - переопределенный оператор должен реализовывать некоторое действие, сходное по смыслу с уже определенными операторами, имеющими такое обозначение. (Впрочем, то же самое справедливо вообще для переопределяемых функций). Конечно, ничто не мешает переопределить, например, оператор "+" для вашего типа таким образом, чтобы в момент его вызова на экран 18 раз выводилось сообщение "Спартак-Чемпион". Однако очевидно, что использование такого оператора неминуемо приведет к путанице и ошибкам.

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

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

    Например,

    void f (int i=0, int j=1); // прототип функции f (...)



    ...

    void f (int i, int j)

    {

    ... // тело функции

    }

    Теперь эту функцию можно вызывать тремя способами

    f (); // i по умолчанию равно 0, j по умолчанию равно 1

    f (10); // i равно 10, j по умолчанию равно 1

    f (11, 22); // i равно 11, j равно 22

    Задать значение j, установив i по умолчанию, нельзя. И вообще, все параметры по умолчанию должны находиться правее аргументов, передаваемых обычным путем.

    Несколько слов об аргументах по умолчанию. Ими могут быть только константы или глобальные переменные.

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

    [назад] [оглавление] [вперед]

    Содержание раздела