運算符重載(學習筆記)

簡介

C++預定義的運算符,只能用於基本數據結構類型的運算:整型、實型、字符型、邏輯型。

定義

運算符重載,就是對已有的運算符(C++中預定義的運算符)賦予多重含義,使同一運算符作用於不同類型的數據時導致不同的行爲

目的

擴展C++中提供的運算符的適用範圍,使之能作用於對象

形式

運算符重載的實質是函數重載
可以重載爲普通函數,也可以重載爲成員函數
把含運算符的表達式轉換成對運算符函數的調用
吧運算符的操作數轉換爲函數的參數
運算符被多次重載時,根據實參的類型決定調用哪個運算符函數

返回值類型 operator 運算符(形參表) {
}

重載爲普通函數時,參數個數爲運算符目數減一
重載爲成員函數時,參數個數爲運算符目數

記住是目數不是數目啦

class Complex {
public:
    double real, imag;
    Complex(double r = 0.0, double i = 0.0):real(r), imag(i) {}
    Complex operator-(const Complex &c);
};
Complex operator+(const Complex &a, const Complex &b) {
    return Complex(a.real+b.real, b.imag);
}
Complex Complex::operator-(const Complex &c) {
    return Complex(real-c.real, imag-c.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-b等價於a.operator-(b)
    return 0;
}

賦值運算符重載(‘=’)

重載爲成員函數

賦值運算符只能重載爲成員函數

class String {
private:
    char *str;
public:
    String():str(new char[1]) { str[0] = 0; }
    const char *c_str() { return str; }
    String& operator=(const char *s) {
        delete [] str;
        str = new char[strlen(s)+1];
        strcpy(str, s);
        return *this;
    }
    ~String() { delete[] str; }
};
int main() {
    String s1, s2;
    s1 = "s1";
    s2 = "s2";
    s1 = s2;
    cout << s1.c_str() << "," << s2.c_str() << endl;
    return 0;
}

如果不定義自己的賦值運算符,那麼s1=s2實際上導致s1.str和s2.str指向同一片內存空間
如果s1對象消亡,析構函數將釋放s1.str指向的空間,則s2消亡時還要釋放一次
如果執行s1=“s_”,會導致s2.str指向的地方被delete
因此要在類String裏添加成員函數


String& operator=(const String &s) {
    delete []str;
    str = new char[strlen(s.str)+1];
    strcpy(str, s.str);
    return *this;
}

考慮 s=s; 這條語句

String &operator=(const String &s) {
    if (this == &s) return *this;
    delete []str;
    str = new char[strlen(s.str)+1];
    strcpy(str, s.str);
    return *this;
}

對運算符進行重載的時候,好的風格是應該儘量保留運算符原本的性質

返回值類型

考慮:
a = b = c; ——> a.operator=(b.operator=( c ));
(a = b) = c; ——> (a.operator=(b)).operator=( c )

but!!! 考慮String類的複製構造函數,同樣會面臨和 = 同樣的問題,用同樣的方法處理

String (String &s) {
    str = new char[strlen(s.str)+1];
    strcpy(str, s.str);
}

運算符重載爲友元函數

一般情況下,將運算符重載爲類的成員函數,是較好的選擇
有時,重載爲成員函數不能滿足使用要求;重載爲普通函數,不能訪問類的私有成員——>就需要將運算符重載爲友元

class Complex {
    double real, imag;
public:
    Complex(double r, double i):real(r), imag(i) {}
    Complex operator+(double r){
        return Complex(real+r, imag);
    }
};

經上述重載後

Complex c;
c = c+5; // 有定義,相當於c=c.operator+(5)
c = 5+c; // 編譯出錯

爲了使上述表達式能成立,需要將+重載爲普通函數
改爲:

Complex operator+(double r, const Complex &c) {
    return Complex(c.real+r, c.imag);
}

就能解釋嘚通5+c啦
但是普通函數又不能訪問私有成員,需要將運算符重載爲友元

class Complex {
    double real, imag;
public:
    Complex(double r, double i):real(r), imag(i) {}
    Complex operator+(double r){
        return Complex(real+r, imag);
    }
    friend Complex operator+(double r, const Complex &c) {
        return Complex(c.real+r, c.imag);
    }
};

實例

可變長整型數組

#include <iostream>
#include <cstring>

using namespace std;

class CArray {
    int size;
    int *ptr;
public:
    CArray(int len = 0); // 參數爲數組元素個數
    CArray(CArray &a);
    ~CArray();
    void push_back(int v);
    CArray& operator=(const CArray &a); // 對象間賦值
    int length() { return size; }
    // 用以支持根據下標訪問數組元素個數,如n=a[1];a[2]=5;
    int& operator[](int i) { return ptr[i]; }
};
int main() {
    CArray a;
    // 要用動態分配的內存來存放數組元素,需要一個指針成員變量
    for (int i = 0; i < 5; ++ i) a.push_back(i);
    CArray a2, a3;
    a2 = a; // 要重載'=',"[]"
    for (int i = 0; i < a.length(); ++ i) cout << a2[i] << " ";
    cout << endl;
    a2 = a3; // a2,a3都是空的,a2.length()返回0
    for (int i = 0; i < a2.length(); ++ i) cout << a2[i] << " ";
    a[3] = 100;
    CArray a4(a); // 要自己寫複製構造函數
    for (int i = 0; i < a4.length(); ++ i) cout << a4[i] << " ";
    return 0;
}
CArray::CArray(int len):size(len) {
    ptr = len == 0 ? nullptr : new int[len];
}
CArray::CArray(CArray &a) {
    if (a.ptr == nullptr) {
        ptr = nullptr;
        size = 0;
        return ;
    }
    ptr = new int[a.size];
    // 記住要指向不同的內存空間
    memcpy(ptr, a.ptr, sizeof(int)*a.size);
    size = a.size;
}
CArray::~CArray() {
    if (ptr) delete [] ptr;
}
// 是賦值號左邊對象存放的數組大小內容都和右邊對象一樣
CArray& CArray::operator=(const CArray &a) {
    // 防止a=a這樣的賦值出錯
    if (ptr == a.ptr) return *this;
    // 如果a裏面的數組是空的
    if (a.ptr == nullptr) {
        if (ptr) delete [] ptr;
        ptr = nullptr;
        size = 0;
        return *this;
    }
    // 如果原有空間足夠大,就不用分配新的空間
    if (size < a.size) {
        if (ptr) delete []ptr;
        ptr = new int[a.size];
    }
    memcpy(ptr, a.ptr, sizeof(int)*a.size);
    size = a.size;
    return *this;
}
// 在尾部添加數組
void CArray::push_back(int v) {
    if (ptr) {
        int *tmp = new int[size+1];
        memcpy(tmp, ptr, sizeof(int)*size);
        delete []ptr;
        ptr = tmp;
    }
    else ptr = new int[1];
    ptr[size++] = v;
}

流插入運算符重載

cout/cin是在iotream中定義的:
cout是ostream的對象
cin是istream的對象
<</>> 這倆運算符能用在 cout/cin 上是因爲在iostream裏對兩個運算符進行了重載
流插入流提取運算符本質上是左移右移運算符,只不過是被重載了

流插入運算符的重載

ostream& ostream::operator<<(int n) {
    ...
    return *this;
}
ostream& ostream::operator<<(const char *s) {
    ...
    return *this;
}

因爲istream和ostream都已經寫好了,所以不能>>和<<重載成成員函數,只能重載成全局函數,要聲明爲友元函數

#include <bits/stdc++.h>

using namespace std;

class Complex {
    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);
    string tmp = s.substr(0, pos);
    c.real = atof(tmp.c_str());
    tmp = s.substr(pos+1, s.length()-pos-2);
    c.imag = atof(tmp.c_str());
    return is;
}
int main() {
    Complex c;
    int n;
    cin >> c >> n;
    cout << c << ' ' << n;
    return 0;
}

類型轉換運算符

類型強制轉換運算符被重載時不能寫返回值類型,實際上其返回值類型就是該類型強制轉換運算符代表的類型
重載的類型轉換運算符可以顯示的轉換也可以自動轉換,如:

#include <bits/stdc++.h>

using namespace std;

class Complex {
    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.4);
    cout << (double)c << endl;
    double n = c;
    cout << n;
    return 0;
}

上述兩行是輸出一樣嗒

自增,自減運算符的重載

由於自增自減有前置後置之分,爲了區分所重載的是前置運算符還是後置運算符,C++規定:
前置運算符作爲一元運算符重載

重載爲成員函數:
T & operator++();
T & operator–();
重載爲全局函數:
T1 & operator++(T2);
T1 & operator–(T2);

後置運算符作爲二元函數重載,多寫一個沒用的參數

重載爲成員函數:
T operator++(int);
T operator–(int);
重載爲全局函數:
T1 operator++(T2, int);
T1 operator–(T2, int);

在C++裏面後置運算符並沒有返回對操作數的引用
前置++和前置–的效率優於後置(整型變量沒啥差別)
但是在沒有後置運算符重載而有前置重載的情況下:在VS下,obj++也調用前置重載,而dev則令obj++編譯出錯

#include <bits/stdc++.h>

using namespace std;

class CDemo {
private:
    int n;
public:
    CDemo(int i = 0):n(i) {}
    CDemo& operator++(){ // 前置++
        n ++;
        return *this;
    } // ++tmp即爲tmp.operator++();
    CDemo operator++(int k) { // 後置++
        CDemo tmp(*this);
        n ++;
        return tmp; // 返回修改前的對象
    } // tmp++即爲tmp.operator++(0);
    // int作爲一個類型強制轉換運算符被重載
    operator int () { return n; }
    friend CDemo& operator--(CDemo& d) { // 前置--
        d.n--;
        return d;
    } // --tmp即爲operator--(tmp);
    friend CDemo operator--(CDemo& d, int) { // 後置--
        CDemo tmp(d);
        d.n--;
        return tmp;
    } // tmp--即爲operator--(tmp, 0);
};
int main() {
    CDemo d(5);
    cout << (d++) << ",";
    cout << d << ",";
    cout << (++d) << ",";
    cout << d << ",";
    cout << endl;
    cout << (d--) << ",";
    cout << d << ",";
    cout << (--d) << ",";
    cout << d;
    return 0;
}

注意事項

1)C++不允許定義新的運算符;
2)重載後運算符的含義應該符合日常習慣;
3)運算符重載不改變運算符的優先級;
4)以下運算符不能被重載:

. .* :: ?: sizeof

5)重載運算符 () / [] / -> / = 時,運算符重載函數必須聲明爲類的成員函數

new和delete能被重載||ヽ( ̄▽ ̄)ノミ|Ю

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