Void SetVal(PSTR sz);
ПЕРЕГРУЗКА ФУНКЦИЙ
Назначение перегрузки
Перегрузка функций – это один из типов полиморфизма, обеспечиваемого C++. В C++ несколько функций могут иметь одно и то же имя. В этом случае функция, идентифицируемая этим именем, называется перегруженной. Перегрузить можно только функции, которые отличаются либо типом, либо числом своих аргументов. Перегрузить функции, которые отличаются только типом возвращаемого значения, нельзя. Перегружаемые функции дают возможность упростить программы, допуская обращение к одному имени для выполнения близких по смыслу действий.
Чтобы перегрузить некоторую функцию, нужно просто объявить, а затем определить все требуемые варианты ее вызова. Компилятор автоматически выберет правильный вариант вызова на основании числа и типа используемых аргументов.
Пример 1.
#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);
mF=abs(F);
mD=abs(D);
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) |-255|=255 abs(float) |-25|=25 abs(double) |-2.55|=2.55
Библиотека времени выполнения C++ использует три разных функции abs(), labs() и fabs() для вычисления абсолютного значения аргумента. Использование той или другой из них зависит от типа аргумента. Так, в данном примере осуществляется перегрузка функции abs(), что упрощает программу. В зависимости от переданного аргумента вызывается нужный вариант функции.
Этот пример наглядно демонстрирует, как использование перегрузки может упростить код, предоставляя программисту возможность пользоваться одной функцией, которой передаются аргументы различного типа. Разумеется, возможность использования перегрузки предъявляет повышенные требования к компилятору.
Декорирование компилятором имен функций
Чтобы реализовать концепцию перегрузки, разработчикам компиляторов C++ пришлось ввести декорирование имен. Последнее означает, что все функции в коде программы получают от компилятора имена, основываясь на имени, заданном программистом, и количестве и типах аргументов. Различные компиляторы делают это несколько отличным друг от друга образом. Здесь мы опишем, как это делает компилятор фирмы Inprise(ранее – фирма Borland).
Вначале идет символ "@" и имя класса, затем символ "@" и имя функции, У всех идентификаторов различается регистр букв. Затем следует последовательность символов "@q", начиная с которой идут кодированные обозначения параметров функции. Для обозначения указателей и ссылок к кодам встроенных типов добавляются буквы "р" и "r", соответственно. Например, если дано такое определение класса:
Class AnyClass
{
public:
Void SetVal();
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
Подробнее о декорировании имен, в частности о кодировании имен конструкторов и деструкторов, а также имен, указанных через операцию разрешения области видимости, можно прочесть в документации, поставляемой с компилятором.
Отсюда видно, что в декорировании имен возвращаемое функцией значение не участвует. Именно по этой причине нельзя перегружать функции, которые отличаются только типом возвращаемого значения. Разобраться с тем, как декорирует имена конкретный компилятор достаточно легко. Достаточно потребовать от него выдать ассемблерный эквивалент текста программы.
Декорирование имен порождает и определенные проблемы. Например, если нужно написать код, который будет вызываться из программы написанной на другом языке. Тогда описанную функцию можно отключать в форме extern "C"{}.
Перегрузка функций
Перегрузка – это практика предоставления более чем одного определения для данного имени функции в одной и той же области видимости. Возможность выбрать соответствующую версию функции, основываясь на типах и числе аргументов, с которыми она вызывается, предоставляется компилятору. При этом два типа данных считаются различными, если для них используются различные инициализаторы. Поэтому аргумент данного типа и ссылка на этот тип рассматриваются как одно и то же с точки зрения перегрузки. Например, объявление двух таких функций
int func(int);иint func(int&);
приведет к ошибке, так как с точки зрения перегрузки они считаются одинаковыми.
Аргументы функции, относящиеся к некоторому типу, модифицированные constили volatile, не рассматриваются как отличные от базового типа с точки зрения перегрузки.
Указатели на const- и volatile-объекты также не рассматриваются как отличные от указателей на базовый тип с точки зрения перегрузки.
Однако механизм перегрузки может различать ссылки, которые имеют модификаторы constи volatile, и ссылки на базовый тип.
Вместе с тем на перегруженные функции накладываются несколько ограничений:
Þ любые две перегруженные функции должны иметь различные списки параметров;
Þ перегрузка функций с совпадающими списками аргументов на основе лишь типа возвращаемых ими значений недопустима;
Þ функции-члены не могут быть перегружены исключительно на основе того, что одна из них является статической, а другая – нет;
Þ все enum-типы данных рассматриваются как различные и могут использоваться для различения перегруженных функций;
Þ типы "массив(чего-то)" и "указатель(на что-то)" рассматриваются как идентичные с точки зрения перегрузки. Это верно только для одномерных массивов.
Þ typedef-определения не влияют на механизм перегрузки, так как они не вводят новых типов данных, а определяют лишь синонимы для существующих типов. Например, следующее определение typedef char* PSTR;не позволит компилятору рассматривать две приведенные ниже функции
void SetVal(char* sz);
void SetVal(PSTR sz);
как различные. Поэтому их одновременное объявление в классе вызовет ошибку.
Þ Для многомерных массивов вторая и последующие размерности рассматриваются как часть типа данных. Поэтому они могут использоваться для различения перегруженных функций. Например, вполне допустимы следующие определения:
void SetVal(char S[]);
void SetVal(char S[][4]);
При объявлении перегруженных функций компилятор отслеживает объявления, данные в пределах одной и той же области видимости. Поэтому, если в производном классе объявлена функция с тем же именем, что и функция в базовом классе, функция из производного класса скрывает функцию базового класса вместо того, чтобы осуществлять перегрузку (так как их области видимости различны). Короче говоря, нельзя путать переопределение функций с перегрузкой. Точно также компилятор отслеживает и другие области видимости.
Поэтому, так как функция, объявленная в области видимости файла, находится не в той же области видимости, что и функция, объявленная в блоке, даже если они имеют одинаковые имена, компилятор не рассматривает их как перегруженные. Локально объявленная функция просто скрывает глобально объявленную.
Перегрузка конструкторов
Чаще всего перегрузка применяется при создании перегруженных конструкторов (перегружать деструктор нельзя!). Целью этой перегрузки является желание предоставить пользователю как можно больше вариантов создания представителей класса. На самом деле мы уже неоднократно встречались с перегрузкой конструкторов, хотя и не говорили об этом. Если класс предоставляет конструктор с параметрами и конструктор по умолчанию, мы уже имеем дело с перегрузкой конструкторов. Как мы уже знаем, конструктор по умолчанию необходим при выделении динамической памяти массиву объектов(ибо динамический массив объектов не может быть инициализирован).
Другой случай, когда возникает необходимость в перегрузке конструкторов, – желание обеспечить пользователя конструкторами преобразования типа.
Пример 2.
Class Rect
{
Int a,b,w,h;
public:
Rect();
Rect(int, int);
Дата добавления: 2016-10-17; просмотров: 1097;