|
Программирование >> Инициализация объектов класса, структура
объявляют одну и ту же функцию 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, но выполняют семантически различные действия. В этом случае лексическая сложность, связанная с #include <string> class Date { public: set( int, int, int ); Date& convert( const string & ); void print(); ... при этом оставить разные имена, отражающие смысл операции: Приведем еще один пример. Следующие пять функций-членов Screen выполняют различные операции над экранным курсором, являющимся принадлежностью того же класса. Может показаться, что разумно перегрузить эти функции под общим названием Screen& moveHome() ; Screen& moveAbs( int, int ); Screen& moveRel( int, int, char *direction ); Screen& moveX( int ); move() : Screen& moveY( int ); Впрочем, последние две функции перегрузить нельзя, так как у них одинаковые списки функция, объединяющая moveX() и moveY() параметров. Чтобы сделать сигнатуру уникальной, объединим их в одну функцию: Screen& move( int, char xy ); Теперь у всех функций разные списки параметров, так что их можно перегрузить под именем move() . Однако этого делать не следует: разные имена несут информацию, без которой программу будет труднее понять. Так, выполняемые данными функциями операции перемещения курсора различны. Например, moveHome() осуществляет специальный вид перемещения в левый верхний угол экрана. Какой из двух приведенных какой вызов понятнее? myScreen.home(); считаем, что этот! ниже вызовов более понятен пользователю и легче запоминается? myScreen.move(); В некоторых случаях не нужно ни перегружать имя функции, ни назначать разные имена: применение подразумеваемых по умолчанию значений аргументов позволяет объединить несколько функций в одну. Например, функции управления курсором употреблением различных имен, проистекает из принятого программистом соглашения об обеспечении набора операций над типом данных и именования функций в соответствии с семантикой этих операций. Правда, механизм классов C++ делает такое соглашение излишним. Следовало бы сделать такие функции членами класса Date, но #include <string> void print( const string & ); void print( double ); перегружает print() void fooBar( int ival ) отдельная область видимости: скрывает обе реализации print() extern void print( int ); ошибка: print( const string & ) не видна в этой области print( Value: ); print( ival ); правильно: print( int ) видна примеру, локально объявленная функция не перегружает, а просто скрывает глобальную: Поскольку каждый класс определяет собственную область видимости, функции, являющиеся членами двух разных классов, не перегружают друг друга. (Функции-члены класса описываются в главе 13. Разрешение перегрузки для функций-членов класса рассматривается в главе 15.) Объявлять такие функции разрешается и внутри пространства имен. С каждым из них также связана отдельная область видимости, так что функции, объявленные в разных #include <string> namespace IBM { extern void print( const string & ); extern void print( double ); пер extern void print( double ); перегружает print() namespace Disney от не отдельная область видимости: не перегружает функцию print() из пространства имен IBM extern void print( int ); пространствах, не перегружают друг друга. Например: moveAbs(int, int); moveAbs(int, int, char*); различаются наличием третьего параметра тина char*. Если их реализации похожи и для третьего аргумента можно найти разумное значение по умолчанию, то обе функции можно заменить одной. В данном случае на роль значения по умолчанию подойдет указатель со значением 0: move( int, int, char* = 0 ); Применять те или иные возможности следует тогда, когда этого требует логика приложения. Вовсе не обязательно включать перегруженные функции в программу только потому, что они существуют. 9.1.4. Перегрузка и область видимости A Все перегруженные функции объявляются в одной и той же области видимости. К
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |