C++知識點總結(其他語法1-運算符重載)

運算符重載1(operator overload)

運算符重載(操作符重載) : 可以爲運算符增加一些新的功能.

#include <iostream>
using namespace std;

class Point {
    friend Point operator+(const Point &, const Point &);
private:
    int m_x;
    int m_y;
public:
    Point(int x, int y) : m_x(x), m_y(y) {}
    void display() {
        cout << "(" << m_x << ", " << m_y << ")" << endl;
    }
};

Point operator+(const Point &p1, const Point &p2) {
    return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}


int main()
{
    Point p1(10, 20);
    Point p2(20, 30);
    Point p3 = p1 + p2;
    // 本質是Point p3 = operator+(p1, p2);
    p3.display();
    Point p4 = p1 + p2 + p3;
    // 本質是調用了兩次operator+, 第一次的結果又作爲參數傳進去
    p4.display();

    return 0;
}

運算符重載2-完善

(1)爲什麼拷貝構造函數的參數是const Point &point 形式?
1.必須是引用, 否則報錯.

class Point {
public:
    Point(Point point) {
        this->m_x = point.m_x;
        this->m_y = point.m_y;
    }
};
int main() {
    Point p1(10, 20);
    Point p2 = p1;
    // 利用已經存在的對象創建新的對象, 
    // 調用拷貝構造函數, 相當於Point point = p1 
    // 又是利用已經存在的對象創建新的的對象, 又
    // 要調用拷貝構造函數, 就又相當於
    // Point point = p1, 從而陷入死循環.所以
    // 必須是引用. 一旦變成引用就是地址值的賦值
    // 就不存在創建對象了.
}

2.最好是const, 從而保證可以接受const對象和非const對象, 使接受參數的範圍更大.

運算符重載3-更多運算符

(1)一般來說跟某個對象相關的代碼, 乾脆重載爲成員函數.
一旦變爲成員函數, 就變成了

class Point {
public:
    Point operator+(const Point &point) {
        return Point(this->m_x + point.m_x, this->m_y + point.m_y);
    // 只接受一個參數, 因爲一旦變爲成員函數, 就
    // 通過對象去調用了, 比如p1 + p2 本質就變成了
    // p1.operator+(p2);就意味着會把p1的地址傳給
    // operator+函數, this就指向外面的p1.
    // 對比原來來說簡單了, 首先只需要接受一個參數
    // 其次這個功能本來就是跟Point相關的, 所以
    // 直接寫在Point類裏面.而且一旦寫成成員函數就
    // 意味着我們自己的私有成員變量可以訪問.
    // 不需要友元函數.
        
    }
};

(2)存在的問題:
在重載運算符的時候, 最好要保留運算符原有的特性, 比如:
因爲int a = 10, b = 20; (a + b) 這個表達式不允許被賦值(因爲a + b 返回的是常量並沒有被存儲在內存中), 所以(p1 + p2)也應該不能賦值, 因爲p1 + p2 返回的是一個臨時對象, 馬上就要銷燬了, 所以不應該可以賦值, 所以要在operator+函數的返回值前加const聲明爲返回常量對象, 不允許被賦值, 但這樣的話, 又會導致新的問題, 在p1 + p2 + p3 時, 相當於p1.operator+(p2).operator+(p3) 因爲常量對象是不能調用非常量函數的(因爲在非常量函數裏面按語法的話可以修改裏面對象的值, 而常量對象又不允許修改, 矛盾), 所以也應該把operator+函數聲明爲常量函數.最終爲
const Point operator+(const Point &point) const {

}
const Point operator-(const Point &point) const {

}
(3)+=運算符
因爲int a = 10, b = 20; (a += b) = 10; 是可以被賦值的, 因爲a += b是a + b後的結果又賦值給a, 而a可以被賦值, 所以(p1 += p2)也應該可以賦值
所以應該返回p1對象, 而返回對象(把對象作爲返回值)又會導致產生中間對象的問題, 所以應該返回引用.

class Point {
public:
    Point &operator+=(const Point &point) {
        this->m_x += point.m_x;
        this->m_y += point.m_y;
        return *this;
    }
};

(4)==運算符
int a = 10, b = 20;
if (a == b) 原來是相等返回1, 不相等返回0, 所以是bool類型

bool operator==(const Point &point) const {
    // 要保證常量對象可以和非常量對象比較
/*  if ((m_x == point.m_x) && (m_y == point.m_y)) {
        return 1;
    }
    else {
        return 0;
    } */
    return (m_x == point.m_x) && (m_y == point.m_y); 
}

(5)!=運算符

bool operator!=(const Point &point) const {
    return (m_x != point.m_x) || (m_y != point.m_y);
}

(6)-運算符(符號)
首先-p1; 不需要傳參, p1.operator-();
而且
Point p1(10, 20);
Point p3 = -p1; 時p1並沒有改變, 所以應該返回一個臨時的
其次int a = 10, b; (-a) = 10;不允許, 因爲a並沒有被賦值, 返回的是一個臨時的值, 所以-p1不允許被賦值, 所以返回const

const Point operator-() const {
    // 因爲(-(-p1))時相當於 p1.operator-().operator-(), 
    // 而返回的const對象不能調用非const函數, 所以函數也要聲明爲const
    return Point(-m_x, -m_y);
}

(7)++, --運算符
1.爲了區分前置++和後置++規定
void operator++() {
// 是前置++
}
void operator++(int) {
// 是後置++
}
2.前置++可以被賦值, 後置++不可以被賦值
因爲int a = 10;
int b = ++a + 5;
相當於a += 1; int b = a + 5; 也就是說++a是先讓a += 1, 再把最新的a返回.所以可以被賦值.
而後置++ int a = 10;
int c = a++ + 5; 會先將a之前的值放到a++的地方等價於int c = 10 + 5; 在 a += 1; 所以a++的話並不會返回a.
3.最終實現:
前置:

Point &operator++() {
    m_x++;
    m_y++;
    return *this;
}

後置:
而Point p2 = p1++ + Point(30, 40);
應該可以, 所以p1不能返回void而應該返回p1之前的值
所以

const Point operator++(int) {
    Point old(this->m_x, this->m_y);
    this->m_x++;
    this->m_y++;
    return old;
}

(8)<<運算符
1.因爲cout << p1 << endl;
所以<<重載函數不能是成員函數, 一旦是成員函數就必須通過Point對象調用
其實cout也是對象, 它的類是ostream
ostream -> output stream
在頭文件iostream中
2.因爲cout << 1 << 2 << endl;
所以調用完函數後應該返回cout
3.因爲(cout << 1) = cout; 返回cout後不允許被賦值, 所以const
但是返回值是const之後, 會導致cout << p1 << p2 出問題, 所以參數cout也應該const, 但是因爲系統自帶的const的定義是非const的, 所以參數是const會導致原來的const找不到.所以放棄, 但是發現其實不能被賦值, 因爲在iostream頭文件裏, 將=賦值運算符重載到了private導致外面不能訪問=
所以返回值不能是const, cout參數不能是const

ostream &operator<<(ostream &cout, const Point &point) {
    cout << "(" << point.m_x << ", " << point.m_y;
    return cout;
}

(9)cin運算符
input stream -> istream
1.要去掉後面參數的const, 因爲是要輸入東西去改變point對象.

istream &operator>>(istream &cin, Point &point) {
    cin >> point.m_x;
    cin >> point.m_y;
    return cin;
}

運算符重載-調用父類的運算符重載函數

class Person {
    int m_age;
public:
    Person &operator=(const Person &person) {
        this->m_age = person.m_age;
    }
};

class Student : public Person {
    int m_score;
public:
    Student &operator=(const Student &student) {
        Person::operator=(student);
        this->m_score = student.m_score;
    }
};
int main() {
    Student stu;
    stu.m_score = 60;
    stu.m_age = 10;
    Student stu1;
    stu1 = stu;
}

運算符重載-仿函數(函數對象)

//int sum(int a, int b) {
//    return a + b;
//}
class Sum {
    int m_age;
public:
    int operator()(int a, int b) {

        return a + b;
    }
};

int main() {
    Sun sum;
    cout << sum(10, 20) << endl;
    // cout << sum.operator()(10, 20) << endl;
    sum(20, 30);
}

仿函數和普通函數的區別是仿函數是成員函數, 意味着可以訪問類裏面的所有成員變量.

運算符重載注意點

1.有些運算符不可以被重載, 比如
(1)對象成員訪問運算符: .
(2)域運算符: ::
(3)三目運算符: ?:
(4)sizeof
2.有些運算符只能重載爲成員函數, 比如
(1)賦值運算符: =
(2)下標運算符: []
(3)函數運算符: ()
(4)指針訪問成員: ->


其他C++系列文章:

C++知識點總結(基礎語法1-函數重載, 默認參數)
C++知識點總結(基礎語法2-內聯函數, const, 引用)
C++知識點總結(面向對象1-類和對象, this指針, 內存佈局)
C++知識點總結(面向對象2-構造函數, 初始化列表)

C++知識點總結(面向對象3-多態)

C++知識點總結(面向對象4-多繼承, 靜態成員static)
C++知識點總結(面向對象5-const成員, 拷貝構造函數)
C++知識點總結(面向對象6-隱式構造, 友元, 內部類, 局部類)
C++知識點總結(其他語法1-運算符重載)
C++知識點總結(其他語法2-模板, 類型轉換, C++11新特性)

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