程序设计实习(2) —— 运算符重载

对象间的运算和结构变量一样, 对象之间可以用 = 进行赋值, 但是不能用 ==, !=, >, <, >=, <= 进行比较, 除非这些运算符经过了 “重载”. 运算符重载的实质是函数重载.

重载为普通函数和成员函数均可. 重载为成员函数时, 重载函数的参数个数比运算符的操作数少一个.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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, a.imag + b.imag);
}
Complex Complex::operator-(const Complex &c) {
    return Complex(real - c.real, imag - c.imag);
};

重载 =

有时候希望赋值运算符两边的类型可以不匹配, 比如把一个 char * 类型的字符串赋值给一个字符串对象, 此时就需要重载赋值运算符 =. 赋值运算符 = 只能重载为成员函数, 通常返回类型为 *this 的引用.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class String {
    char *str;
public:
    String() : str(new char[1]) { str[0] = 0; }
    const char *c_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 = "Good Luck,"; // s.operator=("Good Luck,");
    cout << s.c_str() << endl;
    // String s2 = "hello!"; // 这是构造函数, 不是赋值
    return 0;
}

如不定义自己的赋值运算符, 那么 S1 = S2 实际上导致 S1.strS2.str 指向同一地方. 为此要做深拷贝.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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
}
String(const String &s) {
    // 复制构造函数也要深拷贝
    str = new char[strlen(s.str) + 1];
    strcpy(str, s.str);
}

也可以重载为友元函数.

1
2
3
4
5
6
7
8
9
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 = a + 2.5 的问题, 但是不能解决 c = 2.5 + a 的问题. 为此要重载为普通函数, 但普通函数又不能访问私有成员, 用友元函数.

1
2
3
4
5
6
7
8
Complex operator+(double r, const Complex &c);
class Complex {
    // --skip--
    friend Complex operator+(double r, const Complex &c);
};
Complex operator+(double r, const Complex &c) {
    return Complex(c.real + r, c.imag);
}

重载流插入运算符 << 和流提取运算符 >>

cout 实际上是一个在 iostream 中定义的, ostream 类的对象.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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"; // 以"a+bi" 的形式输出
    return os;
}
istream &operator>>(istream &is, Complex &c) {
    string s;
    is >> s; // 将 "a+bi" 作为字符串读入
    int pos = s.find("+", 0);
    string sTmp = s.substr(0, pos); // 分离出代表实部的字符串
    c.real = atof(sTmp.c_str());
    // atof 库函数能将 const char* 指针指向的内容转换成 float
    sTmp = s.substr(pos + 1, s.length() - pos - 2); // 分离出代表虚部的字符串
    c.imag = atof(sTmp.c_str());
    return is;
}

重载类型转换运算符

没有返回值, 因为转换函数的返回值类型就是要转换的类型.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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; // 输出 1.2
    double n = 2 + c; // double n = 2 + c.operator double()
    cout << n; // 输出 3.2
    return 0;
}

重载自增自减运算符 ++--

自增运算符 ++, 自减运算符 -- 有前置/后置之分, 为了区分所重载的是前置运算符还是后置运算符, C++ 规定前置运算符作为一元运算符重载, 后置运算符作为二元运算符重载, 多写一个没用的参数.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 前置, 先加减再返回, 返回引用
// 重载为成员函数
T& operator++();
T& operator--();
// 重载为全局函数
T& operator++(T&);
T& operator--(T&)

// 后置, 先返回再加减, 返回值
// 重载为成员函数
T operator++(int);
T operator--(int);
// 重载为全局函数
T operator++(T&, int);
T operator--(T&, int);

重载 ->

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class A {
private:
    int x;
public:
    A() : x(5) {}
    int getX() {
        return x;
    }
    A *operator->() {
        return this;
    }
};

int main() {
    A a;
    cout << a->getX() << endl; // a.operator->()->getX()
    return 0;
}

看起来似乎没有什么用, 但是可以用来实现智能指针.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class client {
public:
    int a;
    client(int x) : a(x) {}
};

class proxy {
    client *target;
public:
    proxy(client *t) : target(t) {}
    client *operator->() const { return target; }
};

class proxy2 {
    proxy *target;
public:
    proxy2(proxy *t) : target(t) {}
    proxy &operator->() const { return *target; }
};

int main() {
    client x(3);
    proxy y(&x);
    proxy2 z(&y);
    cout << x.a << y->a << z->a; // print "333"
    return 0;
}

注意事项

  • 运算符重载不改变运算符的优先级.

  • 以下运算符不能重载: ., .*(成员函数指针), ::, ?:(三目运算符), sizeof.

  • 重载运算符 (), [], -> 或者赋值运算符 = 时,运算符重载函数必须声明为类的成员函数.

  • 重载运算符是为了让它能作用于对象, 因此重载运算符不允许操作数都不是对象 (有一个是枚举类型也可以).

    1
    
    void operator+(int a, char* b); // 错误
    
本文遵循 CC BY-NC-SA 4.0 协议
使用 Hugo 构建