跳转至

重載運算符

重載運算符是通過對運算符的重新定義,使得其支持特定數據類型的運算操作。重載運算符是重載函數的特殊情況。

C++ 自帶的運算符,最初只定義了一些基本類型的運算規則。當我們要在用户自定義的數據類型上使用這些運算符時,就需要定義運算符在這些特定類型上的運算方式。

限制

重載運算符存在如下限制:

  • 只能對現有的運算符進行重載,不能自行定義新的運算符。
  • 以下運算符不能被重載:::(作用域解析),.(成員訪問),.*(通過成員指針的成員訪問),?:(三目運算符)。
  • 重載後的運算符,其運算優先級,運算操作數,結合方向不得改變。
  • &&(邏輯與)和 ||(邏輯或)的重載失去短路求值。

實現

重載運算符分為兩種情況,重載為成員函數或非成員函數。

當重載為成員函數時,因為隱含一個指向當前成員的 this 指針作為參數,此時函數的參數個數與運算操作數相比少一個。

而當重載為非成員函數時,函數的參數個數與運算操作數相同。

下面將給出幾個重載運算符的示例。

函數調用運算符

函數調用運算符 () 只能重載為成員函數。通過對一個類重載 () 運算符,可以使該類的對象能像函數一樣調用。

重載 () 運算符的一個常見應用是,將重載了 () 運算符的結構體作為自定義比較函數傳入優先隊列等 STL 容器中。

下面就是一個例子:給出 \(n\) 個學生的姓名和分數,按分數降序排序,分數相同者按姓名字典序升序排序,輸出排名最靠前的人的姓名和分數。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <queue>
using namespace std;

struct student {
  string name;
  int score;
};

struct cmp {
  bool operator()(const student& a, const student& b) const {
    return a.score < b.score || (a.score == b.score && a.name > b.name);
  }
};

priority_queue<student, vector<student>, cmp> pq;

int main() {
  int n;
  cin >> n;
  for (int i = 1; i <= n; i++) {
    string name;
    int score;
    cin >> name >> score;
    pq.push({name, score});
  }
  student rk1 = pq.top();
  cout << rk1.name << ' ' << rk1.score << endl;
  return 0;
}

自增自減運算符

自增自減運算符分為兩類,前置和後置。為了能將兩類運算符區別開來,對於後置自增自減運算符,重載的時候需要添加一個類型為 int 的空置形參。

另外一點是,內置的自增自減運算符中,前置的運算符返回的是引用,而後置的運算符返回的是值。雖然重載後的運算符不必遵循這一限制,不過在語義上,仍然期望重載的運算符與內置的運算符在返回值的類型上保持一致。

因此,對於類型 T,典型的重載自增運算符的定義如下:

重載定義(以 ++ 為例) 成員函數 非成員函數
前置 T& T::operator++(); T& operator++(T& a);
後置 T T::operator++(int); T operator++(T& a, int);

比較運算符

std::sort 和一些 STL 容器中,需要用到 < 運算符。在使用自定義類型時,我們需要手動重載。

還是以講函數調用運算符時舉的例子説起,如果我們重載比較運算符,實現代碼是這樣的(主函數因為沒有改動就略去了):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct student {
  string name;
  int score;

  bool operator<(const student& a) const {
    return score < a.score || (score == a.score && name > a.name);
    // 上面省略了 this 指針,完整表達式如下:
    // this->score<a.score||(this->score==a.score&&this->name>a.name);
  }
};

priority_queue<student> pq;

上面的代碼將小於號重載為了成員函數,當然重載為非成員函數也是可以的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct student {
  string name;
  int score;
};

bool operator<(const student& a, const student& b) {
  return a.score < b.score || (a.score == b.score && a.name > b.name);
}

priority_queue<student> pq;

事實上,只要有了 < 運算符,則其他五個比較運算符的重載也可以很容易實現。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* clang-format off */

// 下面的幾種實現均將小於號重載為非成員函數

bool operator<(const T& lhs, const T& rhs) { /* 這裏重載小於運算符 */ }
bool operator>(const T& lhs, const T& rhs) { return rhs < lhs; }
bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }
bool operator==(const T& lhs, const T& rhs) { return !(lhs < rhs) && !(lhs > rhs); }
bool operator!=(const T& lhs, const T& rhs) { return !(lhs == rhs); }

參考資料與註釋: