Часть 1. Первое знакомство. Чего и куда грузим? - Языки программирования - Shelek
Раньше, или позже, но любой программист сталкивался с проблемой - какое имя дать новой функции. Конечно, компилятору-то "по барабану", что EfgD23j5v6H(int D4fG3e), что Search(int Number) - он и то и другое воспримет с одинаковой радостью. Но нам-то желательно иметь хоть немного осмысленные имена функций. Давайте представим себе, что мы пишем некую самопальную базу данных, и нам надо сделать в ней подсистему поиска. Это чисто гипотетическое предположение, скорее всего никто ТАК делать не будет :) Каждой функции поиска желательно дать осмысленное имя, что могло бы привести вот к такому результату:
Код: (C++)
int Search_int(int Num);
int Search_long(long Num);
int Search_float(float Num);
int Search_double(double Num);

Но гараздо проще дать всем этим функциям одно имя для поиска всех типов данных. Так, например:
Код: (C++)
int Search(int Num);
int Search(long Num);
int Search(float Num);
int Search(double Num);

Заметим - имена функций одинаковы, отличие только в типах аргументов. Подобный прием и называется перегрузкой функций. Цель перегрузки функций состоит в том, чтобы функции с одним именем ВЫПОЛНЯЛИСЬ ПО-РАЗНОМУ (и возможно возвращали разные значения) при обращении к ним с разными по типам и количеству параметрами. В языке С++ функции могут иметь одинаковые имена до тех пор, пока они значимо отличаются хотя бы одним параметром. Если значимого различия нет - компилятор предупредит о возникшей неопределенности.
Я привел такой (не очень удачный) пример еще с одной целью - хорошо, если вы сразу увидите, к каким проблемам может привести подобная перегрузка. Если нет - ничего страшного, мы все равно рассмотрим это попозже.
Необходимо отличать перегрузку и редекларацию (переобъявление, переопределение) функции. Если функции возвращают одинаковый тип и список параметров у них абсолютно одинаковый, то второе объявление функции будет обработано как повторное определение. Если списки параметров двух функций абсолютно одинаковы, но отличаются только типы возврата, то второе объявление - ошибка:
Код: (C++)
int Search(int Num);
long Search(int Num); //ошибка!

Списки параметров функций могут быть идентичны, даже если они и не выглядят одинаково. Давайте посмотрим несколько примеров, в них будет не перегрузка - а переопределение.
Код: (C++)
struct Phone {...};
struct Record {...};

typedef Phone Telno;
Record lookup(const Phone &);
Record lookup(const Telno &); // Telno и Phone это тот же самый тип

Выглядит как вполне разные типы. А на самом деле - одно и тоже, ведь Telno не новый тип, а всего лишь синоним типа Phone.
Код: (C++)
Record lookup(const Account &acct);
Record lookup(const Account &); // имя параметра игнорируется

Список параметров в объявлении первой функции содержит имя параметра - acct. Но имена параметров функций не изменяют сам список параметров.
Код: (C++)
Record lookup(const Phone &, const Name &);
Record lookup(const Phone &, const Name& = "Вася Пупкин");

Здесь списки параметров отличаются только заданным по умолчанию значением. Заданный по умолчанию параметр не изменяет количество параметров. Функция все равно имеет два параметра, независимо от того передаются ли они при вызове пользователем или компилятором.
Код: (C++)
Record lookup(Phone);
Record lookup(const Phone);

Последняя пара отличается только объявлением параметра константой. Вспомним, как передаются такие параметры. Когда параметр скопирован, являлся ли параметр константой - функции уже неважно, она выполняется с его копией. Функция не может изменить этот параметр. То есть, безразлично как передается объект - как константный, или нет. Эти два параметра неразличимы. Так что это - тоже переопределение. Но это в данном случае, а если параметр - ссылка или указатель, вот тогда const может сыграть очень важную роль :)
Теперь немного об обещаном выше - о проблемах :)
Наряду с признанием достоинств применения перегруженных функций существует и такое мнение - перегруженные функции вызывают больше проблем, чем их решают. Очень даже может быть. Посмотрим.
Вот, например, проблема двусмысленности:
Код: (C++)
compare(int, long);
compare(long, int);

compare(123, 123); //Ну, и какую из функций будем вызывать?

А уж к чему могут привести аргументы по умолчанию... Смотрим:
Код: (C++)
double calc(double x) {return x*x;}
double calc(double x, double y) {return x*y;}

double g;
g=calc(1.2345);
g=calc(1.2345, 5.4321);

В результате никаких проблем пока нет... Хорошо. Добавим еще одну функцию :)
Код: (C++)
double calc(double x=1.0, double y=1.0, double z=1.0) {return x*y*z;}

g=calc(1.2345, 5.4321); //Какую теперь вызывать? Компилятор плюется и ругается :)

Ну, а вот такое как?
Код: (C++)
int search(int);
int search(float);

search(3.1415); //компилятор в ступоре...

Вот уж тут-то, казалось бы чего не так? На первый взгляд все кажется нормально. Ан нет. Передаваемый функции параметр "3.1415" на самом деле имеет тип double. А компилятор может с одинаковым успехом автоматически преобразовать тип как в int, так и во float. Вот его и растаращило :)
Мда-с... А никто и не обещал, что будет легко :) Вывод - при написании и использовании перегруженных функций надо быть внимательным и аккуратным, дабы не удивляться потом, почему это компилятор вдруг подсовывает совсем не ту функцию, о которой вы мечтали, или вовсе не может определиться :)
Чтобы правильно использовать перегруженные функции, необходимо понять, как правильно определить набор перегруженных функций и как компилятор решает, какую именно функцию надо использовать для данного запроса.
Ну, что же, давайте этим и займемся :) Только уже в следующей части. На этот раз - хватит :)
Кстати, а в некоторых литературных источниках встречается мнение, что функциям (особенно в классах) лучше все же давать различные имена, а не одно имя "на всех". Аргумент - более осмысленные и интуитивно понятные имена функций. И с этим трудно не согласиться :)


Если у вас есть вопросы – пишите, будем разбираться.

Сергей Малышев (aka Михалыч).
Information
  • Posted on 31.01.2010 23:47
  • Просмотры: 1631