目錄
- 函數重載
- 運算符重載
- C++運算符重載范圍對照表
- 注意事項
- 運算符重載語法
- 全局運算符重載
- 類內運算符重載
- 下面以一個一元運算符為例,介紹特性1:
- 下面介紹特性3:(必須類內重載的運算符?)
函數重載
?函數重載是指同一個作用域內,可以聲明幾個功能類似的同名函數,但是這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同。
函數重載的要點:
- 同一作用域的同名函數。
也就是說,如果兩個同名的函數不在同一個作用域那就不算重載,具體調用在哪個域就使用哪個函數。 - 形式參數,也就是傳入的參數。個數、類型或者順序必須不同,編譯器會自動根據傳入的參數判斷使用的是哪個函數。
?下面舉例體會:
#include<iostream>
using namespace std;namespace myspace{void myfunction(int){cout<<"Hello from myspace"<<endl;}void myfunction(double){cout<<"Hello from myspace double"<<endl;}
}void myfunction(float)
{cout<<"Hello from global namespace"<<endl;
} int main()
{myspace::myfunction(10); //輸出Hello from myspace,匹配到myspace::myfunction(int)myspace::myfunction(3.14); ///輸出Hello from myspace double,匹配到myspace::myfunction(double)myfunction(1.23); //輸出Hello from global namespace,匹配到myfunction(float)return 0;
}
運行后輸出的結果如下:
兩個在同一個命名空間的函數myfunction,根據其傳入值的類型來決定實際調用哪個。并且當不同命名空間的myfunction調用時,總是選擇最近的一個(全局)。符合預期的結果。
函數重載的好處顯而易見,
可以極大程度地避免冗余的函數命名
,例如可以add_int和add_double之類的函數合并為一個同名的add,優化代碼復用與維護?。
運算符重載
?運算符重載最經典的一個例子就是iostream中的cin與cout重載的<<與>>
。原本>> or <<
只是用來作移位操作的,這里被重載為輸入與輸出的功能。
?運算符重載(Operator Overloading)是面向對象編程中的核心特性,允許對已有運算符賦予新的功能以適應自定義數據類型。其本質是通過定義特定函數,改變運算符在處理類或結構體時的行為邏輯,使得對象操作更貼近內置類型的使用方式。簡單來說就是對已有的運算符進行函數重載,賦予新的功能可以重載的運算符有下面:
C++運算符重載范圍對照表
運算符類型 | 可重載運算符 | 不可重載運算符 |
---|---|---|
算術運算符 | + - * / % ++ -- | |
關系運算符 | == != < > <= >= | |
邏輯運算符 | && || ! | |
位運算符 | & | ^ ~ << >> | |
賦值運算符 | = += -= *= /= %= &= |= ^= <<= >>= | |
其他運算符 | [] () -> , new delete new[] delete[] | :: .* . ?: sizeof typeid |
特殊說明 | 流運算符<< >> 必須全局重載 | 成員訪問符. 等禁止重載 |
注意事項
- 不能改變運算符優先級和結合性
- 至少一個操作數為自定義類型
- 保持運算符原有語義
運算符重載語法
?運算符重載有著嚴格的語法規定:
返回類型 operator運算符符號(參數列表) { // 實現運算符邏輯 }
?并且重載之后遵循原運算符的計算順序,例如My_class operator+(My_class c1,My_class c2)
調用時應該為My_class num3 = num1 + num2
。
全局運算符重載
?百分之九十以上的運算符重載都是在類內進行的,即使是在類外的全局運算符重載函數,其輸入參數也必須包含一個類對象。
例如 int operator+( int a, int b)
這樣是不允許的。因為C++標準明確規定:運算符重載至少有一個參數必須是類類型、枚舉類型或對它們的引用,不能重載基本類型(如int)的運算符?。
改成下面這樣可以編譯通過:(string是一個類)
#include<iostream>
#include<string>
using namespace std;void operator+(string a, int b)
{cout << "hello" << endl;
}int main()
{string s = "h";int i = 10;s + i; //操作符重載return 0;
}
輸出為:
?當然了,全局運算符重載的作用可不是像上面這樣用來搞怪的。前面說到了,運算符重載的輸入參數必須有一個是類類型,枚舉類型或對它們的引用,對于類內運算符重載來說,類內運算符重載默認第一個參數(左參數)是類本身指針(this)
因此其可以忽略傳入第一個參數的聲明。
?對于全局運算符重載來說全局運算符重載默認第一個參數(左參數)可以是任意類類型,枚舉類型或對它們的引用
,并且需顯式聲明所有操作數參數(也就是說全局重載時,operate不能省略第一個傳入的參數。)流運算符的重載必須是全局運算符重載
。
?下面給正經示例:
// 全局重載+,混合內置類型和類類型
Vector operator*(const Vector& v, double scalar) {return Vector(v.x * scalar, v.y * scalar);
}
// 調用方式
Vector v1 = v * 2.5; // 合法
友元函數與流運算
#include <iostream>
using namespace std;class Vector {
private:double x, y;
public:Vector(double x=0, double y=0) : x(x), y(y) {}// 聲明友元全局運算符friend Vector operator+(const Vector& v1, const Vector& v2);friend ostream& operator<<(ostream& os, const Vector& v);
};// 實現友元運算符+
Vector operator+(const Vector& v1, const Vector& v2) {return Vector(v1.x + v2.x, v1.y + v2.y);
}// 實現友元流輸出運算符
ostream& operator<<(ostream& os, const Vector& v) {return os << "(" << v.x << ", " << v.y << ")";
}int main() {Vector a(1, 2), b(3, 4);cout << "a + b = " << a + b << endl;return 0;
}
上面的全局運算符重載定義了兩個友元函數:
- + 號的重載。
- 流運算符 <<的重載。
定義成友元函數是因為兩個全局運算符重載函數不屬于類的成員函數,但它們都要訪問類的私有成員。因此需要在類內聲明它們為類的友元函數。
類內運算符重載
?類內運算符重載有這么幾個核心特性:
- 隱式調用this指針。類內重載的運算符函數默認以this作為左操作數(左操作數也必須是當前對象),因此二元運算符只需顯式聲明一個參數(右操作數)?。例如operator+(this,b) == operator+(b),this被省略。
理論上一元運算符,如++,--之類的類內重載都不需要顯式聲明形參,但有時候為了區分運算的順序,例如a++和++a,需要傳入偽參數。
- operator作為成員函數,可直接訪問類的私有成員,無需友元聲明?。
- 必須為類內重載的運算符:=(賦值)、[](下標)、()(函數調用)、->(成員訪問)?。
下面以一個一元運算符為例,介紹特性1:
#include<iostream>
using namespace std;class Counter {
public:int count;Counter(int n=0) : count(n) {}// 前綴++(返回引用,避免拷貝)Counter& operator++() {++count;return *this;}// 后綴++(int參數區分)Counter operator++(int) {Counter temp = *this;++count;return temp;}
};int main()
{Counter c;cout << c.count << endl; // 0cout << ++c.count << endl; // 1cout << c.count++ << endl; // 1cout << c.count << endl; // 2return 0;
}
下面介紹特性3:(必須類內重載的運算符?)
- 賦值運算符=?
用于對象間的深拷貝,需處理自賦值問題。
class MyClass {
public:MyClass& operator=(const MyClass& rhs);
};
正常來說,如果沒有自定義賦值運算符編譯器會默認進行淺拷貝,如下:
class BadClass {int* data;
public:BadClass() : data(new int[100]) {}~BadClass() { delete[] data; }
};
BadClass b1, b2;
b2 = b1; // 危險:兩個對象指向同一內存
什么是淺拷貝?
在C++中,當類包含動態分配的內存時,直接使用默認的賦值運算符會導致淺拷貝問題,前拷貝對于基本類型(如int、double)直接復制值,類類型成員調用其自身的賦值運算符,指針類型就僅復制地址(不復制指向的內容)
上面的代碼進行淺拷貝后,對于b2.data的更改會同步到b1.data(淺拷貝使得二者地址相同)。更好的辦法是下面這樣:
class BadClass {int* data;
public:// ... 原有構造函數和析構函數 ...// 自定義賦值運算符BadClass& operator=(const BadClass& other) {if (this != &other) { // 防止自賦值delete[] data; // 釋放原有內存data = new int[100]; //指向新地址std::copy(other.data, other.data + 100, data); // 值深拷貝}return *this;}
};
- 函數調用運算符()仿函數
仿函數就是讓類的對象能像函數一樣被調用。語法如下:
典型例子:
class Adder {int value;
public:Adder(int v) : value(v) {}int operator()(int x) { return x + value; }
};Adder add5(5);
cout << add5(10); // 輸出15
對于int operator()(int x) { return x + value; }
,類內運算符重載函數可以訪問類內的成員變量,因此value無需傳入。
3. 下標運算符[]?
提供類似數組的訪問方式,通常返回引用以支持修改。
class Vector {
public:int& operator[](int index);
};
- 成員訪問運算符->?
常用于智能指針或迭代器實現。
class SmartPtr {
public:T* operator->() { return ptr; }
};