運算符重載
- 1. 運算符重載基礎
- 1.1 運算符重載語法
- 1.2 運算符重載細節補充
- 1.3 更多的運算符重載
- 2. 重載單目運算符
- 3. 如何直接輸入輸出對象類型——重載運算符 << 和 >>
- 3.1 單個對象實現 cou <<
- 3.2 多個對象實現 cout<<
- 3.3 右移運算符 輸入 cin >>
- 3.4 重載括號運算符(仿函數/函數對象)
- 4. 重載運算符注意事項
1. 運算符重載基礎
C++將運算符重載擴展到自定義的數據類型,它可以讓對象操作更美觀。
例如字符串string用加號(+)拼接、cout用兩個左尖括號(<<)輸出。
有時候我們需要讓對象之間進行運算, C++ 提供的“運算符重載”機制,賦予運算符新的功能,就能解決用+將兩個復數對象相加這樣的問題。
- 舉個例子,比如說如下代碼:
類Point
class Point {friend Point add(Point, Point);int m_x;int m_y;
public:Point(int x, int y) :m_x(x), m_y(y) {}void display() {cout << "(" << m_x << ", " << m_y << ")" << endl;}};
函數add()
Point add(Point p1, Point p2) {return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
然后在主函數里面我們定義兩個對象,讓兩個點相加
int main() {Point p1(10, 20);Point p2(20, 30);Point p3 = add(p1, p2);p3.display();getchar();return 0;
}
輸出:
這樣做就顯得很繁瑣,如果可以直接這么寫就好了:
Point p3 = p1 + p2;
也就是直接讓這兩個對象進行相加操作,
但是默認情況下這么寫報錯
所以我們可以利用 運算符重載(操作符重載):可以為運算符增加一些新的功能
1.1 運算符重載語法
返回值 operator運算符(參數列表);
接著剛才的問題,我們利用運算符重載為運算符增加一些新的功能,使得“+”可以允許兩個Point類型進行相加操作。
Point operator+(Point p1, Point p2) {return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
不要忘記在Point類里面更新友元:
friend Point operator+(Point, Point);
完整代碼:
#include <iostream>
using namespace std;class Point {friend Point operator+(Point, Point);int m_x;int m_y;
public:Point(int x, int y) :m_x(x), m_y(y) {}void display() {cout << "(" << m_x << ", " << m_y << ")" << endl;}};//Point add(Point p1, Point p2) {
// return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
//}
Point operator+(Point p1, Point p2) {return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}int main() {Point p1(10, 20);Point p2(20, 30);Point p3 = p1 + p2;p3.display();getchar();return 0;
}
輸出:
- 本質上是調用了 operator+() 函數,并且返回了 Point 類型值
// 這兩行代碼等價
Point p3 = operator+(p1, p2);Point p3 = p1 + p2;
- 利用運算符重載為運算符后,三個數相加也可以
例:
int main() {Point p1(10, 20);Point p2(20, 30);Point p3(30, 40);Point p4 = p1 + p2 + p3;p4.display();getchar();return 0;
}
輸出:
此時三個對象相加也可以了,所以還是比直接調用 add()函數 要方便的多
原理是這樣的:
// 調用了兩次operaator+
Point p4 = operator+(operator+(p1, p2), p3) ;
1.2 運算符重載細節補充
之前在 C++ 對象型參數和返回值 的帖子筆記中提到,最好不要在函數參數使用對象型類型,不然會產生中間變量。
Point operator+(Point p1, Point p2) {return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
修改成 引用(減少中間對象的產生) :
Point operator+(const Point &p1, const Point &p2) {return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
前面用 const 修飾的原因是可以使加入進來的參數接收對象更廣,既可以接受const對象,也可以接受非const對象。
記得更細友元:
friend Point operator+(const Point &, const Point &);
- 有沒有發現這種寫法和拷貝構造函數的格式很類似呢?
拷貝構造函數格式: 類名(const 類名& 對象名){…}
// 拷貝構造函數Point(const Point &point) {m_x = point.m_x;m_y = point.m_y;}
我的理解是也是由于 const 引用既可以接受const參數也可以接受非const參數的原因,它的接受范圍更大
- 另外,運算符重載也可以直接寫在類里面,這樣就可以直接訪問類里面的成員,我們就省下了去寫友元這一步
- p.s: const 用來修飾成員函數,如果是在類外,全局函數就不要用const修飾了
#include <iostream>
using namespace std;class Point {friend Point operator+(Point, Point);int m_x;int m_y;
public:Point(int x, int y) :m_x(x), m_y(y) {}void display() {cout << "(" << m_x << ", " << m_y << ")" << endl;}// 運算重載符const Point operator+(const Point &point){return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指針可以省略}};
變成成員函數以后,則需要用對象去調用
p1.operator+(p2);
//等價于
p1 + p2;
那么成員函數中只接收一個參數就可以了
// 運算重載符Point operator+(const Point &point){return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指針可以省略}
還可以再完善以下,左邊的這個const是防止返回值被賦值
右邊的const能保證我們的返回值可以再次調用operator+()函數
// 運算重載符const Point operator+(const Point &point)const{return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指針可以省略}
- 結論:全局函數和成員函數都支持運算符重載
1.3 更多的運算符重載
除了 “ + ”,還有其他的運算符重載,如 “ - ”(減法運算)
const Point operator-(const Point &point)const{return Point(m_x - point.m_x, m_y - point.m_y);}
" += "
Point &operator+=(const Point &point) {m_x += point.m_x;m_y += point.m_y;return *this; // 取出this指針所指向的東西}
" == "
bool operator==(const Point &point) const {// 1\0if ((m_x == point.m_x) && (m_y == point.m_y)) {return 1;} else {return 0;}// 或者以下寫法// return (m_x == point.m_x) && (m_y == point.m_y);
“ != ”
bool operator!=(const Point &point) const {return (m_x != point.m_x) || (m_y != point.m_y);}
" - " (負號)
const Point operator-() const {return Point(-m_x, -m_y);}
2. 重載單目運算符
可重載的一元運算符:
1)++ 自增 2)-- 自減 3)! 邏輯非 4)& 取地址
5)~ 二進制反碼 6)* 解引用 7)+ 一元加 8) - 一元求反
一元運算符通常出現在它們所操作的對象的左邊。
但是,自增運算符++和自減運算符–有前置和后置之分。
C++ 規定,重載++或–時,如果重載函數有一個int形參,編譯器處理后置表達式時將調用這個重載函數。
- 區別:1.只需要在參數后面加個 int 就是后置++
// 前置++Point &operator++() {m_x++;m_y++;return *this;}
// 后置++const Point operator++(int) { // 返回const是由于 后置++ 是不能被賦值的(運算符后置++本身的特性)Point old(m_x, m_y);m_x++;m_y++;return old; // 返回的是臨時的變量,因為前置++是先賦值再運算,也就是最后在進行++操作}
- 2.前置++可以被賦值,后置++不可以
int main() {int a = 10;(++a) = 20; // ++放在前面可以賦值(a++) = 20; // 會報錯,因為相當于先賦值,a再自己+1
}
- 所以在寫后置++的重載的時候,我們一個是要考慮它本身的不能被賦值的特性(前面用const修飾),一個是返回值應當返回+1之前的值
// 后置++const Point operator++(int) { // 返回const是由于 后置++ 是不能被賦值的(運算符后置++本身的特性)Point old(m_x, m_y);m_x++;m_y++;return old; // 返回的是臨時的變量,因為前置++是先賦值再運算,也就是最后在進行++操作}
3. 如何直接輸入輸出對象類型——重載運算符 << 和 >>
3.1 單個對象實現 cou <<
- 我們能否能像輸出其他類型一樣,直接將對象進行 cout 呢?
可以通過對左移運算符進行重載
int main() {Point p1(10, 20);cout << p1 << endl; getchar();return 0;
}
這種情況重載就不能寫在類里了,因為左移運算符左邊是cout,而想要調用類里的成員函數首先要是對象才行,而cout顯然不是對象,所以要寫在類的外面
// output stream -> ostream 輸出流
void operator<<(ostream& cout, const Point& point) {cout << "(" << point.m_x << ", " << point.m_y << ")";
}
(ostream 是 cout 所在的類)
注意不要忘記在類里面放友元,不然無法訪問m_x,m_y成員變量
friend void operator<<(ostream &, const Point &);
3.2 多個對象實現 cout<<
- 如果想實現多個數據的cout:
cout << p1 << p2 << endl;
其實類似如下過程:
cout << p1 << p2 << endl;
// 等價于
operator << (cout, p1) << p2 ;
其實就是得到一次返回值以后,再用這個返回值調用一次左移運算符,所以我們可以更新一下:
// output stream -> ostream 輸出流
ostream& operator<<(ostream& cout, const Point& point) {cout << "(" << point.m_x << ", " << point.m_y << ")";return cout; // 返回cout本身,然后就可以繼續打印
}
更新友元:
friend ostream& operator<<(ostream&, const Point&);
可以用以下代碼實驗
int main() {Point p1(10, 20);Point p2(20, 30);cout << p1 << p2 << endl; getchar();return 0;
}
輸出:可以正常打印對象了
3.3 右移運算符 輸入 cin >>
- 如果我們想從鍵盤輸入一些東西給到對象,該怎么做?
Point p1(10,20);
cin >> p1;
先在類里面更新友元:
friend istream& operator>>(istream&, Point&);
然后和cout類似,istream是cin所在的類,返回一個cin
// input stream -> istream
istream &operator>>(istream &cin, Point &point) {cin >> point.m_x;cin >> point.m_y;return cin;
}
最后可以通過鍵盤輸入查看對象中的值是否被修改了,鍵盤輸入:40 50 60 70
int main() {Point p1(10, 20);Point p2(20, 30);cin >> p1 >> p2;cout << p1 << p2 << endl; getchar();return 0;
}
輸出:
對象里的成員變量的值已經更改為鍵盤輸入的值了
3.4 重載括號運算符(仿函數/函數對象)
括號運算符()也可以重載,對象名可以當成函數來使用(函數對象、仿函數)。
括號運算符重載函數的語法:返回值類型 operator()(參數列表);
注意:
- 括號運算符必須以成員函數的形式進行重載。
- 括號運算符重載函數具備普通函數全部的特征。
- 如果函數對象與全局函數同名,按作用域規則選擇調用的函數。
函數對象的用途:
- 表面像函數,部分場景中可以代替函數,在STL中得到廣泛的應用;
- 函數對象本質是類,可以用成員變量存放更多的信息;
- 函數對象有自己的數據類型;
- 可以提供繼承體系。
4. 重載運算符注意事項
- 返回自定義數據類型的引用可以讓多個運算符表達式串聯起來。(不要返回局部變量的引用)
- 重載函數參數列表中的順序決定了操作數的位置。
- 重載函數的參數列表中至少有一個是用戶自定義的類型,防止程序員為內置數據類型重載運算符。
- 如果運算符重載既可以是成員函數也可以是全局函數,應該優先考慮成員函數,這樣更符合運算符重載的初衷。
- 重載函數不能違背運算符原來的含義和優先級。
- 不能創建新的運算符。
- 以下運算符不可重載:
sizeof sizeof運算符
. 成員運算符
.*??? 成員指針運算符
::??? 作用域解析運算符
?:??? 條件運算符
typeid??? 一個RTTI運算符
const_cast??? 強制類型轉換運算符
dynamic_cast??? 強制類型轉換運算符
reinterpret_cast???強制類型轉換運算符
static_cast??? 強制類型轉換運算符
暫時先寫這么多,這部分的知識點還有很多,后面遇到再回來補充
- 以下運算符只能通過成員函數進行重載:
= 賦值運算符
() 函數調用運算符
[] 下標運算符
-> 通過指針訪問類成員的運算符