Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Перегрузка унарных операций




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

class monster{... monster & operator ++() {++health; return *this;}}monster Vasia;cout << (++Vasia).get_health();

Если функция определяется вне класса, она должна иметь один параметр типа класса:

class monster{... friend monster & operator ++(monster &M);};monster& operator ++(monster &M) {++M.health; return M;}

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

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

class monster{... monster operator ++(int){monster M(*this); health++; return M;}};monster Vasia;cout << (Vasia++).get_health();

Перегрузка бинарных операций

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

class monster{... bool operator >(const monster &M) { if(health > M.get_health()) return true; return false; }};

Если функция определяется вне класса, она должна иметь два параметра типа класса:

bool operator >(const monster &M1, const monster &M2) { if(M1.get_health() > M2.get_health()) return true; return false; }

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

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

monster& operator = (const monster &M){if (&M == this) return *this; // Проверка на самоприсваиваниеif (name) delete [] name;if (M.name) { name = new char [strlen(M.name) + 1]; strcpy(name, M.name); } else name = 0;health = M.health; ammo = M.ammo; skin = M.skin;return *this;}

Операцию присваивания можно определять только в теле класса. Она не наследуется.

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

Можно определить функции-операции, которые будут осуществлять преобразование класса к другому типу. Формат:

operator имя_нового_типа ();

Тип возвращаемого значения и параметры указывать не требуется. Можно определять виртуальные функции преобразования типа.

Пример:

monster::operator int(){return health;}...monster Vasia; cout << int(Vasia);

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

void *operator new(size_t size);

void operator delete (void *);

где void * - указатель на область памяти, выделяемую под объект, size - размер объекта в байтах, size_t - тип размерности области памяти, int или long.

Переопределение этих операций позволяет написать собственное распределение памяти для объектов класса.

Операции, не допускающие перегрузки. В С++ существует несколько операций, не допускающих перегрузки:

. прямой выбор члена объекта класса;

.* обращение к члену через указатель на него;

?: условная операция;

:: операция указания области видимости;

sizeof операция вычисления размера в байтах;

# препроцессорная операция.

 

 

Дружественные функции

Иногда желательно дать функциям не членам возможность доступа к закрытой части класса. У класса есть механизм предоставления права доступа к своей закрытой части функциям не членам. Просто в описание класса помещается описание функции, перед которым стоит ключевое слово friend. Например, если имеется

class Vec; // Vec - имя класса

class vector {

friend Vec operator+(Vec, Vec);

//...

};

То вы можете написать

Vec operator+(Vec a, Vec b)

{

int s = a.size();

if (s!= b.size()) error("плохой размер вектора для +");

Vec& sum = *new Vec(s);

int* sp = sum.v;

int* ap = a.v;

int* bp = b.v;

while (s--) *sp++ = *ap++ + *bp++;

return sum;

}

Одним из особенно полезных аспектов механизма friend является то, что функция может быть другом двух и более классов. Чтобы увидеть это, рассмотрим определение vector и matrix, а затем определение функции умножения.

Теперь, наконец, можно обсудить, в каких случаях для доступа к закрытой части определяемого пользователем типа использовать члены, а в каких - друзей. Некоторые операции должны быть членами: конструкторы, деструкторы и виртуальные функции, но обычно это зависит от выбора. Рассмотрим простой класс X:

class X {

//...

X(int);

int m();

friend int f(X&);

};

Внешне не видно никаких причин делать f(X&) другом дополнительно к члену X::m() (или наоборот), чтобы реализовать действия над классом X. Однако член X::m() можно вызывать только для "настоящего объекта", в то время как друг f() может вызываться для объекта, созданного с помощью неявного преобразования типа. Например:

void g()

{

1.m(); // ошибка

f(1); // f(x(1));

}

Поэтому операция, изменяющее состояние объекта, должно быть членом, а не другом. Для определяемых пользователем типов операции, требующие в случае фундаментальных типов операнд lvalue (=, *=, ++ и т.д.), наиболее естественно определяются как члены. И наоборот, если нужно иметь неявное преобразование для всех операндов операции, то реализующая ее функция должна быть другом, а не членом. Это часто имеет место для функций, которые реализуют операции, не требующие при применении к фундаментальным типам lvalue в качестве операндов (+, -, || и т.д.).Если никакие преобразования типа не определены, то оказывается, что нет никаких существенных оснований в пользу члена, если есть друг, который получает ссылочный параметр, и наоборот. В некоторых случаях программист может предпочитать один синтаксис вызова другому. Например, оказывается, что большинство предпочитает для обращения матрицы m запись m.inv(). Конечно, если inv() действительно обращает матрицу m, а не просто возвращает новую матрицу, обратную m, ей следует быть другом.При прочих равных условиях выбирайте, чтобы функция была членом: никто не знает, вдруг когда-нибудь кто-то определит операцию преобразования. Невозможно предсказать, потребуют ли будущие изменения изменить статус объекта. Синтаксис вызова функции члена ясно указывает пользователю, что объект можно изменить; ссылочный параметр является далеко не столь очевидным. Кроме того, выражения в члене могут быть заметно короче выражений в друге. В функции друге надо использовать явный параметр, тогда как в члене можно использовать неявный this. Если только не применяется перегрузка, имена членов обычно короче имен друзей.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...