Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ООП Лекции PDF / ООП 07 Лек Перегрузка функций

.pdf
Скачиваний:
67
Добавлен:
15.02.2015
Размер:
228.24 Кб
Скачать

Лекция 7

Перегрузка операторов

1

Л Е К Ц И Я 7

ПЕРЕГРУЗКА ФУНКЦИЙ ________________________________________________________________ 1

Назначение перегрузку __________________________________________________________________ 1 Декорирование компилятором имен функций________________________________________________ 2 Перегрузка функций_____________________________________________________________________ 2 Перегрузка конструкторов________________________________________________________________ 3 Неоднозначность перегрузки _____________________________________________________________ 3

ПЕРЕГРУЗКА ФУНКЦИЙ

Назначение перегрузки

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

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

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

#include <iostream.h>

int abs ( int ) ;

// прототипы перегруженных функций

// функция получает и возвращает целое значение

float abs ( float ) ;

// функция оперирует с вещественным числом одинарной точности

double abs ( double ) ;

// функция оперирует с вещественным числом двойной точности

void main ( )

 

{

 

int mN , N = -255 ;

float mF , F = -25.0f ; double mD , D = -2.55 ;

mN = abs ( N ) ;

// вызов перегруженной функции abs ( int )

mF = abs ( F ) ;

// вызов перегруженной функции abs ( float )

mD = abs ( D ) ;

// вызов перегруженной функции abs ( double )

cout << '\n' ;

cout << '|' << N <<"| = " << mN << '\t' ; cout << '|' << F <<"| = " << mF << '\t' ; cout << '|' << D <<"| = " << mD << '\n' ;

}

int abs ( int a )

{

cout<<"abs(int)\t"; return a < 0 ? –a : a ;

}

float abs ( float a )

{

cout<<"abs(float)\t"; return a < 0 ? –a : a ;

}

double abs ( double a )

{

cout<<"abs(double)\t"; return a < 0 ? –a : a ;

}

При выполнении программа выводит на экран:

abs(int)

abs(float)

abs(double)

|-255| = 255

|-25| = 25

|-2.55| = 2.55

Библиотека времени выполнения C++ использует три разных функции abs ( ) , fabs ( ) и fabsf ( ) для вычисления абсолютного значения аргумента. Использование той или другой из них зависит от типа аргу-

Выжол Ю.А.

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

Лекция 7

Перегрузка операторов

2

мента. Так, в данном примере осуществляется перегрузка функции abs ( ), что упрощает программу. В зависимости от переданного аргумента вызывается нужный вариант функции.

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

Декорирование компилятором имен функций

Чтобы реализовать концепцию перегрузки, разработчикам компиляторов C++ пришлось ввести декорирование имен. Последнее означает, что все функции в коде программы получают от компилятора имена, основываясь на имени, заданном программистом, и количестве и типах аргументов. Различные компиляторы делают это несколько отличным друг от друга образом. Здесь мы опишем, как это делает компилятор фирмы Inprise (ранее – фирма Borland).

Вначале идет символ "@" и имя класса, затем символ "@" и имя функции, У всех идентификаторов различается регистр букв. Затем следует последовательность символов "@q", начиная с которой идут кодированные обозначения параметров функции. Для обозначения указателей и ссылок к кодам встроенных типов добавляются буквы "р" и "r", соответственно. Например, если дано такое определение класса:

class AnyClass

{

public:

void SetVal ( void ) ; void SetVal ( int );

void SetVal ( int* , double ) ; void SetVal ( int& , int , float ) ;

}

компилятор Borland C++ сгенерирует такие имена функций:

@AnyClass@SetVal@qv

@AnyClass@SetVal@qi

@AnyClass@SetVal@qpid

@AnyClass@SetVal@qriif

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

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

Перегрузка функций

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

int func ( int ) ; int func ( int& ) ;

приведет к ошибке, так как с точки зрения перегрузки они считаются одинаковыми.

Аргументы функции, относящиеся к некоторому типу, модифицированные const или volatile, не рассматриваются как отличные от базового типа с точки зрения перегрузки.

Указатели на const- и volatile-объекты также не рассматриваются как отличные от указателей на базовый тип с точки зрения перегрузки.

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

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

Выжол Ю.А.

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

Лекция 7

Перегрузка операторов

3

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

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

все enum-типы данных рассматриваются как различные и могут использоваться для различения перегруженных функций;

типы "массив (чего-то)" и "указатель (на что-то)" рассматриваются как идентичные с точки зрения перегрузки.

typedef-определения не влияют на механизм перегрузки, так как они не вводят новых типов данных, а определяют лишь синонимы для существующих типов.

Например, следующее определение typedef char* PSTR ;

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

void SetVal ( char* sz ) ; void SetVal ( PSTR sz ) ;

как различные. Поэтому их одновременное объявление в классе вызовет ошибку.

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

void SetVal (char S [ ] ) ; void SetVal (char S [ ] [4] ) ;

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

Перегрузка конструкторов

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

Другой случай, когда возникает необходимость в перегрузке конструкторов, – желание обеспечить пользователя конструкторами преобразования типа.

Неоднозначность перегрузки

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

найдено точное соответствие; выполнено тривиальное преобразование;

выполнено преобразование целочисленных типов; существует стандартное преобразование к желаемому типу аргумента;

существует определенное программистом преобразование (оператор преобразования или конструктор) к требуемому типу аргумента;

были найдены аргументы, представленные многоточием.

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

Выжол Ю.А.

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

Лекция 7

Перегрузка операторов

4

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

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

Функция с n аргументами по умолчанию, с точки зрения соответствия аргументов, рассматривается как совокупность из n+1 функций, каждая из которых отличается от предыдущей заданием одного дополнительного аргумента. Многоточие (...) действует как произвольный символ: оно соответствует любому заданному аргументу. Это может служить источником неоднозначности при выборе перегруженной функции.

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

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

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

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

Выжол Ю.А.

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