Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
39
Добавлен:
16.04.2013
Размер:
64.51 Кб
Скачать

Если перегрузка выполнена функцией-не-членом, то

++с равнозначно operitor++(с)

Перегрузка бинарных операторов

Вернемся к нашему примеру clock и покажем, как перегрузить бинарные опе­раторы. В основном, поддерживаются те же принципы. Когда бинарный опе­ратор перегружается с помощью функции-члена, он получает в качестве первого аргумента неявно передаваемую переменную класса, а в качестве второго аргу­мента — единственный аргумент списка параметров. Дружественные функции и обычные функции получают оба аргумента из списка параметров (отсутствует неявный аргумент). Конечно, обычные функции не имеют доступа к закрытым членам.

Создадим операцию для типа clock, которая складывает два значения.

В файле clock.срр

class clock {

. . . . .

friend clock operator +(clock c1, clock c2) ;

};

clock operator+(clock cl, clock c2)

{

return (cl.tot_secs + c2.tot_secs) ;

}

Целое выражение неявно преобразуется к типу clock с помощью преобразующего конструктора clock ::clock (unsigned long). Оба значения типа с lock передаются как аргументы функции, и оба они являются кандидатами для преобразования при присваивании. Поскольку operator+ () является симметричным бинарным оператором, аргументы должны трактоваться одинаково. Таким образом, обыкно­венно симметричные бинарные операторы перегружаются дружественными функ­циями.

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

class clock {

. . . . .

clock operator-(clock c) ;

};

clock clock::operator-(clock c)

{

return (tot_secs - c.tot_secs);

}

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

Определим операцию умножения как бинарную операцию, первый аргумент которой — типа unsigned long, а второй — переменная типа clock. Для этой операции необходима дружественная функция, так как тип первого аргумента функции-члена, которая перегружает оператор, должен совпадать с типом своего класса.

class clock {

. . . . .

friend clock operator*(unsigned long m, clock c) ;

};

clock operator*(unsigned long m, clock c)

{

return (m * c.tot_secs);

}

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

clock operator*(clock с, unsigned long m)

{

return (m * c.tot_secs);

}

А вот альтернатива, здесь вторая функция определена через первую:

clock operator*(clock с, unsigned long m)

{

return (m * c) ;

}

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

Перегрузка операторов присваивания и индексирования

Оператор присваивания для типа класса по умолчанию генерируется компилятором для выполнения почленного присваивания. Это прекрасно, когда подходит поверхностное копирование. Для типов вроде my_string или vect, которые

нуждаются в глубоком копировании, это некорректно. В качестве практического правила можно принять следующее: каждый раз, когда класс нуждается в явно определенном копирующем конструкторе, он нуждается и в определении оператора присваивания.

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

Переделаем класс vect, расширив его функциональность с помощью перегрузки операторов (см. раздел 6.5, <Класс vect», на стр. 166). Обновленный класс будет содержать несколько улучшений, делающих его более безопасным и полезным. Будет до6авлен конструктор, преобразующий обычный целый массив в безопасный массив. Это позволит нам написать код, использующий безопасные массивы, а затем эффективно применять этот код с обычными массивами. Наконец, оператор индексирования перегружается и заменяет функцию-член element.

В файле vect2.h

//Безопасный массив типа vect с перегруженным []

class vect { public:

//конструкторы и деструктор

explicit vect(int n = 10);

//инициализациявектором vect //инициализация массивом

vect(const vect& v);

vect(const int a[], int n) ;

~vect() ( delete [] p; )

//другие функции-члены

//верхняя граница //с проверкой //границ

int ub() const ( return (size - 1); }

int& operator[] (int i) ;

private:

int* p;

int size;

};

vect::vect(int n) : size (n)

{

assert(n > 0) ;

p = new int[size];

assert(p != 0) ;

}

vect::vect(const int a[], int n) : size (n)

{

assert(n > 0) ;

p = new int[size];

assert(p != 0) ;

for (int i = 0; i < size; ++i)

p[i] = a [i] ;

}

vect::vect(const vect& v) : size (v.size)

{

р = new int[size];

assert(p != 0) ;

for (int i = 0; i < size; ++i)

p[i] = v. p [ i ] ;

}

int& vect::operator[](int i)

{

assert(i >= 0 && i < size);

return p[i];

}

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

имя_класса& operator[]{целый_тип);

Такие функции могут использоваться с обеих сторон присваивания.

Было бы также удобно иметь возможность присваивать один массив другому. Пользователь с помощью перегрузки может задать поведение такого присваивания. При этом хорошим стилем является соответствие стандартному использованию присваивания. Следующая функция-член перегружает присваивание для класса vect:

vect& vect::operator=(const vect& v)

{

if (this != &v) {//в случае присваивания самому

//себе — ничего не делаем

assert(v.size == size);

for (int i = 0; i < size; ++i)

p[i] = v.p[i];

}

return *this;

}

Разбор функции vect: :operator=(const vect& v)

• vect& vect::operator=(const vect& v)

Функция operator= () возвращает ссылку на vect и имеет один явный аргумент типа ссылка на vect. Первый аргумент оператора присваивания является неявным аргументом. Можно было бы записать функцию, как возвращающую void, но тогда она не допускала бы повторного присваивания.

• if (this != &v){

Ничего не делать, если пытаются присвоить переменную самой себе.

• assert(v.size = = size);

Гарантия того, что размеры совпадают.

• for (int i = 0; i < size; ++i)

p[i] = v. p [ i ] ;

return (*this) ;

Явный аргумент v.p[] будет правой стороной присваивания; неявный аргумент р [ ] — левой стороной. Указатель на себя разыменовывается и передается в качестве значения выражения. Это позволяет использовать повторные присваивания с поряд­ком выполнения справа налево.

Выражения с типом vect могут вычисляться с помощью надлежащей перегрузки различных арифметических операторов. В качестве примера давайте перегрузим би­нарный +, подразумевая поэлементное сложение двух переменных типа vect.

vect vect::operator+(const vect& v)

{

assert(size = = v.size);

vect sum(s) ;

for (int i = 0; i < s; ++i)

sum.p[i] =p[i] + v.p[i];

return sum;

}

Теперь, имея в виду класс vect, посмотрим, что означают следующие выражения:

a = b; //a, b — типа vect

b = с; //а, b, с — типа vect vect

a = vect(data, DSIZE); //преобразует массив data[DSIZE]

a = b + а; //присваивание и сложение

a = b + (с = а) + d; //сложное выражение

Класс vect — вполне оформившийся АТД. Он ведет себя и проявляется в клиентс­ком коде в точности как любой встроенный тип. Заметьте, что перегрузка присваива­ния и перегрузка плюса вовсе не означает, что operator+= также перегружен. В са­мом деле, ответственность за то, что различные операторы имеют согласованную семантику, лежит на разработчике класса. Когда перегрузка связана с наборами опе­раторов, их обычно перегружают соответственно.

В перегруженном операторе присваивания строк my_sting длины строк (если они разной длины) подгоняются (в отличие от перегрузки присваивания vect, в ко­торой векторы должны быть одинакового размера).

В файле string7.cpp

my_string my_string::operator=(my_string& a)

{

if (this ! = &a) {// если а=а, ничего не делаем

if (a.len !=len) {// строки разного размера?

delete []s;

len=a.len;

s=new char [len + 1];

assert (s !=0);

}

strcpy (s,a.s); // копирование поверх старой строки

}

return *this;

}

Соседние файлы в папке Тельминов (мб)