Здравствуйте, Red Line, Вы писали:
Надо сразу сказать, что перегрузка чего-либо в Си++ это только для удобства программиста, но никак не то, без чего нельзя обойтись в принципе.
Что такое оператор? Любой? Это всего на всего некоторая фукция, кстати возвращающая значение, но у этой функции есть три особенности.
Имя. Имена операторов стандартизированны. Ты можешь как угодно использовать те, что есть, но не можешь создавать свои
Количество параметров. Ты мошешь как угодно обрабатывать параметры, но не можешь изменить их число. Не может быть операции + на тремя аргументами.
Синтаксис вызова. Если для обычной функции параметры перечисляются в скобках, через запятую, после имени функции, то для операторов это не так.
Рассмотрим простой пример
int a = 3;
int b = 5;
int c = a + b;
Формально здесь задействованно 2 оператора. Оператор присваивания = и оператор сложения +.
Это встроенные операторы. Что это значит? Это значит, что ими можно пользоватся без написания своего кода. Они готовы к употребления с самого начала.
Что такое перегрузка операторов? Это определение оператора для какого-то типа(или типов если параметров несколько), для которого такой оператор ещё не опредлён. То есть, например, нельзя определить оператор + для параметров типа int потому что такой оператор (не важно, встроенный или нет) уже определён.
Например у нас есть тип комплексных чисел. Очевидно, что запись
complex a;
complex b;
complex c = a + b;
Понятнее и проще, чем
complex a;
complex b;
complex c = ComplexAdd(a, b);
Вот для того чтобы проще писать программы операторы и перегружают.
Есть однако одно исключение, оператор присваивания =. Его надо перегружать, если копирование объекта выполняется каким-то особым образом.
Например рассмотрим класс строки.
class string
{
private:
char * str;
int len;
public:
string(char * init)
{
len = strlen(init);
str = new char[len + 1];
strcpy(str, init);
}
~string()
{
delete[] str;
}
}
Вроде бы класс правильный. Мы выделяем память в конструкторе и освобождаем в деструкторе. Но я не написал оператор присваивания для этого класса. Что же будет? Он сгенерируется автоматически. Такой оператор присваивания будет поэлементно (не побитово!) копировать поля из одного объекта в другой.
Как же это будет работать?
Вот простой пример
{
string a("aaaa");
string b("bbb");
b = a;
}
Итак. (Адреса памяти взяты для примера, они могут быть совершенно другими)
Сперва в конструкторе a выделится 5 байт для строки "aaaa" по адресу 0x00100000 и она будет скопирована в выделенную память.
Затем в конструкторе b выделится 4 байта для строки "bbb" по адресу 0x001000005 и она будет скопирована в выделенную память.
Далее полю str объекта b будет присвоена значение поля str объекта a и оно станет равно 0x00100000, а полю len объекта b будет присвоено значение поля len объякта a и оно станет равно 5.
Затем в деструкторе a мы особождаем ранее выделенную память по адресу 0x00100000.
Далее в деструкторе b мы особождаем ранее выделенную память по адресу 0x00100000.
Что мы имеем? Мы дважда освободили память по адресу 0x00100000 и ни разу не осободили память по адресу 0x00100005.
Первое приведёт к ошибке выполнения, второе к утечке памяти.
Как же исправить такое неправильное поведение? Надо определить оператор присваивания. Свой, не тривиальный оператор, который всё сделает как надо. А вот и он
class string
{
private:
char * str;
int len;
public:
string(char * init)
{
len = strlen(init);
str = new char[len + 1];
strcpy(str, init);
}
~string()
{
delete[] str;
}
// Возвращаем ссылку на string.
// Если не знаешь, что такео сылки, не надо утруждать себя мыслями зачем,
// достаточно пока знать, что это позволяет избежать лишнего копирования
string & operator = (const string & s)
{
// Удаляем старую строку, она нам больше не понадобится.
// Обрати внимание, что это очень похоже на код деструктора
delete[] str;
// Выясняем длину строки и выделяем необходимое количество байт. Далее копируем новую строку в выделенную память.
// Обрати внимание, что это очень похоже на код конструктора
len = s.len;
str = new char[len + 1];
strcpy(str, s.str);
// Возвращаем то, гед сейчас новое значение строки, то есть сам объект.
return *this;
}
}
Вот теперь всё будет работать правильно. Справедливости ради надо сказать, что в некоторых случаях, компилятор может использовать оператор присваивания вместо конструктора копирования. Например как здесь.
string a("kuku")
string b = a; // скорее всего соптимизируется в string b(a);
Поэтому для избежания лишних проблем конструктор копирования лучше тоже написать.
string(const string & s)
{
len = s.len;
str = new char[len + 1];
strcpy(str, s.str);
}