CPP運算符重載

函數的重載

函數重載是指完成不同功能的函數可以具有相同的函數名
C++編譯器根據函數的實參確定應該調用哪一個函數。

1.定義的重載函數必須具有不同的參數個數,或不同的參數類型。編譯系統根據不同的參數去調用不同的重載函數,實現不同的功能。
2、僅返回值不同時,不能定義爲重載函數。

運算符重載

運算符重載的作用

  • 重新定義的運算符稱爲運算符的重載。
  • 運算符重載賦予已有的運算符多重含義或新的功能。C++通過重新定義運算符,使它能夠用於特定的對象,執行特定的功能

用一個經典的例子說明這個情況

  • 用運算符重載實現兩個複數相加
class Complex{
private:
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i){}
    void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
    Complex(const Complex &c){real = c.real;imag = c.imag;}
    Complex operator+(const Complex &c2);
};

Complex Complex::operator+(const Complex &c2){
    Complex c;
    c.real = real+c2.real;
    c.imag = imag+c2.imag;
    return c;
}

/*//也可以利用無名對象
Complex Complex::operator+(const Complex &c2){
    return Complex(real+c2.real,imag+c2.imag);
}
*/

int main(){
    Complex c1(1,2);
    Complex c2(2,5);
    Complex c3 = c1;
    c3.display();
    return 0;
}

爲了重載運算符,必須定義一個函數,告訴編譯器,遇到這個重載運算符就調用該函數,由這個函數來完成該運算符應該完成的操作,這種函數稱爲運算符重載函數,它通常是類的成員函數或者是友元函數。運算符的操作數通常是類的對象

運算符重載對C++有重要的意義:
本來C++提供的運算符只能用於C++的標準數據類型,不能用於用戶自己定義的類對象,影響了類和對象的使用。
C++不是爲類對象另外定義一批新的運算符,只允許重載現有的運算符,使這些簡單易用、衆所周知的運算符能夠直接用於類對象。

  • 運算符重載使C++具有更強大的功能、更好的可擴充性和適應性。

運算符重載的規則

<返回類型> operator<運算符>(<參數表>)
{函數體}
A operator+(A &);

operator是定義運算符重載函數的關鍵字,它與其後的運算符一起構成函數名

  • 只能對C++中已有的運算符進行重載。

在C++中允許重載的運算符如下
在這裏插入圖片描述

C++中不能重載的運算符只有5個:
. (成員訪問運算符)
.* (成員指針訪問運算符)
(域運算符)
sizeof (長度運算符)
?: (條件運算符)

  • 重載不能改變運算符的優先級、結合性、運算對象(即操作數)的個數。
  • 重載運算符的函數不能有默認的參數,否則就改變了運算符參數的個數。
class room{
    float Length;
    float Wide;
public:
    room(float a=0.0,float b=0.0){   Length=a;    Wide=b; }
    void Show(void) {cout<<"Length="<<Length<<'\t'<<"Wide="<<Wide<<endl;}
    void ShowArea(void) { cout<<"Area="<<Length*Wide<<endl;    }
    room operator+(room &);//重載運算符+,函數原型
};
room room::operator + (room &r)   //重載運算符函數
{   room rr;
    rr.Length =Length+r.Length;
    rr.Wide =Wide+r.Wide ;
    return rr;
}
int main(void)
{   room r1(3,2),r2(1,4), r3,r4,r5;
    r1.Show ();    r2.Show ();
    r3=r1+r2;
    r3.Show ();
    r4=r1+r2+r3; //運算順序:(r1+r2), (r1+r2)+r3
    r4.Show ();
    r5=r1+(r2+r3); //報錯!!!
    r5.Show();
    return 0;
}
  • 重載的運算符必須和用戶定義的自定義類型的對象一起使用,其參數至少應有一個是類對象(或類對象的引用)。
  • 參數不能全部是C++的標準類型,以防止用戶修改用於標準數據類型的運算符。

有兩個運算符一般不需要重載

  • ①賦值運算符(=)可以用於每一個類對象,利用它在同類對象之間相互賦值。(也有必須重載的情況)
  • ②地址運算符&也不必重載,它能返回類對象在內存中的起始地址。

運算符重載的方式

方式(1) 作爲類成員函數

當用成員函數實現運算符的重載時,運算符重載函數的參數只能有二種情況:
沒有參數
帶有一個參數

  • 對於只有一個操作數的運算符,在重載這種運算符時,通常不能有參數;
  • 對於有二個操作數的運算符,只能帶有一個參數。這個參數可以是對象、對象的引用或其它類型的參數。

用成員函數實現運算符的重載時**,運算符的左操作數爲當前對象**,這是一個隱含的函數參數,要用到隱含的this指針

c = a+b ; c = a.operator+(b);
c += a  ; c = operator+=(a);
c = ++a ; c = a.operator++();
class A{
public:
	int x ;
	A(int x):x(x){}
};
a + b ;
實際上爲 a.operator+(b);

A operator+(A&b)
{
	return(this->x+b.x)
} 

運算符重載函數不能定義爲靜態的成員函數,因爲靜態的成員函數中沒有this指針。

demo

class A
{    int i;
public:     A(int a=0){ i=a;    }
    void Show(void){    cout<<"i="<<i<<endl;    }
    A operator +(A &a){
        A   t;
        t.i=i+a.i;
        return t;
    }
    A operator+=(A &a){
        i=i+a.i;
        return *this;  //修改這個對象的成員數據後,再返回
    }
};

int main(void)
{    A a1(10),a2(20),a3;
    a1.Show ();
    a2.Show ();
    a3=a1+a2;
    a3.Show ();
    a1+=a2;  // int a1,a2; a1+=a2就相當於 a1=a1+a2;
    a1.Show ();
    return 0;
}

方式2:作爲類的友元函數

友元函數是在類外的普通函數,與一般函數的區別是可以調用類中的私有或保護數據。

friend <類的類型> operator<運算符><參數表>{……}

friend A operator+(A &a,A &b)
{……}
c = a+b; //c = operator+(a,b);

將運算符重載函數定義爲友元函數,參與運算的對象全部成爲函數參數
(沒有隱含的this指針了)

注意和上面的區別!!!

c = a+b; c = operator+(a,b);
c += a; operator+=(c,a);
c = ++a; c = operator++(a); 

對單目運算符,友元函數有一個參數。
對雙目運算符,友元函數有2個參數。

還是用上面的複數相加的例子:

將運算符“+”重載爲適用於複數加法,重載函數不作爲成員函數,而放在類外,作爲Complex類的友元函數。

class Complex{
private:
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i){}
    void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}

    friend Complex operator+(const Complex &,const Complex &);
};

Complex operator+(const Complex &c1,const Complex &c2){
    //聲明瞭爲友元,可以使用Complex的私有數據
    return Complex(c1.real+c2.real,c1.imag+c2.imag);
}


int main(){
    Complex c1(1,2),c2(2,5);
    Complex c3 = c1+c2;
    c3.display();
    return 0;
}

方式3:既非類的成員函數也不是友元函數的普通函數

只有在極少的情況下才使用既不是類的成員函數也不是友元函數的普通函數(一般不用),因爲普通函數不能直接訪問類的私有成員。

仍然以上面的複數類爲例
考慮這樣的情形:

Complex c3 = 2+c2;// 不合法
只能寫作:
Complex c3 = Complex(0,1)+c2; 

那如果就像使這種寫法可行呢

可以這樣重載:

friend Complex operator+(int r,Complex &c);
Complex operator+(int r,Comlex &c)
{
	return Complex(r+c.real,c.imag);
}

注意這裏的加法交換律並不默認處理,
如果需要,再重載一次就可。

friend Complex operator+(Complex &c,int r);
Complex operator+(Comlex &c,int r)
{
	return Complex(r+c.real,c.imag);
}

C++規定,賦值運算符=、下標運算符[]、函數調用運算符()、成員運算符-> 必須定義爲類的成員函數

流插入運算符<<、流提取運算符>>、類型轉換運算符不能定義爲類的成員函數只能作爲友元函數

典型運算符的重載

雙目運算符的重載

常用的有:

< > <= >= + - * / %

把雙目運算符即爲B

  • 重載爲類的成員函數
a1 B a2 相當於 a1.operatorB(a2);
  • 重載爲類的友元函數
a1 B a2 相當於 operatorB(a1,a2);

單目運算符重載

常用的有

! - & *  ++  --

注意:

b = ++a;
b = a++;

雖然++運算後對象a的值一致,但先自加或後自加的函數的返回值不一致,必須在重載時予以區分。

  • ++爲前置運算時,成員函數重載的一般格式
<type> operator++()
{……}
  • ++爲後置運算時,成員函數重載的一般格式
<type> operator++(int)
{……}
class Complex{
private:
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i){}
    void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
    //前置++的重載 ++c;
    Complex operator++();
    Complex operator++(int);
};

Complex Complex::operator++(){
    real++;
    imag++;
    return *this;
}

Complex Complex::operator++(int){
    Complex c(*this);  //將當前對象的引用傳給複製構造參數
    real++;
    imag++;
    return c;
}



int main(){
    Complex c1(1,2),c2(2,5),c3;
    c3 = c1++;
    c3.display();
    c1.display();
    c3 = ++c2;
    c3.display();
    c2.display();
    return 0;
}
1 , 2i
2 , 3i
3 , 6i
3 , 6i
  • ++爲前置運算的時候,友元函數重載函數的一般格式
A operator++(A &a)
{……}
  • ++爲後置運算的時候,友元函數重載函數的一般格式
A operator++(A&a,int)
{……}
class Complex{
private:
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i){}
    void display()const{cout<<real<<" , "<<imag<<"i"<<endl;}
    //前置++的重載 ++c;
    friend Complex& operator++(Complex&);
    friend Complex operator++(Complex&,int);
};

//當然此處也可以不返回對象引用,但是耗內存和時間。
Complex& operator++(Complex& c){
    c.real++;
    c.imag++;
    return c;
}

//當然此處絕不返回對象引用,因爲c1是臨時的副本,生命在函數結束之後被析構
Complex operator++(Complex& c,int){
    Complex c1(c);  //將當前對象的引用傳給複製構造參數
    c.real++;
    c.imag++;
    return c1;
}



int main(){
    Complex c1(1,2),c2(2,5),c3;
    c3 = ++c1;
    c1.display();
    c3.display();
    c3 = c2++;
    c2.display();
    c3.display();
    return 0;
}
2 , 3i
2 , 3i
3 , 6i
2 , 5i

流運算符的重載

cincout分別是istream類ostream類對象。在類庫提供的頭文件中已經對“<<”和“>>”進行了重載,作爲流插入運算符和流提取運算符,能用來輸出和輸入C++標準類型的數據,用#include 包含到自己的程序文件中。

用戶自己定義的類型的數據,不能直接用“<<”和“>>”來輸出和輸入,必須進行重載。

istream & operator >> (istream &, 自定義類 &);
ostream & operator << (ostream &, 自定義類 &);

只能將重載“>>”和“<<”的函數作爲友元函數或普通的函數,而不能將它們定義爲成員函數。

原因:
一旦將其重載爲類的成員函數,那麼左操作數就是調用<<的這個對象,那麼operator <<的第一個參數就是這個對象,隱含的this指針,那麼就只能這樣調用
A << cout。這樣顯然不是我們要的。

cout<<a 被編譯系統解釋爲
operator<<(cout,a)
class Complex{
private:
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i){}
    friend ostream& operator<<(ostream&,Complex&); //重載 << 插入運算符
    friend istream& operator>>(istream&,Complex&); //重載 >> 插入運算符    
};

ostream& operator<<(ostream& output,Complex& c)
{
    if(c.imag>0)
        output<<"("<<c.real<<"+"<<c.imag<<"i"<<")"<<endl;
    else if(c.imag==0)
        output<<c.real<<endl;
    else
        output<<"("<<c.real<<c.imag<<"i"<<")"<<endl;
    return output;
}

istream& operator>>(istream& input,Complex& c)
{
    cout<<"input  real part and imaginary part of complex number:"<<endl;
    input>>c.real>>c.imag;
    return input;
}

int main(){
    Complex c1;
    cin>>c1;
    cout<<c1;
    return 0;
}
input  real part and imaginary part of complex number:
1 1 //從鍵盤輸入
(1+1i)
  • return output 的作用
返回cout的當前值,**以便連續輸出** 。output是ostream類的對象,它是實參cout的引用,也就是cout通過傳送地址給output,二者共享同一段存儲單元,或者說**output是cout的別名**return output就是return cout,將輸出流cout返回,即保留輸出流的現狀。

賦值運算符重載 “=”

同類型的對象間可以相互賦值,默認實現是把對象的各個成員一一賦值。注意:與複製構造函數調用方式的不同。

注意:
①②是不一樣的,①是調用複製構造函數的簡便寫法

②是先創建a2對象,再用賦值語句=,這沒有調用複製構造函數

①
A a1;
A a2 = a1; //相當於   A a2(a1);
② 
A a1,a2;
a2 = a1;  

說明:
當對象的成員中使用了new開闢空間,
不能直接使用默認的賦值運算符“=”,否則在程序的執行期間會出現運行錯誤。
這個原因和複製構造函數是一致的。
同一空間釋放兩次,必須重載。

class  A{
    char *ps;
public:
    A( ){ ps=0;}
    A(const char *s ){ps =new char [strlen(s)+1];  strcpy(ps, s);}
    ~A( ){ if (ps)   delete [] ps;}
    void Show(void) {  cout<<ps<<endl;}
};

int  main(void )
{ A s1("China!"), s2("Computer!");
    s1.Show();
    s2.Show();
    s2=s1; //相當於 s2.ps=s1.ps;
    s1.Show();
    s2.Show();
}

前四行如願輸出

China!
Computer!
China!
China!
CppReview(52520,0x1000d5dc0) malloc: *** error for object 0x10056f560: pointer being freed was not allocated
CppReview(52520,0x1000d5dc0) malloc: *** set a breakpoint in malloc_error_break to debug

重載賦值運算符“=”

格式

<函數類型> <className>::operator=(<參數表>)

A& A::operator=(A&a)

b = a; 相當於  b.operator=(a); 

demo

class  A{
    char *ps;
public:
    A( ){ ps=0;}
    A(const char *s ){ps =new char [strlen(s)+1];  strcpy(ps, s);}
    A& operator=(A&);
    ~A( ){ if (ps)   delete [] ps;}
    void Show(void) {  cout<<ps<<endl;}
};

//返回同類型的引用適合於連續賦值
A& A::operator=(A&a)
{
    if(ps) delete []ps;  //這塊空間如果不主動delete掉,即使此對象析構,也不會釋放
    if(a.ps){
        ps = new char[strlen(a.ps)+1];
        strcpy(ps, a.ps);  // 必須是深複製
    }
    else{
        ps=0;
    }
    return *this;
}

int  main(void )
{ A s1("China!"), s2("Computer!");
    s1.Show();
    s2.Show();
    s2=s1; //相當於 s2.ps=s1.ps;
    s1.Show();
    s2.Show();
}

轉換構造函數

  • 數據類型之間的顯式類型轉換
    類型名(數據)
int(10.5)  == 10

轉換構造函數是將一個其他類型的數據轉換成一個類的對象,屬於構造函數的重載。

以上面的Complex類爲例

//默認構造函數
Complex() 
//初始化的構造函數
Complex(double r,double i)
//用於複製對象的複製構造函數
Complex(const Complex &c)
//轉換構造函數
Complex(double r){real=r;imag=0}
class Complex{
private:
    double real,imag;
public:
    Complex(double r,double i):real(r),imag(i){}
    Complex(double r){real = r;imag =0;}
    friend ostream& operator<<(ostream&,Complex&); //重載 << 插入運算符
};

ostream& operator<<(ostream& output,Complex& c)
{
    output<<"("<<c.real<<"+"<<c.imag<<"i"<<")"<<endl;
    return output;
}

int main(){
    Complex c1(1,10);
    Complex c2 = 3.0;
    cout<<c1;
    cout<<c2;
    return 0;
}
Complex c2 = 3.0; //轉換構造函數
//相當於 Complex c2(3.0)

隱式轉換常常會帶來程序邏輯的錯誤,而且這種錯誤一旦發生是很難察覺的,應當儘量避免。
在聲明構造函數的時候前面添加上explicit即可,這樣就可以防止這種轉換。C++中的explicit關鍵字只需用於修飾只有一個參數的類構造函數, 表明該構造函數是顯示的, 而非隱式的, 跟它相對應的另一個關鍵字是implicit, 意思是隱藏的,類構造函數默認情況下即聲明爲implicit(隱式)。

一旦聲明爲顯式的(explicit)

//Complex c2 = 3.0; //違法
//只能這樣寫
Complex c2(3.0)

不僅可以將一個標準類型數據轉換成類對象,也可以將另一個類的對象轉換成類對象。

class B;
class A{
public:
    int x;
    A(int x):x(x){}
    A(B&);
};


class B{
public:
    int x;
    B(int x):x(x){}
};

A::A(B &b)
{
    x = b.x;
}

int main(){
    B b(1);
    A a = b;
    cout<<a.x<<endl;
    return 0;
}

類型轉換函數

類型轉換函數是在類中定義一個成員函數,將這個類的對象轉換爲另外一種類型的數據

  • ①轉換函數必須是類的成員函數,不能是友元函數
  • ②轉換函數的調用是隱含的,沒有參數,操作數是對象。
    格式
operator <type>()
{……
	return <type>;
}


ClassName:: operator <type>()
{
	……
}

仍以Complex類爲例

在這裏插入代碼片
class Complex{
private:
    double real,imag;
public:
    Complex(double r,double i):real(r),imag(i){}
    operator double();

};

Complex::operator double()
{
    cout<<"轉換爲複數的模長"<<endl;
    return sqrt(real*real+imag*imag);
}

int main(){
    Complex c(3,4);
    cout<<c<<endl; //這裏就可以直接cout了,因爲存在着類型轉換
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章