Skip to the content.

第21讲 运算符重载

因为惯用 Java,个人很不习惯 C++ 这个特性,且认为运算符重载引入的复杂性远远比带来的方便多,这是一个不必要的特性。学习它,只是为了读懂别人的代码,恐怕自己是不会主动使用它的。
视频地址:

1. 运算符重载的基本概念

运算符连接的表达式,完全可以视同函数的另外一种写法而已,本质上就是函数。类似牛顿和莱布尼兹各自发明了不同的符号系统表达微积分。
运算符重载,会对运算符号赋予新的定义,理解上和函数重载是类似的。运算符重载的本质即函数重载。运算符可以被重载为普通函数或成员函数。
它的定义格式如下:

返回值类型 operator 运算符(形参表) {
    ...
}

1.1 重载为普通函数的例子

class Complex {
 public:
  double real;
  double imaginary;

  Complex(double real = 0.0, double imaginary = 0.0) {
    this->real = real;
    this->imaginary = imaginary;
  }
};

Complex operator+(const Complex &a, const Complex &b) {
  return Complex(a.real + b.real, a.imaginary + b.imaginary);
}

int main() {
  Complex a(1, 2), b(2, 3), c;
  c = a + b;
  return 0;
}

(代码下载, 为了代码规范,源代码将 realimaginary 属性声明为 private,并使用友元重载方式)

1.2 重载为成员函数的例子

运算的目数比前面重载为普通函数时少一个。

a + b 相当于调用 a.operator+(b),具体参下例:

class Complex {
  double real;
  double imaginary;

 public:
  Complex(double real = 0.0, double imaginary = 0.0) {
    this->real = real;
    this->imaginary = imaginary;
  }

  Complex operator-(const Complex &);  // 运算符目数只有一个
};

Complex Complex::operator-(const Complex &a) {
  return Complex(real - a.real, imaginary - a.imaginary);
}

int main() {
  Complex a(1, 2), b(3.4, 4.5), c, d;
  d = a - b;  // 相当于 c = a.operator-(b)
  return 0;
}

(代码下载)

2. 赋值运算符”=”的重载

如果想把不同类型的对象做赋值,比如把 double 型变量赋值给 Complex 对象,把 char * 型字符串赋值给字符串对象等等,就需要重载赋值运算符。 这个符号比较特殊,和前面的双目运算符不一样。赋值运算符只能重载为成员函数。 例子参 sample3.cc

个人实在是讨厌这个特性,不同类型之间的赋值,多写一个构造函数会死么?偏要用这种极不符合认知习惯的方式?!

2.1 深浅复制

对象的复制(copy,拷贝,or clone, 克隆),可以通过赋值运算来完成。但默认的对象是复制,是逐个字节的复制,称为浅复制。 如两个相同类型的对象 a, ba 赋值:a = b,如果 b 中包含指针,那么就会把指针复制给 a, 如果赋值后 b 对象销毁了,b中的指针指向的对象也销毁了。而此时 a 中还有那个指针,却指不到正确的值,成了所谓的野指针。 这时就需要重新定义赋值运算,即重载赋值运算符。
运算符重载时,应尽量保留运算符原本的特性,所以返回值最好是引用类型。
Java 中不会有野指针的问题。Java 中的深浅复制也是一个值得思考的好问题。(TODO)

3. 运算符重载为友元函数

前面讲到,运算符重载的几种情况:

如果运算符重载为普通函数,但又需要使用对象的私有变量时,就可以把重载函数声明为类的友元来实现。当然,不用友元,给类加几个 getter 也是可以的,像 Java 那样。
参这个例子:

这个例子非常好地展示了重载的正确使用方式,但也显示重载真的很难用。

class Complex {
  double real;
  double imaginary;

 public:
  Complex(double real = 0.0, double imaginary = 0.0) {
    this->real = real;
    this->imaginary = imaginary;
  }

  Complex operator+(double real);
  friend Complex operator+(double real,
                           const Complex &c);  // 因为要用到 Complex
                                               // 对象的私有成员,所以也作为
                                               // Complex 的友元

  void print();
};

Complex Complex::operator+(double real) {
  return Complex(this->real + real, imaginary);
}

// 重载为普通函数,以满足 double + Complex 的算式
Complex operator+(double real, const Complex &c) {
  return Complex(real + c.real, c.imaginary);
}

void Complex::print() { ... }

int main() {
  Complex a(1, 2), b, c;
  b = a + 5;  // 用到的成员函数
  b.print();
  c = 5 + a;  // 用到的普通函数,并是 Complex 的友元
  c.print();
  return 0;
}

(代码下载)
特别要注意 b = a + 5c = 5 + a 调用的是不同的运算符重载函数。这里也显示运算符的重载不支持交换律,如果要重载+,就必须写两个重载函数,一个以成员函数出现,一个是全局函数但作为类的友元函数出现,这点让代码显得很丑陋。


<Previous | Home | Next>