簡介
C++預定義的運算符,只能用於基本數據結構類型的運算:整型、實型、字符型、邏輯型。
定義
運算符重載,就是對已有的運算符(C++中預定義的運算符)賦予多重含義,使同一運算符作用於不同類型的數據時導致不同的行爲
目的
擴展C++中提供的運算符的適用範圍,使之能作用於對象
形式
運算符重載的實質是函數重載
可以重載爲普通函數,也可以重載爲成員函數
把含運算符的表達式轉換成對運算符函數的調用
吧運算符的操作數轉換爲函數的參數
運算符被多次重載時,根據實參的類型決定調用哪個運算符函數
返回值類型 operator 運算符(形參表) {
}
重載爲普通函數時,參數個數爲運算符目數減一
重載爲成員函數時,參數個數爲運算符目數
記住是目數不是數目啦
class Complex {
public:
double real, imag;
Complex(double r = 0.0, double i = 0.0):real(r), imag(i) {}
Complex operator-(const Complex &c);
};
Complex operator+(const Complex &a, const Complex &b) {
return Complex(a.real+b.real, b.imag);
}
Complex Complex::operator-(const Complex &c) {
return Complex(real-c.real, imag-c.imag);
}
int main() {
Complex a(4, 4), b(1, 1), c;
c = a+b; // 等價於c=operator+(a,b)
cout << c.real << "," << c.imag << endl;
cout << (a-b).real << "," << (a-b).imag << endl;
// a-b等價於a.operator-(b)
return 0;
}
賦值運算符重載(‘=’)
重載爲成員函數
賦值運算符只能重載爲成員函數
class String {
private:
char *str;
public:
String():str(new char[1]) { str[0] = 0; }
const char *c_str() { return str; }
String& operator=(const char *s) {
delete [] str;
str = new char[strlen(s)+1];
strcpy(str, s);
return *this;
}
~String() { delete[] str; }
};
int main() {
String s1, s2;
s1 = "s1";
s2 = "s2";
s1 = s2;
cout << s1.c_str() << "," << s2.c_str() << endl;
return 0;
}
如果不定義自己的賦值運算符,那麼s1=s2實際上導致s1.str和s2.str指向同一片內存空間
如果s1對象消亡,析構函數將釋放s1.str指向的空間,則s2消亡時還要釋放一次
如果執行s1=“s_”,會導致s2.str指向的地方被delete
因此要在類String裏添加成員函數
String& operator=(const String &s) {
delete []str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
return *this;
}
考慮 s=s; 這條語句
String &operator=(const String &s) {
if (this == &s) return *this;
delete []str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
return *this;
}
對運算符進行重載的時候,好的風格是應該儘量保留運算符原本的性質
返回值類型
考慮:
a = b = c; ——> a.operator=(b.operator=( c ));
(a = b) = c; ——> (a.operator=(b)).operator=( c )
but!!! 考慮String類的複製構造函數,同樣會面臨和 = 同樣的問題,用同樣的方法處理
String (String &s) {
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
}
運算符重載爲友元函數
一般情況下,將運算符重載爲類的成員函數,是較好的選擇
有時,重載爲成員函數不能滿足使用要求;重載爲普通函數,不能訪問類的私有成員——>就需要將運算符重載爲友元
class Complex {
double real, imag;
public:
Complex(double r, double i):real(r), imag(i) {}
Complex operator+(double r){
return Complex(real+r, imag);
}
};
經上述重載後
Complex c;
c = c+5; // 有定義,相當於c=c.operator+(5)
c = 5+c; // 編譯出錯
爲了使上述表達式能成立,需要將+重載爲普通函數
改爲:
Complex operator+(double r, const Complex &c) {
return Complex(c.real+r, c.imag);
}
就能解釋嘚通5+c啦
但是普通函數又不能訪問私有成員,需要將運算符重載爲友元
class Complex {
double real, imag;
public:
Complex(double r, double i):real(r), imag(i) {}
Complex operator+(double r){
return Complex(real+r, imag);
}
friend Complex operator+(double r, const Complex &c) {
return Complex(c.real+r, c.imag);
}
};
實例
可變長整型數組
#include <iostream>
#include <cstring>
using namespace std;
class CArray {
int size;
int *ptr;
public:
CArray(int len = 0); // 參數爲數組元素個數
CArray(CArray &a);
~CArray();
void push_back(int v);
CArray& operator=(const CArray &a); // 對象間賦值
int length() { return size; }
// 用以支持根據下標訪問數組元素個數,如n=a[1];a[2]=5;
int& operator[](int i) { return ptr[i]; }
};
int main() {
CArray a;
// 要用動態分配的內存來存放數組元素,需要一個指針成員變量
for (int i = 0; i < 5; ++ i) a.push_back(i);
CArray a2, a3;
a2 = a; // 要重載'=',"[]"
for (int i = 0; i < a.length(); ++ i) cout << a2[i] << " ";
cout << endl;
a2 = a3; // a2,a3都是空的,a2.length()返回0
for (int i = 0; i < a2.length(); ++ i) cout << a2[i] << " ";
a[3] = 100;
CArray a4(a); // 要自己寫複製構造函數
for (int i = 0; i < a4.length(); ++ i) cout << a4[i] << " ";
return 0;
}
CArray::CArray(int len):size(len) {
ptr = len == 0 ? nullptr : new int[len];
}
CArray::CArray(CArray &a) {
if (a.ptr == nullptr) {
ptr = nullptr;
size = 0;
return ;
}
ptr = new int[a.size];
// 記住要指向不同的內存空間
memcpy(ptr, a.ptr, sizeof(int)*a.size);
size = a.size;
}
CArray::~CArray() {
if (ptr) delete [] ptr;
}
// 是賦值號左邊對象存放的數組大小內容都和右邊對象一樣
CArray& CArray::operator=(const CArray &a) {
// 防止a=a這樣的賦值出錯
if (ptr == a.ptr) return *this;
// 如果a裏面的數組是空的
if (a.ptr == nullptr) {
if (ptr) delete [] ptr;
ptr = nullptr;
size = 0;
return *this;
}
// 如果原有空間足夠大,就不用分配新的空間
if (size < a.size) {
if (ptr) delete []ptr;
ptr = new int[a.size];
}
memcpy(ptr, a.ptr, sizeof(int)*a.size);
size = a.size;
return *this;
}
// 在尾部添加數組
void CArray::push_back(int v) {
if (ptr) {
int *tmp = new int[size+1];
memcpy(tmp, ptr, sizeof(int)*size);
delete []ptr;
ptr = tmp;
}
else ptr = new int[1];
ptr[size++] = v;
}
流插入運算符重載
cout/cin是在iotream中定義的:
cout是ostream的對象
cin是istream的對象
<</>> 這倆運算符能用在 cout/cin 上是因爲在iostream裏對兩個運算符進行了重載
流插入流提取運算符本質上是左移右移運算符,只不過是被重載了
流插入運算符的重載
ostream& ostream::operator<<(int n) {
...
return *this;
}
ostream& ostream::operator<<(const char *s) {
...
return *this;
}
因爲istream和ostream都已經寫好了,所以不能>>和<<重載成成員函數,只能重載成全局函數,要聲明爲友元函數
#include <bits/stdc++.h>
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0):real(r), imag(i) {}
friend ostream& operator<<(ostream& os, const Complex &c);
friend istream& operator>>(istream& is, Complex &c);
};
ostream& operator<<(ostream& os, const Complex &c) {
os << c.real << "+" << c.imag << "i";
return os;
}
istream& operator>>(istream& is, Complex &c) {
string s;
is >> s;
int pos = s.find("+", 0);
string tmp = s.substr(0, pos);
c.real = atof(tmp.c_str());
tmp = s.substr(pos+1, s.length()-pos-2);
c.imag = atof(tmp.c_str());
return is;
}
int main() {
Complex c;
int n;
cin >> c >> n;
cout << c << ' ' << n;
return 0;
}
類型轉換運算符
類型強制轉換運算符被重載時不能寫返回值類型,實際上其返回值類型就是該類型強制轉換運算符代表的類型
重載的類型轉換運算符可以顯示的轉換也可以自動轉換,如:
#include <bits/stdc++.h>
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0):real(r), imag(i) { }
operator double() { return real; }
};
int main() {
Complex c(1.2, 3.4);
cout << (double)c << endl;
double n = c;
cout << n;
return 0;
}
上述兩行是輸出一樣嗒
自增,自減運算符的重載
由於自增自減有前置後置之分,爲了區分所重載的是前置運算符還是後置運算符,C++規定:
前置運算符作爲一元運算符重載
重載爲成員函數:
T & operator++();
T & operator–();
重載爲全局函數:
T1 & operator++(T2);
T1 & operator–(T2);
後置運算符作爲二元函數重載,多寫一個沒用的參數
重載爲成員函數:
T operator++(int);
T operator–(int);
重載爲全局函數:
T1 operator++(T2, int);
T1 operator–(T2, int);
在C++裏面後置運算符並沒有返回對操作數的引用
前置++和前置–的效率優於後置(整型變量沒啥差別)
但是在沒有後置運算符重載而有前置重載的情況下:在VS下,obj++也調用前置重載,而dev則令obj++編譯出錯
#include <bits/stdc++.h>
using namespace std;
class CDemo {
private:
int n;
public:
CDemo(int i = 0):n(i) {}
CDemo& operator++(){ // 前置++
n ++;
return *this;
} // ++tmp即爲tmp.operator++();
CDemo operator++(int k) { // 後置++
CDemo tmp(*this);
n ++;
return tmp; // 返回修改前的對象
} // tmp++即爲tmp.operator++(0);
// int作爲一個類型強制轉換運算符被重載
operator int () { return n; }
friend CDemo& operator--(CDemo& d) { // 前置--
d.n--;
return d;
} // --tmp即爲operator--(tmp);
friend CDemo operator--(CDemo& d, int) { // 後置--
CDemo tmp(d);
d.n--;
return tmp;
} // tmp--即爲operator--(tmp, 0);
};
int main() {
CDemo d(5);
cout << (d++) << ",";
cout << d << ",";
cout << (++d) << ",";
cout << d << ",";
cout << endl;
cout << (d--) << ",";
cout << d << ",";
cout << (--d) << ",";
cout << d;
return 0;
}
注意事項
1)C++不允許定義新的運算符;
2)重載後運算符的含義應該符合日常習慣;
3)運算符重載不改變運算符的優先級;
4)以下運算符不能被重載:
. | .* | :: | ?: | sizeof |
---|
5)重載運算符 () / [] / -> / = 時,運算符重載函數必須聲明爲類的成員函數
new和delete能被重載||ヽ( ̄▽ ̄)ノミ|Ю