KnigaRead.com/

Д. Стефенс - C++. Сборник рецептов

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Д. Стефенс, "C++. Сборник рецептов" бесплатно, без регистрации.
Перейти на страницу:

Рецепт 8.11.

8.13. Перегрузка операторов инкремента и декремента

Проблема

Имеется класс, для которого имеют смысл операции инкремента и декремента, и требуется перегрузить operator++ и operator--, которые позволят легко и интуитивно выполнять инкремент и декремент объектов этого класса.

Решение

Чтобы это сделать, перегрузите префиксную и постфиксную формы ++ и --. Пример 8.14 показывает обычную методику перегрузки операторов инкремента и декремента.

Пример 8.14. Перегрузка инкремента и декремента

#include <iostream>


using namespace std;


class Score {

public:

 Score() : score_(0) {}

 Score(int i) : score_(i) {}

 Score& operator++() {

  // префикс

  ++score_;

  return(*this);

 }

 const Score operator++(int) {

  // постфикс

  Score tmp(*this);

  ++(*this); // Использование префиксного оператора

  return(tmp);

 }

 Score& operator--() {

  --score_;

  return(*this);

 }

 const Score operator--(int x) {

  Score tmp(*this);

  --(*this);

  return(tmp);

 }

 int getScore() const {return(score_);}

private:

 int score_;

};


int main() {

 Score player1(50);

 player1++;

 ++player1; // score = 52

 cout << "Счет = " << player1.getScore() << 'n';

 (--player1)--; // score_ = 50

 cout << "Счет = " << player1.getScore() << 'n';

}

Обсуждение

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

Представьте себе инкремент целого числа. С помощью оператора ++ имеется два способа выполнить его для некоторого целого i.

i++; // постфиксный

++i; // префиксный

Оба инкрементируют i: первая версия создает временную копию i, инкрементирует i и затем возвращает временное значение, а вторая инкрементирует i и затем возвращает его. C++ позволяет выполнять перегрузку операторов, что означает, что вы можете заставить свой собственный тип (класс или enum) вести себя так же, как и int.

Чтобы добиться нужного эффекта, перегрузите operator++ и operator--. Пример 8.14 иллюстрирует, как перегружать префиксную и постфиксную версии.

Score& operator++() { // префиксный

 ++score_;

 return(*this);

}


const Score operator++(int) { // постфиксный

 Score tmp(*this);

 ++(*this);

 return(tmp);

}

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

После этого класс Score можно использовать как int.

Score player1(50);

player1++;

++player1; // score_ = 52

Вы, вероятно, заметили, что сигнатуры префиксной версии operator++ возвращают ссылку на текущий класс. Именно так и следует делать (а не возвращать, к примеру, void), чтобы инкрементируемый или декрементируемый объект мог использоваться в других выражениях. Рассмотрим такую строку из примера.

(--player1)--;

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

foo(--player1);

Функция foo ожидает аргумент типа Score, и для корректной компиляции именно это должно возвращаться из префиксного operator--.

Перегрузка операторов — это мощная возможность, которая позволяет для типов, определяемых пользователем, использовать те же операторы, что и для встроенных типов. Сторонники других языков, которые не поддерживают перегрузку операторов, утверждают, что эта возможность сбивает с толку и очень сложна, и, следует признать, может быть перегружено очень много операторов, соответствующих любому поведению. Но когда дело касается простого инкремента и декремента, хорошо иметь возможность изменить поведение класса так, как этого хочется.

Смотри также

Рецепт 8.14.

8.14. Перегрузка арифметических операторов и операторов присвоения для работы с классами

Проблема

Имеется класс, для которого имеют смысл некоторые из унарных или бинарных операторов С++, и требуется, чтобы пользователи класса могли использовать их при работе с объектами этого класса. Например, если есть класс с именем Balance, который содержит значение с плавающей точкой (например, баланс счета), будет удобно, если для объектов Balance можно было бы использовать некоторые стандартные операторы С++, как здесь.

Balance checking(50.0);

savings(100.0);

checking += 12.0;

Balance total = checking + savings;

Решение

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

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

#include <iostream>


using namespace std;


class Balance {

 // These have to see private data

 friend const Balance operator+(const Balance& lhs, const Balance& rhs);

 friend const Balance operator+(double lhs, const Balance& rhs);

 friend const Balance operator+(const Balance& lhs, double rhs);

public:

 Balance() : val_(0.0) {}

 Balance(double val) : val_(val) {}

 ~Balance() {}


 // Унарные операторы

 Balance& operator+=(const Balance& other) {

  val_ += other.val_;

  return(*this);

 }

 Balance& operator+=(double other) {

  val_ += other;

  return(*this);

 }

 double getVal() const {return(val_);}

private:

 double val_;

};


// Бинарные операторы

const Balance operator+(const Balance& lhs, const Balance& rhs) {

 Balance tmp(lhs.val_ + rhs.val_);

 return(tmp);

}


const Balance operator+(double lhs, const Balance& rhs) {

 Balance tmp(lhs + rhs.val_);

 return(tmp);

}


const Balance operator+(const Balance& lhs, double rhs) {

 Balance tmp(lhs.val_ + rhs);

 return(tmp);

}


int main() {

 Balance checking(500.00);

 savings(23.91);

 checking += 50;

 Balance total = checking + savings;

 cout << "Платежный баланс: " << checking.getVal() << 'n';

 cout << "Общий баланс: " << total.getVal() << 'n';

}

Обсуждение

Наиболее часто используют перегрузку для арифметических операторов и операторов присвоения. Существует огромное количество различных классов, для которых имеют смысл арифметические операторы (сложение, умножение, остаток от деления, сдвиг битов вправо/влево) и операторы присвоения — вне зависимости от того, используются ли они для вычислений или для чего-то другого. Пример 8.15 показывает основные методики перегрузки этих операторов.

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

Balance x(0), у(32);

x = y;

Вторая строка — это краткая запись вызова Balance::operator=(y). Оператор присвоения отличается от большинства других операторов тем, что если вы не создаете собственной его версии, то компилятором создается версия по умолчанию. Версия по умолчанию просто копирует в текущий объект каждый член целевого объекта, что, конечно, не всегда приемлемо, так что его можно перегрузить и обеспечить другое поведение или перегрузить и предоставить возможность присвоения объектов типов, отличных от текущего

Для класса Balance из примера 8.15 оператор присвоения можно определить вот так.

Balance& operator=(const Balance& other) {

 val_ = other.val_;

 return(*this);

}

Первое, на что вы должны обратить внимание, если не знакомы с перегрузкой операторов, — это синтаксис operator=. Именно так объявляются все операторы. Все операторы можно рассматривать как функции с именами operator[symbol], где symbol — это перегружаемый оператор. Единственным различием между операторами и обычными функциями является синтаксис их вызова. На самом деле, если вы хотите ввести побольше кода и написать отвратительно выглядящий код, то операторы можно вызывать и с помощью такого синтаксиса.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*