跳转至

Operator & Cast

答应我不要再考四种 cast 的区别了好吗?好的。

operator

在 C++ 中,我们可以让自定义类型也能够使用运算符。实际上你可能已经使用过类似的机制,例如 std::string+ 运算符。

1
2
3
4
int main() {
  std::string s1 = "Hello, ";
  std::cout << s1 + "world!\n";
}

注意到我们可以使用 s1 + "world!\n" 这样的语法,这就是运算符重载operator overloading的效果。

运算符重载并不一定与重载相关

前者的“重载”表示我们能将 C++ 的内置运算符(如 +- 等)应用在自定义类型上,而后者的“重载”表示同一函数名可以有不同的实现。当然——运算符重载也可以被重载。

接下来我们来关注如何为自定义类型实现运算符重载。考虑下面的复数类:

1
2
3
4
5
struct complex {
  const double real;
  const double imag;
  complex(double r, double i) : real {r}, imag {i} {}
};

通常有两种做法。第一种将运算符重载作为全局函数来实现:

1
2
3
complex operator+(const complex &lhs, const complex &rhs) {
  return complex {lhs.real + rhs.real, lhs.imag + rhs.imag};
}

对于这种方式,运算符的操作数都需要作为参数传入。如果我们有两个 complex 对象 wz,那么 w + z 会被编译器视作 operator+(w, z) 的函数调用。因此它也遵循函数调用的查找规则(例如在 namespace 中)。作为外部函数,它无法访问类的私有成员变量,但可以通过 friend 声明来实现。

第二种是将运算符重载作为类的成员函数来实现:

1
2
3
4
5
6
7
8
struct complex {
  const double real;
  const double imag;
  complex(double r, double i) : real {r}, imag {i} {}
  complex operator+(const complex &other) const {
    return complex {real + other.real, imag + other.imag};
  }
};

对于这种方式,运算符的第一个操作数就是当前对象,第二个操作数(若有)就是参数 otherw + z 会被编译器视作 w.operator+(z) 的成员函数调用。因此,它也受到访问控制的影响。这种方式的优点是可以直接访问类的成员变量,但它仍然会有一些限制…

考虑交换律。我们现在为 complex 类实现与 double 类型的加法:

struct complex {
  const double real;
  const double imag;
  complex(double r, double i) : real {r}, imag {i} {}
  complex operator+(const complex &other) const {
    return complex {real + other.real, imag + other.imag};
  }
  complex operator+(double d) const {
    return complex {real + d, imag};
  }
};

int main() {
  complex w {1, 2};
  w + 3; // OK: w.operator+(3)
  3 + w; // Error: ?
};

这种情况下,3 + w 就无法通过编译了。我们必须单独为其实现一个全局 operator+ 函数:

1
2
3
4
5
complex operator+(double d, const complex &c) {
  return complex {d + c.real, c.imag};
  // or
  return c + d; // OK: c.operator+(d)
}

实际上 std::string+ 运算符也是这样实现的。

基本上任何现有的运算符都可以被重载,诸如加减乘除、位运算、逻辑运算、关系运算等,甚至 newdelete、解引用运算符 * 和取址运算符 & 等等都可以被重载。不能被重载的运算符有:

  • .(成员访问)
  • .*(成员指针访问)
  • ::(作用域解析)
  • ?:(三元运算符)

以及一些关键字,如 sizeoftypeid 等。不能通过 operator 定义新的运算符。

TBD

自定义后缀运算符

explicit

类型转换

static_cast

dynamic_cast

const_cast

reinterpret_cast

评论