Полиморфизм. Перегрузка функций
Слово полиморфизм греческого происхождения: "наличие в пределах одного вида резко отличающихся по облику особей". В языке С каждая функция должна иметь уникальное имя. Одним из путей реализации полиморфизма в С++ является перегрузка функций (overloading), то есть использование одного имени для разных функций. Компилятор различает такие функции по числу и типу параметров. В С++ могут перегружаться любые функции, в том числе и методы класса. При этом действуют следующие ограничения.
1. Не могут перегружаться функции, имеющие совпадающие тип и число аргументов, но разные типы возвращаемых значений. Например, если объявить функции: int fun(long i) и long fun(long i), то при вызове функции: fun(70000) перед компилятором встанет неразрешимая проблема выбора, так как при вызове функции тип возвращаемого значения никак не может быть указан.
2. Не могут перегружаться функции, имеющие неявно совпадающие типы аргументов, например, int и int&.
Пример 13.
Рассмотрим механизмы описания и использования перегружаемых функций получения суммы двух аргументов.
#include<iostream.h>
// перегрузка функции add:
int add(int i, int j)
{ cout<<"Сложение целых чисел\n";
return i + j;
}
double add(double i, double j)
{ cout<<"Сложение вещественных чисел\n";
return i + j;
}
void main ()
{ cout << "2 + 3=" << add(2,3) << '\n';
cout << "2.5 + 2.0=" << add(2.5, 2.0) << '\n';
}
Полезность таких функций в том, что они повышает ясность программ. Особенно удобна перегрузка конструкторов, инициализирующих объекты.
Перегрузка конструкторов
Конструкторы могут быть перегружены, что позволяет создавать объекты в зависимости от значений, используемых при инициализации.
Пример 14.
Демонстрация перегруженных конструкторов.
class X
{ int integer_part;
double double_part;
public:
X ( int i ) { integer_part = i; } // конструктор с инициализацией
X ( double d) // перегруженный конструктор
{ double_part = d; }
};
void main()
{ X one (10); // вызов конструктора X::X(int)
X two (3.14); // вызов конструктора X::X(double)
}
Функция с параметрами по умолчанию
С++ разрешает описание функций или объявление их прототипов с формальными параметрами, имеющими значение по умолчанию, которые должны быть последними в списке при объявлении функции.
Например:
char func (char ch, int i, int k=5)
{ тело функции }
В функции func при ссылке на нее с тремя аргументами, например, при вызове функции:
ret = func ('A', 10, 25);
параметры получат значения: ch = 'A', i = 10, k = 25.
Но если при вызове функции список аргументов будет состоять из двух элементов, например:
ret = func ('A', 10);
то параметры получат значения: ch = 'A', i = 10, k = 5 (по умолчанию).
Если функция описана, например, так:
char Fun (int a = 5, int j = 7)
{ тело функции },
то она может вызываться с двумя или одним аргументом или даже вовсе без аргументов.
Конструктор по умолчанию
Конструктор, не имеющий параметров, называется конструктором по умолчанию. Если отсутствует определенный пользователем конструктор по умолчанию, компилятор С++ генерирует собственный конструктор по умолчанию для описанного класса – это конструктор, например, класса Х, который не принимает никаких аргументов X :: X().
Конструктор, как и любая функция, может иметь любое количество параметров, в том числе и параметры по умолчанию. Например, конструктор
X :: X (int, int=0)
может принимать один или два аргумента. Если будет только один аргумент, то недостающий второй аргумент будет принят как 0. Аналогично, конструктор
X :: X (int=5, int=6)
может принимать два аргумента, один аргумент, либо не принимать аргументов вообще, причем в каждом случае принимаются значения, соответствующие умолчаниям.
Однако конструктор по умолчанию X :: X() не принимает аргументов вообще, и его не следует путать с конструктором, например, X :: X (int=0), который может либо принимать один аргумент, либо не принимать аргументов. При вызове конструкторов следует избегать неоднозначностей. В следующем примере возможна неоднозначная трактовка компилятором конструктора по умолчанию и конструктора, принимающего целый параметр.
Пример 15.
Неоднозначность конструкторов.
class X
{ public:
X();
X( int = 0);
};
void main()
{ X one (10); // так МОЖНО: используется конструктор X::X(int)
X two; // так НЕЛЬЗЯ: X::X() или X::X(int=0) ?
}
Конструктор копирования
Это особый тип конструктора. Конструктор копирования для класса Х – это конструктор, который имеет один аргумент ссылочного типа X& и, возможно, параметры по умолчанию. Ссылочный тип тесно связан с типом указатель (X*) и позволяет передавать в функцию не сам объект, а только ссылку на него.
По умолчанию, объекты класса можно копировать. В частности, объект некоторого класса можно проинициализировать при помощи копирования объекта того же класса. Это можно сделать даже там, где объявлен конструктор.
Например, для класса X корректное описание конструктора копирования может быть таким:
class X
{ public:
X() {...} // конструктор по умолчанию
X(const X&) {...} // конструктор копирования
X(const X&, int i=4) {...} // конструктор копирования с параметром // по умолчанию
};
Конструктор копирования вызывается тогда, когда выполняется копирование объекта, обычно при объявлении объекта с его инициализацией, например:
X one; // вызов конструктора по умолчанию
X two = one; // вызов конструктора копирования
X two (one); // вызов конструктора копирования с параметром
По умолчанию, копия объекта класса содержит копию каждого члена, т.е. объект two будет полной копией объекта one. Аналогично, объекты класса могут по умолчанию почленно копироваться при помощи операции присваивания:
X three;
three = one;
Подобное почленное копирование приемлемо, если оно выполняется для статических и автоматических переменных класса. Выделение динамической памяти для объектов с использованием указателей и их копирование будет рассмотрено ниже.