Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы на вопросы Осадчий А.В. гр.010902.docx
Скачиваний:
9
Добавлен:
24.04.2019
Размер:
143.34 Кб
Скачать

25.Функции-операции как члены класса и как дружественные функции.

Функции-операции могут быть, или не быть функциями-элементами; если функции не являются элементами, они обычно являются друзьями.

К тому же каждая из этих перегруженных функций-операций может потребовать доступа к закрытым элементам данным объекта класса, являющегося входным или выходным потоком, так что эти перегруженные функции-операции делают иногда функциями-друзьями класса из соображений эффективности.

Операции-функции можно вызывать непосредственно, хотя обычно они вызываются косвенно, при использовании перегруженных операций:

c3 = c1.operator + (c2); // то же, что c3 = c1 + c2

В отличие от new и delete, которые имеют свои собственные правила, операция-функция должна быть либо не-статической функцией-компонентом, либо иметь как минимум один аргумент типа класса. Операций-функции =, (), [] и -> должны являться нестатическими функциями-компонентами.

Фактически, операция — это та же функция, но записываемая особым образом. По этой причине логично иметь возможность определять операции для произвольных типов таким же образом, как и методы — чтобы можно было работать с ними точно так же, как и с элементарными типами. Эта возможность называется «перегрузка операций» и присутствует в большинстве языков 4—5 поколений. В таких языках транслятор, фактически, подставляет вместо выполнения операции вызов соответствующей ей функции.

Перегрузка операций в форме внешних ф-й

Чтобы перегрузить оператор в форме внешней ф-ции, необходимо определить глобальную ф-цию.

class String {

friend String& operator+(const String&, const String&);

private:

char* s; }

String& operator+(const String& s1, const String& s2)

{ .... }

Перегруженная ф-ция выглядит так же, как и любая глобальная ф-ция. Именно для таких случаев и были придуманы друзья. Если бы мы не объявили ф-цию operator+ другом, то она не имела бы доступа к переменной s.

Внешними ф-циями могут перегружаться любые операторы, кроме операторов преобразования, =, [], () и -> — все эти операторы должны перегружаться только ф-циями класса.

Перегрузка операторов в форме функций класса

Синтаксис напоминает обычную перегрузку функций класса, разве что кол-во аргументов уменьшается на 1 по сравнению с формой внешней ф-ции.

class String { private:

char* s;

public:

String& operator+(const String&) const; };

String& String::operator+(const String& s1) const

{ char* s2 = new char[strlen(s1.s) + strlen(s) + 1];

strcat(s2, s1, s);

String newStr(s2);

delete s2;

return newStr; }

Любой оператор может быть перегружен в форме ф-ции класса. Не применяется в случае если:

1. Первый аргумент относится к базовому типу (например, int или double).

2. Тип первого аргумента определен в коммерческой библиотеке, которую нежелательно

модифицировать.

26.Перегрузка операции присваивания. Условия вызова оператора присваивания и конструктора копирования.

Операция отличается тремя особенностями:

операция не наследуется;

операция определена по умолчанию для каждого класса в качестве операции поразрядного копирования объекта, стоящего справа от знака операции, в объект, стоящий слева.

операция может перегружаться только в области определения класса. Это гарантирует, что первым операндом всегда будет леводопустимое выражение.

Если вас устраивает поразрядное копирование, нет смысла создавать собственную функцию operator =(). Однако бывают случаи, когда поразрядное копирование нежелательно. Например, использование предопределенной операции присваивания для классов, содержащих указатели в качестве компонентных данных, чаще всего приводит к ошибкам. Покажем это на примере.

Пользовательский класс - строка String:

class String

{

public:

String(char *);

~String();

void show();

protected:

char *p; // указатель на строку

int len; // текущая длина строки

};

String::String(char *ptr)

{

len = strlen(ptr);

p = new chat[len + 1];

if (!p)

{

cout << "Ошибка выделения памяти\n");

exit(1);

}

strcpy(p, ptr);

}

String::~String()

{

delete [] p;

}

void String::show(void)

{

cout << *p& lt;< "\n";

}

int main(void)

{

String s1("Это первая строка"),

s2("А это вторая строка");

s1.show();

s2.show()

s2 = s1; // Это ошибка

s1.show();

s2.show();

return 0;

}

В чем здесь ошибка? Когда объект s1 присваивается объекту s2, указатель p объекта s2 начинает указывать на ту же самую область памяти, что и указатель p объекта s1. Таким образом, когда эти объекты удаляются, память, на которую указывает указатель p объекта s1, освобождается дважды, а память, на которую до присваивания указывал указатель p объекта s2, не освобождается вообще.

Хотя в данном примере эта ошибка и не опасна, в реальных программах с динамическим распределением памяти она может вызвать крах программы.

В этом случае необходимо самим перегружать операцию присваивания. Покажем как это сделать для нашего класса String.

class String

{

public:

...

String &operator =(String &);

protected:

char *p; // указатель на строку

int len; // текущая длина строки

};

String &String::operator =(String &ob);

{

if (this == &ob) return *this;

if (len < ob.len)

{

// требуется выделить дополнительную память

delete [] p;

p = new char[ob.len + 1];

if (!p)

{

cout << "Ошибка выделения памяти\n");

exit(1);

}

}

len = ob.len;

strcpy(p, ob.p);

return *this;

}

В этом примере выясняется, не происходит ли самоприсваивание (типа ob = ob). Если имеет место самоприсваивание, то просто возвращается ссылка на объект.

Затем проверяется, достаточно ли памяти в объекте, стоящем слева от знака присваивания, для объекта, стоящего справа от знака присваивания. Если не достаточно, то память освобождается и выделяется новая, требуемого размера. Затем строка копируется в эту память.

Отметим две важные особенности функции operator =(). Во-первых, в ней используется параметр-ссылка. Это необходимо для предотвращения создания копии объекта, передаваемого через параметр по значению. В случае создания копии, она удаляется вызовом деструктора при завершении работы функции. Но деструктор освобождает память, на которую указывает p. Однако эта память все еще необходима объекту, который является аргументом. Параметр-ссылка помогает решить эту проблему.

Во-вторых, функция operator =() возвращает не объект, а ссылку на него. Смысл этого тот же, что и при использовании параметра-ссылки. Функция возвращает временный объект, который удаляется после завершения ее работы. Это означает, что для временной переменной будет вызван деструктор, который освобождает память по адресу p. Но она необходима для присваивания значения объекту. Поэтому, чтобы избежать создания временного объекта, в качестве возвращаемого значения используется ссылка.

Другой путь решения проблем, описанных выше - это создание конструктора копирования. Но конструктор копирования может оказаться не столь эффективным решением, как ссылка в качестве параметра и ссылка в качестве возвращаемого значения функции. Это происходит потому, что использование ссылки исключает затраты ресурсов, связанных с копированием объектов в каждом из двух указанных случаев.