运算符重载(学习笔记)

简介

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能被重载||ヽ( ̄▽ ̄)ノミ|Ю

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