运算符重载
基本规则
可以重载的运算符:
不可重载的运算符:
//返回类型 operator后面加运算符(参数列表)
//eg. Integer operator+(Integer l, Integer r);class Integer{
public:Integer(int n = 0) : i(n) {}const Integer operator+(const Integer& v){ //在类中定义运算符重载函数return Integer(i+v.i);}const void print_i(){ cout << i << endl; }
private:int i;
};int main()
{Integer x(10),y(20);Integer z = x + y; //相当于 x.operator+(y)z.print_i(); //打印结果z = x + 3; //ok,打印出13z.print_i(); z = 3 + 7; //ok,将10传给构造函数创建一个Integer对象z.print_i();z = 3 + y; //error,3不是Integer对象,没有实现运算符重载,会报错z.print_i(); return 0;
}
由上面的例子可以看到,z = 3 + y会报错,因为3不是Integer对象,双目运算符重载调用的是运算符左边的对象的运算符重载函数。可将成员函数修改为全局函数(在类中将该函数添加friend关键字)。
class Integer{
public:Integer(int n = 0) : i(n) {}friend const Integer operator+(const Integer& l, const Integer& r);const void print_i(){ cout << i << endl; }
private:int i;
};const Integer operator+(const Integer& l, const Integer& r){ //全局函数return Integer(l.i + r.i);
}int main()
{Integer x(10),y(20);Integer z = x + y; //相当于 x.operator+(y)z.print_i(); //打印结果z = x + 3; //ok,打印出13z.print_i(); z = 3 + 7; //ok,将10传给构造函数创建一个Integer对象z.print_i();z = 3 + y; //ok,会调用全局函数 z.print_i(); return 0;
}
是否将运算符重载设置为成员函数的基本规则:
1、单目运算符应该被设置为成员函数
2、= () [] -> ->*必须设置为成员函数
3、赋值运算符应该被设置为成员函数
4、其他的双目运算符应该作为全局函数(如+、-、*、/等)
原型
常见操作符的原型:
操作符++和--,++和--也是可以实现运算符重载,怎么区分是++x还是x++?
首先要了解a++和++a的区别:
a++可以这么理解:先对a原来的值(a=5)拷贝一份,接着执行a = a + 1,最后将之前拷贝的副本赋值给b,于是就有b = 5, a = 6
++a可以这么理解:先执行a = a + 1,最后将新的结果a = 7赋值给b,于是就有b = 7, a = 7
int main()
{int a = 5;int b = a++; //b = 5, a = 6cout << "b = " << b << ",a = " << a <<endl;int c = ++a; //a = 7, c = 7cout << "c = " << c << ",a = " << a <<endl;return 0;
}
输出结果:
++、--运算符的重载
按照这样的运算规则对++运算符进行重载。
class Integer{
public:Integer(int n = 0) : i(n) {}friend const Integer operator+(const Integer& l, const Integer& r);friend const Integer operator-(const Integer& l, const Integer& r);const int get() { return i;}const Integer& operator++(); //++x,++做前缀const Integer operator++(int); //x++,int并不会作为形参传递,返回的是一个新的Integer对象,所以不加引用const Integer& operator--(); //--x,--做前缀const Integer operator--(int); //x--,int并不会作为形参传递
private:int i;
};//对'+'运算符重载
const Integer operator+(const Integer& l, const Integer& r){ //全局函数return Integer(l.i + r.i);
}//直接对原来对象的值进行修改
const Integer& Integer::operator++(){*this = *this + 1; //调用'+'的重载函数return *this; //返回一个integer对象,加&是是因为这是对原来的对象的直接修改
}//返回值不加引用是因为返回的是一个局部对象,return后实际上会执行一个拷贝构造函数操作
const Integer Integer::operator++(int){ //返回一个新创建的对象Integer old(*this);++(*this); //调用上面的++重载函数return old;
}//实现--x
const Integer& Integer::operator--(){*this = *this - 1;return *this;
}//实现x--
const Integer Integer::operator--(int){Integer old(*this); //拷贝构造函数--(*this); //调用--x的重构函数return old;
}int main()
{Integer x(5);Integer y = x++;cout << "y.i = " << y.get() << ", x.i = " << x.get() << endl;Integer z = ++x;cout << "z.i = " << z.get() << ", x.i = " << x.get() << endl;return 0;
}
可以看到输出结果与上个例子结果一致。
关系运算符的重载
通过==重载来实现!=的重载,通过<重载来实现>、<=、>=的重载,这么写的好处是充分利用已有函数,后期修改只需修改两个函数。
class Integer{
public:Integer(int n = 0) : i(n) {}friend const Integer operator+(const Integer& l, const Integer& r); //友元函数friend const Integer operator-(const Integer& l, const Integer& r);const int get() { return i;}const Integer& operator++(); //++x,++做前缀const Integer operator++(int); //x++,int并不会作为形参传递,返回的是一个新的Integer对象,所以不加引用const Integer& operator--(); //--x,--做前缀const Integer operator--(int); //x--,int并不会作为形参传递//overload relational operatorsbool operator==(const Integer& r) const;bool operator!=(const Integer& r) const;bool operator<(const Integer& r) const;bool operator<=(const Integer& r) const;bool operator>=(const Integer& r) const;bool operator>(const Integer& r) const;private:int i;
};// overload relational operators definition
bool Integer::operator==(const Integer& r) const{return (i == r.i);
}bool Integer::operator!=(const Integer& r) const{return !(*this == r); //调用==的重载函数
}bool Integer::operator<(const Integer& r) const{return i < r.i;
}bool Integer::operator>(const Integer& r) const{return r < *this; //调用<的重载函数
}bool Integer::operator<=(const Integer& r) const{return !(r < *this); //调用>的重载函数, <=就是>取反
}bool Integer::operator>=(const Integer& r) const{return !(*this < r); //调用<的重载函数, >=就是<取反
}int main()
{Integer x(5);Integer y(7);cout << boolalpha << (x < y) <<endl; //调用x.operator<(y),boolalpha使得打印出bool类型cout << boolalpha << (x > y) <<endl;cout << boolalpha << (x == y) <<endl;return 0;
}
输出结果:
类型转换
用户定义的类型转换:当构造函数是单个参数或运算符转换的隐式类型转换时编译器会进行隐式转换。
C++类型转换:
对于用户定义的类型,有两种方法实现T==>C。一是C中存在用T作为单个参数传递的构造函数,二是T中存在用运算符重载的方式实现T==>C。
构造函数实现自动类型转换
#include <iostream>class One{
public:One() {}
};class Two{
public:Two(const One&){}
};void f(Two){}int main()
{One one; f(one); //wants a Two, has a onereturn 0;
}
f()函数需要Two类型的对象作为参数,当将对象one作为参数传递时,编译器会查找是否存在用类One来构建类Two的构造函数,这时会调用Two::Two(const One&),结果就是将Two的对象作为参数传递给f()。
自动类型转换可以避免定义两个不同版本的f()函数,但是自动类型转换会隐式地调用Two的构造函数,会对程序的效率有影响。
避免编译器使用构造函数实现自动类型转换需要加关键词explict,如下:
class One{
public:One() {}
};class Two{
public:explicit Two(const One&){}
};void f(Two){}int main()
{One one;//f(one); //errorf(Two(one)); //ok,Two(one)创建了一个临时的对象将其作为参数传递给f()return 0;
}
运算符重载实现自动类型转换
class Three{
private:int i;
public:Three(int ii) : i(ii) {}
};class Four{
private:int x;
public:Four(int xx) : x(xx) {}operator Three() const { return Three(x); } //函数名就是要转换的类型,所以最前面不用加返回值类型
};void g(Three) {}int main()
{Four four(1);g(four); //实现Four==>Three,再将转换后的对象传递给g()g(1); //调用Three(1,0)return 0;
}
自动类型转换的缺陷
当上述提到的两种自动类型转换的情况同时存在时,就会产生一个模糊的转换,两种方式都能实现隐式的自动类型转换,编译器不知道使用哪种方式,就会出现报错的情况
class Orange{
public:Orange(const Apple&); //Convert Apple to Orange
};class Apple{
public:operator Orange() const; //Convert Apple to Orange
};void f(Orange){ }int main()
{Apple a;//f(a); //ambiguous conversionreturn 0;
}
类型转换总结
尽量不要使用这种自动类型转换,函数调用时会出现各种问题,使用显式类型转换函数,如double ToDouble()来代替operator double() const。
参考资料:
Thinking in C++,Chapter 12 Operator Overloading
浙江大学翁凯C++教程