運算符重載基本概念
1)目的是拓展原C程序運算符的作用範圍,使程序看起來更加簡潔
2)本質是函數,可以稱之爲運算符函數
3)可以定義爲普通函數,也可定義爲成員函數
4)把含運算符的表達式轉換成函數的調用
5)運算符操作數轉換爲函數的參數
6)運算符函數可以重載,調用時根據參數類型選擇
例如:
class complex{
public:
double real, imag;
complex(double r = 0.0, double i = 0.0) :real(r), imag(i){}
complex operator - (const complex & r);
};
complex operator+(const complex &c1, const complex &c2){
return complex(c1.real+c2.real,c1.imag+c2.imag);
}
complex complex::operator-(const complex &r){
return complex(real-r.real,imag-r.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.operator-(b);
return 0;
}
賦值運算符重載
1)考慮需要將兩個不同類型的變量相互賦值,比如將 char* 類型的字符串常量賦值給字符串對象 complex
2)只能是成員函數
如示例:實現 s=“hello" 功能
class String{
private:
char * str;
public:
String() :str(new char[1]){
str[0] = 0;
}
~String(){
delete[] str;//析構函數 負責當對象消亡時將str指向的空間釋放
}
const char * c_str(){
return str;
}
String& operator=(char * s);//聲明賦值運算符成員函數
};
String& String::operator=(char* s){//主義函數返回值爲對象的引用
delete[]str;//先將成員指針str指向的空間delete掉
str = new char[strlen(s) + 1];//再new出一片新的字符數組內存空間,空間大小爲 s指向的空間大小加一
strcpy(str, s);//然後將S指向的空間複製到str指向的空間 完成賦值
return *this;//最後返回被賦值的對象
}
int main(){
String s1, s2;
s1 = "hello";
s2 = "nice to meet you";
cout << s1.c_str() << endl << s2.c_str() << endl;
return 0;
}
輸出:
hello
nice to meet you
3)如果需要實現 s1=s2 的賦值語句,則上訴代碼存在問題
4)我們希望將s2.str指向的空間賦值給s1.str指向的空間,而單純 s1=s2的結果是使得s1.str與s2.str相同,即s1.str與s2.str同指向同一片內存空間
5)這樣就會導致當s1消亡時,及時s2沒有消亡,但s2.str所指向的空間已經被析構函數釋放了,這樣不妥
6)改進方式:重新定義類string的賦值運算符函數
例如:
String& operator=(const String & r);
String& String::operator=(const String & r){
delete[] str;
str = new char[strlen(r.str) + 1];
strcpy(str, r.str);
return *this;
}
7)如果出現 s1=s1 的語句 則上訴代碼會出現問題
8)這個語句會使得 s1.str 所指向的空間先釋放掉 從而導致無法進行後面的賦值
所以改進:
String& String::operator=(const String & r){
if (this==&r)//判斷賦值語句兩端的對象是否相同
return *this;
delete[] str;
str = new char[strlen(r.str) + 1];
strcpy(str, r.str);
return *this;
}
9)如果出現調用複製構造函數的情況,就會出現如上訴 s1=s2 一樣的問題
10)需要重新定義新的複製構造函數
例如:
String(const String& e){
str = new bar[strlen(e.str)+1];
strcpy(str,e.str);
}
11)爲了實現類似 (s1=s2)=s3(使得s1=s3) 的功能,賦值運算符函數的返回值需要是對象的引用(只有返回值是引用的函數可以放在等號的左側)
運算符重載爲友元函數
1)有時需要將運算符函數定義爲普通函數
例如:
class complex{
double real, imag;
public:
complex(double r, double i) :real(r), imag(i){}
complex operator+(int r){
return complex(real + r, imag);
}
};
2)如上的運算符函數operator+可以解釋類似於 c=c+5; 的代碼,但不能解釋類似於 c=5+c 的代碼
3)所以需要將運算符函數定義爲普通函數來實現上訴功能
例如:
complex operator+(double c,const complex & s){
return complex(s.real+c,s.imag);
}
4)但爲了使得普通函數可以訪問對象的私有成員變量,需要將其聲明爲友元
例如:
class complex{
double real, imag;
public:
complex(double r, double i) :real(r), imag(i){}
void c_print(){
cout << real << ',' << imag << endl;
}
complex operator+(double r){
return complex(real+r,imag);
}
friend complex operator+(double c, const complex & s);
};
賦值運算符重載應用 可變長數組實現
1)在應用中,如何能使得一個數組的長度可以改變,例如可以實現如下整型數組類的功能
int main(){
CArray a;//定義一個類CArray對象,其代表一個空數組(需要定義構造函數)
for (int i = 0; i < 5;++i)
a.push_back(i);//可以通過函數爲數組向後增加一個整型元素 i(需要定義push_back函數)
CArray a2, a3;
a2 = a;//可以通過賦運算符 = 將 a 數組內容賦值給 a2 數組內容(需要賦值運算符重載函數)
for (int i = 0; i < a2.length(); ++i)//可以用函數返回數組長度(需要定義length函數)
cout << a2[i] << " ";//可以通過[]運算符返回數組對應元素的值(需要定義[]運算符重載函數)
a2 = a3;
for (int i = 0; i < a2.length(); ++i)
cout << a2[i] << " ";
cout << endl;
a[3] = 100;//可以通過[]運算符進行對數組對應元素賦值的工作
CArray a4(a);//可以通過構造函數完成兩數組內容的複製(需要定義複製構造函數)
for (int i = 0; i < a4.length(); ++i)
cout << a4[i] << " ";
return 0;
}
2)類 CArray 定義如下
class CArray{
private:
int size;//用於儲存數組元素的個數
int * p;//用於指向整型數組空間
public:
CArray(int s = 0){//構造函數可以根據所給參數完成數組空間的分配
size = s;//儲存數組元素的個數
if (s == 0)//當參數爲零 則代表數組爲空
p = NULL;//指針置空
else//否則 數組元素爲 s
p = new int[s];//動態分配一片大小爲sizeof(int)*s的空間 將其地址賦值給指針
}
CArray(CArray& p1){//複製構造函數
if (!p1.p){//如果要複製的數組爲空
p = NULL;//則指針置空
size = 0;//元素數爲零
return;//返回
}
p = new int[p1.size];//否則 重新分配空間
memcpy(p, p1.p, sizeof(int)*p1.size);//並完成內存字節的複製
size = p1.size;//儲存元素數
}
~CArray(){//析構函數
if (p)//如果指針不爲空
delete[] p;//則將所指空間釋放
}
void push_back(int i);//push_back函數聲明
int length();//length()函數聲明
CArray & operator=(const CArray p1);//賦值運算符重載函數聲明
int & operator[](int i){//[]運算符重載函數 返回是int &(爲使得a[1]=3合法)
return p[i];//返回相應數組元素 引用使得返回值和數組元素一回事
}
};
3)push_back函數
void CArray::push_back(int i){//再數組後再增加一個元素 i
if (p){//判斷原數組不爲空
int * tmp;//定義一箇中間指針
tmp = new int[size + 1];//使中間指針指向一個大小比原數組大一個元素的數組空間
memcpy(tmp, p, sizeof(int)*size);//將原數組內容複製到中間指針指向的數組空間
delete[]p;//將原數組空間釋放
p = tmp;//將中間指針賦值給作用數組 此時原數組的空間多了一個元素空間的大小
}
else//原數組爲空
p = new int[1];//則直接動態分配一個元素大小的空間
p[size++] = i;//將size自加 並將最後的元素賦值爲 i
}
4)length()函數
int CArray::length(){
return size;//直接返回size
}
5)賦值運算符重載函數
CArray & CArray::operator=(const CArray p1){
if (p == p1.p)//判斷賦值運算符兩端爲同一個對象
return *this;//則直接返回
if (p1.p == NULL){//判斷賦值右端數組爲空數組 則將被賦值數組置空
if (p)//判斷被賦值數組不是空
delete[] p;//則將所指空間釋放
p = NULL;//再將指針置空
size = 0;//然後將元素數置零
return *this;//最後返回
}
if (size < p1.size){//判斷原數組空間大小比賦值數組小
if (p)//原數組不爲空
delete[] p;//則將所指空間釋放
p = new int[p1.size];//並重新分配一個與賦值數組空間一樣大小的空間
}
memcpy(p, p1.p, sizeof(int)*p1.size);//空間分配後 或空間夠用 則完成兩數組空間內容的複製
size = p1.size;//修改數組元素數
return *this;//最後返回
}
左移右移運算符重載
1)cout是 iostream 文件中定義的 ostream類的對象
2)cin是istream類的對象
3)運算符"<<",">>“分別在ostream類與istream類中進行了重載
4)故"cout<<5"的調用形式是"cout.operator(5)”
5)爲了能夠實現類似於"cout<<5<<“hello”"的語句,重載函數ostream::operator()的返回值類型應該是ostream類的引用:ostream &
例1:實現如下代碼 使能夠輸出 5hello
class student{
public:
int age;
};
ostream & operator<<(ostream & o, const student & s){//需要定義爲普通函數 函數參數等於運算符操作數(2) 爲了減小開銷函數參數使用對象的引用
o << s.age;
return o;
}
int main(){
student s;
s.age = 5;
cout << s << "hello";
return 0;
}
輸出:
5hello
例2:假定c是complex複數類的對象,現在希望寫"cout<<c",就能以"a+bi"的形式輸出c的值,寫"cin>>c",就能從鍵盤中讀入"a+bi"並賦值給c,使得c.real=a,c.imag=b。
class complex{
private:
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); //find()函數 找到字符'+'在字符串中的位置
string stmp = s.substr(0, pos); //將'+'前的字符串剝離出
c.real = atof(stmp.c_str()); //將剝離出的字符串轉換成flaot類型
stmp = s.substr(pos + 1, s.length() - pos - 2);
c.imag = atof(stmp.c_str());
return is;
}
int main(){
complex c;
int n;
cin >> c >> n;
cout << c << ' ' << n;
return 0;
}
輸入:
123+13.1i 456
輸出:
123+13.1i 456
重載類型轉換運算符
1)重載的類型轉換運算符有顯性和隱性
2)聲明定義時不用寫返回值類型 因爲返回值類型和運算符相同
3)單目運算符 操作數爲一 聲明成員函數時不用寫參數
class complex{
private:
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.2);
cout << (double)c << endl;//顯性作用 輸出1.2
double n = 2 + c;//隱性作用 等價於2+c.operator double ();
cout << n << endl;//輸出3.2
return 0;
}
輸出:
1.2
3.2
自增自減運算符重載
1)自增自減運算符有前置和後置之分
2)C++規定 前置時爲一元運算符 後置時爲二元運算符
3)前置運算符重載函數返回引用
例如:
class CDemo{
private:
int n;
public:
CDemo (int i = 0) :n(i){}
CDemo & operator++();//前置++
CDemo operator++(int );//後置++
operator int(){//類型轉換
return n;
}
friend CDemo & operator--(CDemo &);//前置--
friend CDemo operator--(CDemo &, int);//後置--
};
CDemo & CDemo::operator++(){//前置++
n++;
return *this;//返回原對象
}//++s等價於s.operator++()
CDemo CDemo::operator++(int i){//後置++
CDemo tmp(*this);//保存修改前的值
n++;
return tmp;//返回修改前的值
}//s++等價於s.operator(0)
CDemo & operator--(CDemo & r){//前置-- 參數得是引用
r.n--;
return r;//返回修改後的對象
}//--s等價於s.operator(s)
CDemo operator--(CDemo & r, int k){//後置--
CDemo tmp(r);//保存修改前的值
r.n--;//修改
return tmp;//返回修改前的值
}//s++等價於s.operator(s,0)
int main(){
CDemo d(5);
cout << d++ << ',';
cout << d << ',';
cout << ++d << ',';
cout << d << endl;
cout << d-- << ',';
cout << d << ',';
cout << --d << ',';
cout << d << endl;
return 0;
}
輸出:
5,6,7,7
7,6,5,5