運算子過載

浅晓寒發表於2024-05-03

運算子過載

基本規則

可以過載的運算子:

運算子過載

不可過載的運算子:

運算子過載
//返回型別 operator後面加運算子(引數列表)
//eg.  Integer operator+(Integer l, Integer r);

class Integer{
public:
    Integer(int n = 0) : i(n) {}
    const Integer operator+(const Integer& v){	//在類中定義運算子過載函式
        return Integer(i+v.i);
    }
    const void print_i(){ cout << i << endl; }
private:
    int i;
};

int main()
{
    Integer x(10),y(20);
    Integer z = x + y;	//相當於 x.operator+(y)
    z.print_i();	//列印結果
    
    z = x + 3;	//ok,列印出13
    z.print_i();	
    
    z = 3 + 7;	//ok,將10傳給建構函式建立一個Integer物件
    z.print_i();
    
    z = 3 + y;	//error,3不是Integer物件,沒有實現運算子過載,會報錯
    z.print_i();	
    
    return 0;
}


由上面的例子可以看到,z = 3 + y會報錯,因為3不是Integer物件,雙目運算子過載呼叫的是運算子左邊的物件的運算子過載函式。可將成員函式修改為全域性函式(在類中將該函式新增friend關鍵字)。

class Integer{
public:
    Integer(int n = 0) : i(n) {}
    friend const Integer operator+(const Integer& l, const Integer& r);
    const void print_i(){ cout << i << endl; }
private:
    int i;
};

const Integer operator+(const Integer& l, const Integer& r){	//全域性函式
        return Integer(l.i + r.i);
}

int main()
{
    Integer x(10),y(20);
    Integer z = x + y;	//相當於 x.operator+(y)
    z.print_i();	//列印結果
    
    z = x + 3;	//ok,列印出13
    z.print_i();	
    
    z = 3 + 7;	//ok,將10傳給建構函式建立一個Integer物件
    z.print_i();
    
    z = 3 + y;	//ok,會呼叫全域性函式 
    z.print_i();	
    
    return 0;
}

是否將運算子過載設定為成員函式的基本規則:

1、單目運算子應該被設定為成員函式

2、= () [] -> ->*必須設定為成員函式

3、賦值運算子應該被設定為成員函式

4、其他的雙目運算子應該作為全域性函式(如+、-、*、/等)


原型

常見運算子的原型:

運算子++和--,++和--也是可以實現運算子過載,怎麼區分是++x還是x++?

首先要了解a++和++a的區別:

a++可以這麼理解:先對a原來的值(a=5)複製一份,接著執行a = a + 1,最後將之前複製的副本賦值給b,於是就有b = 5, a = 6

++a可以這麼理解:先執行a = a + 1,最後將新的結果a = 7賦值給b,於是就有b = 7, a = 7

int main()
{
    int a = 5;
    int b = a++;	//b = 5, a = 6
    cout << "b = " << b << ",a = " << a <<endl;
    int c = ++a;	//a = 7, c = 7
    cout << "c = " << c << ",a = " << a <<endl;
    
    return 0;
}

輸出結果:


++、--運算子的過載

按照這樣的運算規則對++運算子進行過載。

class Integer{
public:
    Integer(int n = 0) : i(n) {}
    friend const Integer operator+(const Integer& l, const Integer& r);
    friend const Integer operator-(const Integer& l, const Integer& r);
    const int get() { return i;}
    const Integer& operator++();	//++x,++做字首
    const Integer operator++(int);	//x++,int並不會作為形參傳遞,返回的是一個新的Integer物件,所以不加引用
    const Integer& operator--();	//--x,--做字首
    const Integer operator--(int);	//x--,int並不會作為形參傳遞
private:
    int i;
};

//對'+'運算子過載
const Integer operator+(const Integer& l, const Integer& r){	//全域性函式
        return Integer(l.i + r.i);
}

//直接對原來物件的值進行修改
const Integer& Integer::operator++(){
    *this = *this + 1;  //呼叫'+'的過載函式
    return *this;   //返回一個integer物件,加&是是因為這是對原來的物件的直接修改
}

//返回值不加引用是因為返回的是一個區域性物件,return後實際上會執行一個複製建構函式操作
const Integer Integer::operator++(int){ //返回一個新建立的物件
    Integer old(*this);
    ++(*this);  //呼叫上面的++過載函式
    return old;
}

//實現--x
const Integer& Integer::operator--(){
    *this = *this - 1;
    return *this;
}

//實現x--
const Integer Integer::operator--(int){
    Integer old(*this); //複製建構函式
    --(*this);  //呼叫--x的重構函式
    return old;
}


int main()
{
    Integer x(5);
    Integer y = x++;
    cout << "y.i = " << y.get() << ", x.i = " << x.get() << endl;
    Integer z = ++x;
    cout << "z.i = " << z.get() << ", x.i = " << x.get() << endl;
    
    return 0;
}

可以看到輸出結果與上個例子結果一致。


關係運算子的過載

透過==過載來實現!=的過載,透過<過載來實現>、<=、>=的過載,這麼寫的好處是充分利用已有函式,後期修改只需修改兩個函式

class Integer{
public:
    Integer(int n = 0) : i(n) {}
    friend const Integer operator+(const Integer& l, const Integer& r); //友元函式
    friend const Integer operator-(const Integer& l, const Integer& r);
    const int get() { return i;}
    const Integer& operator++();	//++x,++做字首
    const Integer operator++(int);	//x++,int並不會作為形參傳遞,返回的是一個新的Integer物件,所以不加引用
    const Integer& operator--();	//--x,--做字首
    const Integer operator--(int);	//x--,int並不會作為形參傳遞
    //overload relational operators
    bool operator==(const Integer& r) const;
    bool operator!=(const Integer& r) const;
    bool operator<(const Integer& r) const;
    bool operator<=(const Integer& r) const;
    bool operator>=(const Integer& r) const;
    bool operator>(const Integer& r) const;

private:
    int i;
};

// overload relational operators definition
bool Integer::operator==(const Integer& r) const{
    return (i == r.i);
}

bool Integer::operator!=(const Integer& r) const{
    return !(*this == r);   //呼叫==的過載函式
}

bool Integer::operator<(const Integer& r) const{
    return i < r.i;
}

bool Integer::operator>(const Integer& r) const{
    return r < *this;   //呼叫<的過載函式
}

bool Integer::operator<=(const Integer& r) const{
    return !(r < *this);   //呼叫>的過載函式, <=就是>取反
}

bool Integer::operator>=(const Integer& r) const{
    return !(*this < r);   //呼叫<的過載函式, >=就是<取反
}


int main()
{
    Integer x(5);
    Integer y(7);
    
    cout << boolalpha << (x < y) <<endl;	//呼叫x.operator<(y),boolalpha使得列印出bool型別
    cout << boolalpha << (x > y) <<endl;
    cout << boolalpha << (x == y) <<endl;
    
    return 0;
}

輸出結果:


型別轉換

使用者定義的型別轉換:當建構函式是單個引數或運算子轉換的隱式型別轉換時編譯器會進行隱式轉換。

C++型別轉換:

運算子過載

對於使用者定義的型別,有兩種方法實現T==>C。一是C中存在用T作為單個引數傳遞的建構函式,二是T中存在用運算子過載的方式實現T==>C。

建構函式實現自動型別轉換

#include <iostream>

class One{
public:
    One()  {}   
};

class Two{
public:
    Two(const One&){}
};


void f(Two){}

int main()
{
    One one;    
    f(one);     //wants a Two, has a one

    return 0;
}
運算子過載

f()函式需要Two型別的物件作為引數,當將物件one作為引數傳遞時,編譯器會查詢是否存在用類One來構建類Two的建構函式,這時會呼叫Two::Two(const One&),結果就是將Two的物件作為引數傳遞給f()。

自動型別轉換可以避免定義兩個不同版本的f()函式,但是自動型別轉換會隱式地呼叫Two的建構函式,會對程式的效率有影響。

避免編譯器使用建構函式實現自動型別轉換需要加關鍵詞explict,如下:

class One{
public:
    One()  {}   
};

class Two{
public:
    explicit Two(const One&){}
};


void f(Two){}

int main()
{
    One one;
    //f(one);		//error
    f(Two(one));     //ok,Two(one)建立了一個臨時的物件將其作為引數傳遞給f()
    return 0;
}

運算子過載實現自動型別轉換

class Three{
private:
    int i;
public:
    Three(int ii) : i(ii) {}
};

class Four{
private:
    int x;
public:
    Four(int xx) : x(xx) {}
    operator Three() const { return Three(x); } //函式名就是要轉換的型別,所以最前面不用加返回值型別
};

void g(Three) {}

int main()
{
    Four four(1);
    g(four);	//實現Four==>Three,再將轉換後的物件傳遞給g()
    g(1);		//呼叫Three(1,0)
    return 0;
}

自動型別轉換的缺陷

當上述提到的兩種自動型別轉換的情況同時存在時,就會產生一個模糊的轉換,兩種方式都能實現隱式的自動型別轉換,編譯器不知道使用哪種方式,就會出現報錯的情況

class Orange{
public:
    Orange(const Apple&);       //Convert Apple to Orange
};


class Apple{
public:
    operator Orange() const;    //Convert Apple to Orange
};

void f(Orange){ }

int main()
{
    Apple a;
    //f(a);       //ambiguous conversion
    return 0;
}

型別轉換總結

儘量不要使用這種自動型別轉換,函式呼叫時會出現各種問題,使用顯式型別轉換函式,如double ToDouble()來代替operator double() const。


參考資料:

Thinking in C++,Chapter 12 Operator Overloading

浙江大學翁凱C++教程

相關文章