Table of Contents:

运算符重载(Operator Overloading):同一个运算符可以有不同的功能

 C++运算符重载基础教程

运算符重载使得程序的书写更加人性化,易于阅读

 类中的运算符重载

比如:虚数的相加

complex operator+(const complex &A) const;
complex complex::operator+(const complex &A)const{
    return complex(this->m_real + A.m_real, this->m_imag + A.m_imag);
}
//调用:c3 = c1 + c2

 在全局范围内重载运算符

注意:要在类中声明为友元函数

class complex{
public:
    complex();
    complex(double real, double imag);
public:
    void display() const;
    //声明为友元函数
    friend complex operator+(const complex &A, const complex &B);
private:
    double m_real;
    double m_imag;
};
complex operator+(const complex &A, const complex &B);
//当执行c3 = c1 + c2;语句时,编译器检测到+号两边都是 complex 对象,就会转换为类似下面的函数调用:
//c3 = operator+(c1, c2);

 C++运算符重载时要遵循的规则

1. 并不是所有的运算符都可以重载
长度运算符sizeof、条件运算符: ?、成员选择符.和域解析运算符::不能被重载。
2. 重载不能改变运算符的优先级和结合性
3. 重载不会改变运算符的用法,原有有几个操作数、操作数在左边还是在右边,这些都不会改变。例如~号右边只有一个操作数,+号总是出现在两个操作数之间,重载后也必须如此
4. 运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的。
5. 运算符重载函数既可以作为类的成员函数,也可以作为全局函数。
将运算符重载函数作为类的成员函数时,二元运算符的参数只有一个,一元运算符不需要参数。之所以少一个参数,是因为这个参数是隐含的。
6. 将运算符重载函数作为全局函数时,一般都需要在类中将该函数声明为友元函数。原因很简单,该函数大部分情况下都需要使用类的 private 成员。
7. 箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载
8. 将运算符重载函数作为全局函数时,二元操作符就需要两个参数,一元操作符需要一个参数,而且其中必须有一个参数是对象,好让编译器区分这是程序员自定义的运算符,防止程序员修改用于内置类型的运算符的性质。

//绝对禁止的
int operator + (int a,int b){
    return (a-b);
}

 C++重载数学运算符

四则运算符(+、-、*、/、+=、-=、*=、/=)和关系运算符(>、<、<=、>=、==、!=)都是数学运算符,它们在实际开发中非常常见,被重载的几率也很高,并且有着相似的重载格式。
代码:省略
我们以全局函数的形式重载了 +、-、*、/、==、!=,以成员函数的形式重载了 +=、-=、*=、/=,而且应该坚持这样做,不能一股脑都写作成员函数或者全局函数

 到底以成员函数还是全局函数(友元函数)的形式重载运算符

 简单地了解转换构造函数

编译器在检测到 Complex 和 double(小数默认为 double 类型)相加时,会先尝试将 double 转换为 Complex,或者反过来将 Complex 转换为 double(只有类型相同的数据才能进行 + 运算),
如果都转换失败,或者都转换成功(产生了二义性),才报错。
本例中,编译器会先通过构造函数Complex(double real);将 double 转换为 Complex,再调用重载过的 + 进行计算,整个过程类似于下面的形式:Complex(double real);
在作为普通构造函数的同时,还能将 double 类型转换为 Complex 类型,集合了“构造函数”和“类型转换”的功能,所以被称为「转换构造函数」。换句话说,转换构造函数用来将其它类型(可以是 bool、int、double 等基本类型,也可以是数组、指针、结构体、类等构造类型)转换为当前类类型。

 为什么要以全局函数的形式重载 +

我们定义的operator+是一个全局函数(一个友元函数),而不是成员函数,这样做是为了保证 + 运算符的操作数(符号两边的运算符不相同时)能够被对称的处理;换句话说,小数(double 类型)在 + 左边和右边都是正确的,而不是调换个位置表现就不一样了。
类的成员函数不能对称地处理数据,程序员必须在(参与运算的)所有类型的内部都重载当前的运算符。这样做不但会增加运算符重载的数目,还要在许多地方修改代码,这显然不是我们所希望的
C++ 只会对成员函数的参数进行类型转换,而不会对调用成员函数的对象进行类型转换

 为什么要以成员函数的形式重载 +=

我们首先要明白,运算符重载的初衷是给类添加新的功能,方便类的运算,它作为类的成员函数是理所应当的,是首选的
采用全局函数能使我们定义这样的运算符,它们的参数具有逻辑的对称性。与此相对应的,把运算符定义为成员函数能够保证在调用时对第一个(最左的)运算对象不出现类型转换,也就是上面提到的「C++ 不会对调用成员函数的对象进行类型转换」。因为调用+=时本身就确定左侧的为当前对象

 C++重载>>和<<(输入和输出运算符)详解

在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载,使其能够用于不同数据的输入输出,但是输入输出的对象只能是 C++ 内置的数据类型(例如 bool、int、double 等)和标准库所包含的类类型(例如 string、complex、ofstream、ifstream 等)。
cout 是 ostream 类的对象,cin 是 istream 类的对象,要想达到这个目标,就必须以全局函数(友元函数)的形式重载<<>>,否则就要修改标准库中的类,这显然不是我们所期望的。

istream & operator>>(istream &in, complex &A){
    in >> A.m_real >> A.m_imag;
    return in;
}

类中声明友员

friend istream & operator>>(istream & in , complex &a);      //加上friend声明

 C++重载[](下标运算符)

必须以成员函数的形式进行重载。该重载函数在类中的声明格式如下:

返回值类型 & operator[ ] (参数);

或者:

const 返回值类型 & operator[ ] (参数) const;

使用第一种声明方式,[ ]不仅可以访问元素,还可以修改元素。使用第二种声明方式,[ ]只能访问而不能修改元素。在实际开发中,我们应该同时提供以上两种形式,这样做是为了适应 const 对象,因为通过 const 对象只能调用 const 成员函数,如果不提供第二种形式,那么将无法访问 const 对象的任何元素。
对于常对象编译器不管实际上有没有修改对象,只要是调用了非 const 的成员函数,编译器就认为会修改对象(至少有这种风险)。

int& Array::operator[](int i){
    return m_p[i];
}

 C++重载++和--(自增和自减运算符)详解

自增++和自减--都是一元运算符,它的前置形式和后置形式都可以被重载。.
由于编译器必须能够识别出前缀自增与后缀自增,人为规定用 operator++() 和 operator–() 重载前置运算符,用 operator++(int) 和 operator--(int) 重载后置运算符,在这里的 int 并没有什么实际的意义,仅仅是为了区分重载的是前置的形式还是后置的形式。

stopwatch stopwatch::run(){
    ++m_sec;
    if(m_sec == 60){
        m_min++;
        m_sec = 0;
    }
    return *this;
}

//++i,前置形式,先加加,后返回对象
stopwatch stopwatch::operator++(){
    return run();
}
//i++,后置形式,原始对应要进行一次复制,然后再进行++,最后再返回复制的对象
//在这个函数中参数n是没有任何意义的,它的存在只是为了区分是前置形式还是后置形式。
stopwatch stopwatch::operator++(int n){
    stopwatch s = *this;
    run();
    return s;
}

 C++重载new和delete运算符详解

内存管理运算符 new、new[]、delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数。一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载

 C++重载()(强制类型转换运算符)

类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。

#include <iostream>
using namespace std;
class Complex
{
    double 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
}

有了对 double 运算符的重载,在本该出现 double 类型的变量或常量的地方,如果出现了一个 Complex 类型的对象,那么该对象的 operator double 成员函数就会被调用,然后取其返回值使用。