C++之路 Day9

C++運算符重載

      今天專題說下C++中的運算符重載,重載這個概念是跨語言的,無論java還是python等面向對象編程語言中都有重載。那麼什麼是運算符重載?C++中的 運算符 (算術運算符、關係運算符、邏輯運算符、位運算符、賦值運算符等)有:+,-,*,/,%,++,--,=,<=,>=,!=,==,&&,||,!,&,~,|,^,<<,>>,+=,*=,^=,&=等等。給出定義:在同一個作用域中運算符指定不同的定義,這就是運算符重載。比如 & 既可以表示取地址,又表示 位操作 的 與運算。

      接着問題,爲什麼需要運算符重載?

      以上所列出的運算符一般是針對C++的內置基本數據類型,如果想讓我們 自定義的類型 也可以實現比如+、-、*、/等運算符的運算呢?此時我們就需要對運算符重載。先拋出一句話:運算符重載的本質是 函數重載。函數重載可再分類爲:友元重載,成員重載。

 

運算符重載語法格式:

      一般格式如下,operator 是關鍵字,後面跟的是運算符,operator+運算符 構成了新的函數名,此時我們說該運算符被重載了。

返值類型 operator 運算符名稱(形參列表) 
{ 
    重載實體; 
}

先看個例子:

//友元重載&成員重載
#include <iostream>

using namespace std;

class Complex{
public:
    Complex(float x, float y):_x(x),_y(y){}
    void dis(){
        cout<<"("<<_x<<"."<<_y<<")"<<endl;
    }
    friend const Complex operator+(const Complex &c1, const Complex &c2);   //友元重載
    Complex const operator+(const Complex &another);    //成員重載,若兩者同時存在,優先使用成員重載
private:
    float _x;
    float _y;
};

const Complex operator+(const Complex &c1, const Complex &c2){
    return Complex(c1._x + c2._x, c1._y + c2._y);
}
const Complex Complex::operator+(const Complex &another){
    cout<<"member func overload"<<endl;
    return Complex(this->_x + another._x, this->_y + another._y);
}
int main()
{
    Complex c1(1, 2);
    Complex c2(3, 4);
    c1.dis();
    c2.dis();
    Complex c3 = c1 + c2;
    c3.dis();
    return 0;
}

再看一個例子:

//賦值運算符重載
//用一個已有對象,給另外一個已有對象賦值.
//兩個對象均已被創建結束後,發生的賦值行爲,就會涉及到賦值運算符重載
//系統提供的默認賦值運算符重載,一經自實現,不復存在
//系統提供的也是shallow copy,會造成內存泄漏,重析構
//要實現deep copy必須自定義
//返回引用,通常 不能用 const 修飾
#include <iostream>

using namespace std;

struct Date{
public:
    Date(int y = 2019, int m = 6, int d = 6)
        :year(y), month(m), day(d){}

    Date &operator=(const Date &another){
        this->year = another.year;
        this->month = another.month;
        this->day = another.day;
        return *this;
    }
    void dis(){
        cout<<"year:"<<year<<"month:"<<month<<"day:"<<day<<endl;
    }
private:
    int year;
    int month;
    int day;
};

int main()
{
    Date d(2209, 10, 9);
    d.dis();    //year:2209month:10day:9

    Date d2(2019, 9, 9);
    //d2 = d;
    d2.dis();   //year:2019month:9day:9

    Date d3;

    d3 = d2 = d;
    d3.dis();   //year:2209month:10day:9
    d2.dis();   //year:2209month:10day:9
    d.dis();    //year:2209month:10day:9
    d3.operator=(d2.operator=(d));
    d3.dis();   //year:2209month:10day:9
    d2.dis();   //year:2209month:10day:9
    return 0;
}

再看一個綜合案例:本例子相當於實現一個簡單的string類。

//棧對象可以返回,不可以返回棧對象的引用
#include <iostream>
#include <string.h>

using namespace std;

class mystring{
public:
    mystring(){
        _str = new char[1];
        *_str = '\0';
    }
    mystring(char *s){
        int len = strlen(s);
        _str = new char[len+1];
        strcpy(_str, s);
    }
    mystring(const char* s){
        if(s == nullptr){
            _str = new char[1];
        }else{
            _str = new char[strlen(s) + 1];
            strcpy(_str, s);
        }
    }
    //copy constructor
    mystring(const mystring &another){
        _str = new char[strlen(another._str) + 1];
        strcpy(_str, another._str);
    }
    mystring &operator=(const mystring &another){
        if(this == &another){    //把this對象賦值給this對象會導致BUG
            return *this;
        }
        delete []this->_str;
        int len = strlen(another._str);
        _str = new char[len+1];
        strcpy(_str, another._str);

        return *this;
    }
    mystring &operator+(const mystring &another){
        int catLen = strlen(this->_str);
        int srcLen = strlen(another._str);
        int len = catLen + srcLen;
        this->_str = static_cast<char *>(realloc(this->_str, len+1));
        memset(this->_str+catLen,0,srcLen+1);
        strcat(this->_str, another._str);

        return *this;
    }
    bool operator == (const mystring &another){
        return strcmp(this->_str, another._str) == 0;
    }
    bool operator > (const mystring &another){
        return strcmp(this->_str, another._str) > 0;
    }
    bool operator < (const mystring &another){
        return strcmp(this->_str, another._str) < 0;
    }

    char &operator[](int idx){
        return _str[idx];
    }

    ~mystring(){
        delete [] _str;
    }
private:
    char *_str;
};

int main()
{

    return 0;
}

重載規則瞭解下:

      1、可以被重載的運算符操作符:

      

+ - * / % ^ & | ~
! , = < > >= <= ++ --
<< >> == != && || += -= /=
%= ^= &= |= *= <<= >>= [] ()
-> ->* new new[] delete delete[]      

      2、不能改變操作數的個數

            關係運算符">"和"<"等是雙目運算符,重載後仍爲雙目運算符,需要兩個參數。運算符 "+","-","*","&"等既可以作爲單目運算符,也可以作爲雙目運算符,可以分別將它們重 載爲單目運算符或雙目運算符

      3、不改變語義

            應當是重載運算符的功能類似於運算符作用於標準內置數據類型所實現的功能。比如+號就不要重載爲 - 號。

      4、 至少有一個操作數是自定義類類型

            操作數不能全是C++的標準類型,以防止用戶修改用於標準數據類型成員的運算符的性質!

      5、只能重載 爲 成員的運算符

= 賦值運算符
[] 下標運算符
() 函數運算符
-> 間接成員訪問
->* 間接取值訪問

      6、C++不允許用戶自己定義新的運算符,只能對已有的C++運算符進行重載 

 

最後再看個例子:

#include <iostream>

using namespace std;

class Mail;
class Sender{
public:
    Sender(string s):_addr(s){}
    Sender &operator<<(const Mail &mail);

private:
    string _addr;
};

class Mail{
public:
    Mail(string _t, string _c):_title(_t),_content(_c){}
    friend Sender& Sender::operator<<(const Mail &mail);
private:
    string _title;
    string _content;
};

Sender & Sender::operator<<(const Mail &mail){
    cout<<"Address:"<<_addr<<endl;
    cout<<"Title :"<<mail._title<<endl;
    cout<<"Content:"<<mail._content<<endl;
    return *this;
}

int main()
{
    Sender sender("[email protected]");
    Mail mail("notice", "drink milk at 6:30");
    Mail mail2("tour", "Six days in Chongqing");
    sender<<mail<<mail2;
    return 0;
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章