运算符重载


持续更新,补充C++新增feature,目录结构遵循《C++ Primer》

  • 普通的运算符只能用于基本数据类型
  • 对抽象的数据类型也能使用C++提供的数据类型
    • 代码更简洁
    • 代码更容易理解
  • 运算符重载的实质是函数重载,形式为:
返回值类型 operator 运算符(形参表){}
  • 在程序编译时:
    • 把运算符的表达式 -> 对运算符函数的调用
    • 把运算符的操作数 -> 运算符函数的参数
    • 运算符多次被重载时,根据实参类型决定调用哪个运算符函数
  • 运算符可以被重载成普通函数
    • 参数个数为运算符的目数(如+为二元运算符,因此参数个数为2)
      class Complex
      {
          public:
              Complex(double r = 0.0, double i = 0.0)
              {
                  real = r;
                  image = i;
              }		
          double real;
          double image;
      };
      //普通的全局函数
      Complex operator+ (const Complex& a, const Complex& b){
          return Complex(a.real+b.real, a.image+b.image);
      }
    
  • 也可以被重载成类的成员函数
    • 参数个数为运算符目数减一
      class Complex
      {
          public:
              Complex(double r = 0.0, double i = 0.0)
              {
                  real = r;
                  image = i;
              }		
              Complex operator+ (const Complex& );
              Complex operator- (const Complex& );
          private:
              double real;
              double image;
      };
      Complex Complex::operator+(const Complex& op)
      {
          return Complex(real+op.real,image+op.image);
      }
      int main()
      {
          Complex x, y(4.3,2.6), z(3.3,1.1);
          x = y+z; //=> x = y.operator+(z)
          return 0;
      }
    
  • 重载<<

C++中的cout<<使用的也是运算符重载,coutostream类的对象,ostream重载了<<:

ostream& ostream::operator<<(int n){
	return *this;
}
//cout<<3<<"this";`等价于:
//cout.operator<<(3).operator<<("this");

也可以重载<<进行自定义输出

class Person{
private:
	string name;

friend ostream& operator<<(ostream& out, const Person& p);
}
ostream& operator<<(ostream& out, const Person& p){
	out<<p.name;
	return out;
}
int main(){
	Person p;
	cout<<p;
}

赋值运算符重载

  • 赋值运算符两边类型可以不匹配
  • 赋值运算符=只能重载为成员函数
class string{
	private:
		char* p;
		
	public:
		string():p(NULL){}
		const char* c_str(){ return p; }
		
		char* operator=(const char* s){
			if(p){
				delete[] p;
			}
			if(s){
				int len = strlen(s);
				p = new char[len+1];
				strcpy(p,s);
			}
			else{
				p = NULL;
			}
			return p;
		}
};

int main(){
	string x1 = "abc"; //通过重载运算符实现
}

  • 深浅拷贝
    • 发生在两个对象互相赋值的过程中
    • 浅拷贝的问题:如果成员变量有指针对象,那么浅拷贝会导致被复制的对象和原对象的指针成员变量值相同,即他们指向同一块内存区域,当对象析构时,会有double free的风险
string& operator=(string& s){

	if(s.c_str() == p){ //自己赋值给自己
		return *this;
	}

	if(p){
		delete[] p;
	}
	p = new char[strlen(s.c_str()+1)];
	strcpy(p,s.c_str());
	return *this;
}


int main(){
	string x1="abc";
	string x2;
	x2 = x1; //通过重载运算符实现
}	
  • 返回值不能设计成void,会有a=b=c的情况
    • 等价于a.operator=(b.operator=(c))
  • 返回值要设计成引用类型
    • 运算结果最终还是作用于自身,因此返回值用引用

运算符重载为友元函数

  • 成员函数不能满足使用要求
  • 普通函数,又不能访问类的私有成员

自加/自减运算符重载

  • 自加++, 自减--运算符有前置/后置之分
  • 前置运算符为一元运算符重载, 返回左值
    • 重载为成员函数
      //重载为成员函数
      T operator++();
      T operator--();
    
    • 重载为全局函数 ​ cpp //重载为全局函数: T operator++(T); T operator--(T); `
    • ++obj, obj.operator++(), operator++(obj)都调用上述函数
  • 后置运算符作为二元运算符重载
    • 多写一个参数,具体无意义,返回右值
    • 重载为成员函数
      T operator++(int); //多写一个参数告诉编译器是后置运算,初始化为0
      T operator--(int);
    
    • 重载为全局函数
      T operator++(T, int);
      T operator--(T, int); //第二个参数没有特殊意义, 默认初始化为0
    
    • obj++, obj.operator++(0), operator++(obj, 0) 都调用上述函数
  • CDemo例子
class CDemo {
private :
	int n;
public:
	CDemo(int i=0):n(i) { }
	CDemo & operator++(); //用于前置++形式
	CDemo operator++(int); //用于后置++形式
	operator int ( ) { 
		return n; 
	}
	friend CDemo & operator--(CDemo &); //友元全局函数,用于前置--形式
	friend CDemo operator--(CDemo &, int); //友元全局函数,用于后置--形式
};
CDemo & CDemo::operator++() { //前置 ++
	n++;
	return * this;
}
CDemo CDemo::operator++(int k) { //后置 ++
	CDemo tmp(*this); //记录修改前的对象
	n++;
	return tmp; //返回修改前的对象
}
CDemo & operator--(CDemo & d) { //前置--
	d.n--;
	return d;
}
CDemo operator--(CDemo & d, int) { //后置--
	CDemo tmp(d);
	d.n -;
	return tmp;
} 

int main(){
	CDemo d(5);
	cout << (d++) << ","; //等价于 d.operator++(0);
	cout << d << ",";
	cout << (++d) << ","; //等价于 d.operator++();
	cout << d << endl;
	cout << (d--) << ","; //等价于 operator--(d,0);
	cout << d << ",";
	cout << (--d) << ","; //等价于 operator--(d);
	cout << d << endl;
	return 0;
} 

类型强制转换运算符重载

  • 有时需要对某个对象进行强制类型转换,转换规则也可以重载,例如:
operator int(){return n;}

int作为一个类型强制转换运算符被重载,Demo s; (int)s,等价于s.int()

  • 类型强制装换运算符重载时
    • 不能写返回值类型
    • 实际上其返回值类型为强制转换后的类型
class Number
{
  public:
    int num;
    Number(int n = 0) : num(n) {}
    int operator*(const Number &n){
        return num * n.num;
    }
    operator int(){
        return num;
   }
}; 

int main()
{
    Number n1(10), n2(20);
    Number n3;
    n3 = n1 * n2;
    cout << int(n3) << endl; //类型转换
    return 0;
}

函数对象(functor)

  • 定义:若一个类重载运算符(),则该类对象就称为函数对象
  • 头文件:<functional>
class A{
public:
	double operator()(int a1, int a2, int a3){
		return (double)(a1+a2+a3)/3;
	}
};
A average; //函数对象
cout<<average(2,3,4); //averate.operator()(3,2,4)
  • STL中的函数对象模板
    • equal_to
    • greater
    • less
  • greater函数对象模板:
template<class T>
struct greater:public binary_function<T,T,bool>{
	bool operator()(const T& x, const T& y)const{
		return x>y;
	}
}

运算符重载的注意事项

  • C++不允许定义新的运算符
  • 以下运算符不能被重载
    • . , .*, ::,?:,sizeof
  • 重载运算符(), [ ], ->或者赋值运算符=时, 重载函数必须声明为类的成员函数

Resources