C++学习笔记 运算符的重载

运算符重载

运算符重载的基本概念

运算符重载的需求

  • C++预定义的运算符,只能用于基本的数据类型的运算:整形、实型、字符型、逻辑型...
  • 在数学上,两个负数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的
  • 有时会希望,让对象也能通过运算符进行运算, 这样代码更简洁,容易理解
  • 例如:
    • complex_a和complex_b是两个复数对象;
      求两个复数的和,希望能直接写 complex_a + complex_b

运算符重载

  • 运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为
  • 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象
  • 同一个运算符, 对不同的类型的操作数,所发生的行为不同

运算符重载的形式

  • 运算符重载的是指是函数重载
  • 可以重载为普通函数, 也可以重载为成员函数
  • 把含运算符的表达式转换成对运算符函数的调用
  • 把运算符的操作数转换成运算符函数的参数
  • 运算符被多次重载时, 根据实参的类型决定调用哪个运算符函数
    返回值参数 operator 运算符(形参表){
        ....
    }
class Complex {
public:
    double real, imag;
    Complex(double r = 0, double i = 0): real(r), imag(i){}
    Complex operator-(const Complex &c);
};
Complex Complex::operator-(const Complex &c) {
    return Complex(real - c.real, imag - c.imag);               //返回一个临时对象
}

Complex operator+(const Complex &a, const Complex &b) {
    return Complex(a.real + b.real, a.imag + b.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;
}

赋值运算符的重载

有时候希望赋值运算符两边的类型可以不匹配,比如, 把一个int类型变量赋值给一个Complex对象,或把一个char*类型的字符串赋值给一个字符串对象,此时就需要重载运算符“=”
赋值运算符 “=” 只能重载为成员函数

class String {
private:
    char *str;
public:
    String():str(new char[1]){str[0] = 0;}
    const char *get_str(){return str;};
    String &operator = (const char *s);
    ~String(){delete []str;}
};
String &String::operator=(const char *s) {
    delete []str;
    str = new char[strlen(s) + 1];
    strcpy(str, s);
    return *this;
}
int main() {
    String s;
    s = "Hello";
    cout << s.get_str() << endl;
//    String s2 = "world";              //这句话要是不注释掉会报错,初始化句会调用构造函数而不是赋值重载
    s = "world";
    cout << s.get_str() << endl;
    return 0;
}

浅拷贝和深拷贝

若有这样的一种情况

String s1, s2;
s1 =  "this";
s2 = "that";
s1 =  s2;

  • 如不定义自己的赋值运算符, 那么s1, s2实际上导致s1.str和s2.str指向同一地方
  • 如果s1对象消亡, 析构函数将释放s1.str指向的空间, 然而s2消亡还要再释放一次, 不妥(程序崩溃)
  • 若s1 = “other”,则s2指向的空间也会消亡
    解决方法:新添加一个赋值运算符的重载
String & operator = (const string  & s){
      if(this == &s) return *this;      //防止s = s这种情况的出现导致程序崩溃
      delete [] str;
      str = new char[strlen(s.str) + 1];
      strcpy(str, s.str);
      return *this;
}

对 operator = 返回值类型的讨论

  • 为什么是String &?
    运算符进行重载的时候, 好的风格应该是尽量保留运算符原本的特性
    (考虑 a = b = c 和 (a = b) = c)
  • 上述的String类是否就没有问题了?
    为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);
};
Complex Complex::operator+ (double r) {
      return Complex(real + r, imag);      //能解释c + 5
}

经过上述重载后

Complex c;
c = c + 5;      //有定义, 相当于c = c.operator + (5);      

但是

       c = 5 + c;       //编译出错

所以, 为了使得上述的表达式能成立, 需要将 + 重载为普通函数

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

类型转换运算符重载

#include <iostream>
using namespace std;
class Complex{
      doubel real, imag;
public;
      Complex(double r = 0, double i = 0):real(r), imag(i){};
      operator double(){return real;}            //重载强制类型转换运算符double
}
int main(){
      Complex c(1.2, 3.4);
      cout << (double)c << endl;                 //输出1.2
      double n = 2 + c;                          //等价于 double n = 2 + c.operator double();
      cout << n;                                 //输出3.2 
}
  • 必须重载后才可使用

自增, 自减运算符的重载

  • 自增运算符、自减运算符--有前置/后置之分, 为了区分所重载的是前置运算符还是后置运算符, 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);
  • 但是在没有后置运算符重载而有前置运算符重载的情况下, 在vs中, obj++ 也调用前置重载, 而dev则令 obj++ 编译出错
    未完待续...
posted @ 2020-12-08 20:51  上帝的绵羊  阅读(122)  评论(0编辑  收藏  举报