关于c++的运算符重载那些事

搞c++有一段时间了,今天突然要重载一个运算符,发现自己有点忘了,遂查查资料做一下c++运算符重载的小总结。

一、何为运算符重载

运算符的重载是c++语言特有的,java什么的是没有运算符重载的,所以运算符重载在笔试面试中有可能成为c++的高频考点。运算符重载就是重新定义运算符的意义,如常用的+,-,×,÷都是可以重载的。运算符重载使用的是关键字operator,表现形式是:

                                                               返回值 operator重载的运算符(函数参数列表)

举一个简单的例子,有一个分数类,定义如下:

  1. class Fraction{
  2. public:
  3. int x;//分子
  4. int y;//分母
  5. Fraction(int x = 0,int y = 1):x(x),y(y){
  6. }
  7. void show(){
  8. cout << x <<"/" << y << endl;
  9. }
  10. }

比如定义了两个类Fraction fa,fb,你做一个fa+fb肯定是错误的,因为编译器会提示你+它不认识,但是这种分数相加在现实中又是非常合理的事情。所以我们必须要重新定义+的意思,以满足+在这种情况下的使用。在这里就该被这样定义;

                                                      Fraction operator+(const Fraction& fa,const Fraction& fb)

那么现在我们要明白的是,当编译器遇到fa+fb这种情况,编译器是如何去解析的?解析的规则如下:

碰到fa + fb的两种解析规则,首先去类的成员函数中找一个函数operator+(constFraction& fb),找不到则去全局区找一个全局函数operator+(constFraction& fa, const Fraction& fb),这里的意思就是说,我们可以把重载函数写成成员函数的形式,也可以写成全局函数的形式,博主建议,能写成成员函数的就不要写成全局函数,而且最好只选取一种方案。上面的红色字体部分就是全局形式的+重载。

知道了上面的原理之后,我们首先来采用全局形式的重载。

  1. Fraction addFraction(const Fraction& fa,const Fraction& fb){
  2. Fraction fc;
  3. fc.x = fa.x * fb.y + fa.y * fb.x;
  4. fc.y = fa.y * fb.y;
  5. return fc;
  6. //return Fraction(fa.x * fb.y + fa.y * fb.x,fa.y * fb.y);//简写
  7. }
/*下面的代码将提供对分数类Fraction的+,-,*,/的重载*/
  1. /*运算符重载,分数类的运算符重载*/
  2. #include <iostream>
  3. using namespace std;
  4. class Fraction{
  5. public:
  6. int x;
  7. int y;
  8. Fraction(int x = 0,int y = 1):x(x),y(y){
  9. }
  10. /*只要能写成员函数就不写全局函数,编译器遇到fa+fb的时候,首先是fa,然后
  11. 碰到+号所以说是按照顺序来的,即是fa调用了重载的成员函数,所以里面的this就是
  12. fa对象*/
  13. Fraction operator+(const Fraction& fb){
  14. Fraction c ;
  15. cout << this->x << this->y << fb.x << fb.y << endl;
  16. c.x = this->x * fb.y + this->y * fb.x;
  17. c.y = this->y * fb.y;
  18. return c;
  19. //return Fraction(this->x * fb.y + this->y * fb.x,this->y * fb.y);
  20. }
  21. /*设计成员函数完成两个分数的相减*/
  22. Fraction operator-(const Fraction& fb){
  23. return Fraction(this->x * fb.y - this->y * fb.x,this->y * fb.y);
  24. }
  25. /*设计成员函数实现两个分数相乘*/
  26. Fraction operator*(const Fraction& fb){//可以设计成任意返回值类型
  27. return Fraction(this->x * fb.x,this->y * fb.y);
  28. }
  29. /*完成两个分数*=*/
  30. void operator*=(const Fraction& fb){
  31. this->x *= fb.x;
  32. this->y *= fb.y;
  33. }
  34. void show(){
  35. cout << x <<"/" << y << endl;
  36. }
  37. };
  38. /*设计一个分数相加的函数,全局形式的重载*/
  39. //Fraction addFraction(const Fraction& fa,const Fraction& fb){
  40. ///*Fraction fc;
  41. //fc.x = fa.x * fb.y + fa.y * fb.x;
  42. //fc.y = fa.y * fb.y;
  43. //return fc;*/
  44. //return Fraction(fa.x * fb.y + fa.y * fb.x,fa.y * fb.y);//简写
  45. //}
  46. /*设计一个函数返回double让一个分数和一个整数相加*/
  47. double operator+(const Fraction& fa,int x){
  48. double c;
  49. c = x+1.0*( fa.x / fa.y);
  50. cout << c << endl;
  51. return c;
  52. }
  53. void main(){
  54. Fraction fa(1,3);
  55. fa.show();
  56. Fraction fb(1,2);
  57. fb.show();
  58. //addFraction(fa,fb).show();
  59. Fraction fc = fb + fa;
  60. fc.show();
  61. //cout << &addFraction(fa,fb);//这句话说明了什么
  62. //Fraction fd = fa - fb;
  63. //fd.show();
  64. //Fraction fe = fa * fb;
  65. //fe.show();
  66. //fa *= fb;
  67. //fa.show();
  68. ///
  69. //double res = fb + 100;
  70. //cout << res;
  71. }

二、输入、输出流运算符的重载<<,>>

以一个整数包装类Integer类为例

class Integer{
int data;
public:
Integer(int data = 0):data(data){}

}

如果对象是一个自定义类,则cin >> fa,cout << fa同样会让编译器报错。那么则需要重载这两个运算符。在上一节中我们说到,重载运算符有两种解析规则,首先在类的成员函数中找,然后才去找全局的重载函数。比如重载输出流运算符>>,先去ostream类型中,找一个成员函数叫做:operator<<(const Integer& i),那么我们想一想,这个能找得到吗?ostream是系统提供的类,你的到ostream类中去添加这样一个成员函数,显然是非常困难的,所以输出,输出运算符最好的形式就是重载成全局形式。

  1. /*采用全局函数重载<<运算符*/
  2. ostream& operator<<(ostream& os,const Integer& i){//如果以void作为输出不支持连续输出
  3. //return os << i.data;
  4. os << i.data;
  5. return os;
  6. }
  7. /*写出自己的输入流函数>>*/
  8. istream& operator>>(istream& is,Integer& i){//这里不能写const,输入就是的改这个对象
  9. is >> i.data;
  10. return is;
  11. }


三、关于对运算符重载的几点说明
包括二元运算符的重载,一元运算符的重载,明确重载的规则,明确哪些运算符可以重载,哪些不可以,哪些只能重载成成员形式,哪些又只能重载成全局形式。

一元运算符的重载的解析规则:首先去a对象找一个成员函数operator#(),如果找不到,就去全局找一个全局函数叫做operator#(a),#代表所需要重载的一元运算符 ,在重载++,--时需要注意,前++和后++代表的意思是不同的,产生的效果也是不同的,所以是分别重载的,还有是否能连续的++,例如++(++a)这种形式。

  1. /*几个一元运算符的重载*/
  2. #include <iostream>
  3. using namespace std;
  4. class Integer{
  5. int data;
  6. public:
  7. Integer(int data = 0):data(data){}
  8. /*!运算符的重载*/
  9. Integer operator!(){
  10. return Integer(!data);
  11. //return !data; 当一个类型中出现了单参构造函数时,这里有默认的类型转换
  12. }
  13. /*-,~,++,--运算符的重载*/
  14. Integer operator-(){
  15. return Integer(-data);
  16. }
  17. /*++运算符的重载,默认是前++*/
  18. Integer& operator++(){ //注意,如果这里返回的不是引用,main函数中有什么效果
  19. data++;
  20. return *this;
  21. }
  22. /*设计后++的重载*/
  23. const Integer operator++(int) //哑元
  24. {
  25. return Integer(data++);
  26. }
  27. friend Integer& operator--(Integer& i);
  28. friend const Integer operator--(Integer& i,int);
  29. friend ostream& operator<<(ostream& os,const Integer& i){
  30. return os << i.data;
  31. }
  32. };
  33. /*现在考虑重载全局形式的前--和后--*/
  34. Integer& operator--(Integer& i){
  35. i.data--;
  36. return i;
  37. }
  38. const Integer operator--(Integer& i,int){
  39. return Integer(i.data--);
  40. }
  41. int main(){
  42. Integer ia(100);
  43. cout << !ia << endl;
  44. cout << !!ia<< endl;
  45. cout << -ia << endl;
  46. cout << ++(++ia)<< endl; //返回void则不支持连续++
  47. cout << ia << endl;
  48. //cout << ia++ << endl;在没有重载后++,这句话在Ubuntu下是通不过的,不支持后++,vs可以通过,但是结果不对
  49. //重载了后++之后
  50. cout << ia++<< endl;//加上const后可以防止连续的后++
  51. cout << ia<< endl;
  52. /*那么连续的后++有没有意义,连续的后++在c语言中是编译不过的,支持连续的前++*/
  53. /*全局形式的前--后--的重载*/
  54. cout << --ia<<endl;
  55. cout << ia << endl;
  56. cout << ia--<<endl;
  57. cout << ia<< endl;
  58. }

四、运算符重载的限制

不能重载的运算符包括

                  1、 ::(作用域)

                  2、.(成员运算符)

                  3、.*成员指针解引用

                  4、Sizeof(类型大小)

                  5、? :三元运算符

                  6、Typeid 获取类型的信息(返回值类型typeinfo)


只能对已有的运算符进行重载,不能发明新的运算符;

不能对基本类型进行运算符重载(运算符重载中至少有一个类型是非基本类型);

不能改变运算符的运算特性;不能把一元的改成二元的;

五、只能是重载成成员形式的运算符:=,[],()(最好是成员+=,-=,/= ,*,->等。

赋值运算符=的重载有些麻烦,因为这里必须涉及到内存的操作,以及深浅拷贝的问题。看下面的代码

  1. #include <iostream>
  2. using namespace std;
  3. class Array{
  4. int size; //用于记录最后有数据的空间的实际大小
  5. int len; //用于标记分配的空间大小
  6. int *datas;
  7. public:
  8. explicit Array(int len = 5):len(len),size(0){//explicit防止隐式转换
  9. //分配内存
  10. datas = new int[len];
  11. }
  12. ~Array(){
  13. delete[] datas;
  14. datas = NULL;
  15. }
  16. //拷贝构造函数
  17. Array(const Array& arr){
  18. //处理内存独立性
  19. size = arr.size;
  20. len = arr.len;
  21. //申请新内存
  22. datas = new int[len];
  23. for (int i = 0; i < size; i++){
  24. datas[i] = arr.datas[i];
  25. }
  26. }
  27. void push_data(int d){
  28. if (size >= len){
  29. //扩容
  30. expend();//expend为什么没有前置声明就可调用,记住在类内进行操作的函数不需要进行前置声明
  31. }
  32. datas[size++] = d;//如果size大于了预分配空间,则要进行扩容操作
  33. }
  34. void expend(){
  35. int *temp = datas; //保留机制
  36. len = 2 * len + 1;
  37. datas = new int[len]; //datas被重新分配空间
  38. for (int i = 0; i < size; i++){
  39. datas[i] = temp[i];
  40. }
  41. delete[] temp; //释放掉中间值,这个过程注意
  42. }
  43. void show(){
  44. if (0 == size)
  45. {
  46. cout << "[]" << endl;
  47. return;
  48. }
  49. for (int i = 0; i < size - 1; i++){
  50. cout << datas[i] << ',';
  51. }
  52. cout << datas[size - 1] << endl;
  53. }
  54. //重载=运算符
  55. Array& operator=(const Array& arr){
  56. if (this != &arr){//防止把自己赋值给自己
  57. size = arr.size;
  58. len = arr.len;
  59. int *temp = datas;
  60. //重新申请内存
  61. datas = new int[len];
  62. //赋值数据
  63. for (int i = 0; i < size; i++){
  64. datas[i] = arr.datas[i];
  65. }
  66. //释放原来自己的内存
  67. delete[] temp;
  68. }
  69. return *this;
  70. }
  71. //重载[]运算符,根据下标取数据
  72. int operator[](int ind)const{//并不对这个对象进行修改,可以加上const
  73. return datas[ind];
  74. }
  75. };
  76. void main(){
  77. //Array arra = 20;//防止了隐式转换,编译不过,如果不加explicit就编译过了
  78. Array arra;
  79. arra.push_data(9);
  80. arra.push_data(5);
  81. arra.push_data(2);
  82. arra.push_data(7);
  83. cout << arra[0] << endl;
  84. cout << arra[10] << endl;//越界
  85. //Array arrb = arra;这种解析方式会去找拷贝构造函数
  86. //arrb.show();
  87. Array arrb;
  88. arrb = arra;//会用运算符重载的方式解析,如果没有提供=重载,会用系统提供的逐字节拷贝方式
  89. arrb.show();//程序崩了,被重载了之后不会报错,也不会崩溃
  90. }


在上面的代码中,如果遇到arrb= arra且我们没有提供赋值运算符重载的时候,那么编译器会采用默认的处理方式,就是逐字节拷贝,那么这种拷贝方式在处理datas堆内存时会遇到什么问题呢?len,size,datas等成员变量依次被覆盖掉,那么此时arrb的成员datas所指向的堆内存就和arra的datas指向的堆内存一样了,那么原来arrb的datas所指向的堆内存就泄露了。这就是这里比较严重的问题。

六、圆括号运算符()的重载

我们在写程序的时候经常喜欢这样用int x = (int)y,这里的y不是int类型,这是强制的类型转换,但是如果我们定义了一个产品类Product,它拥有成员int count代表产品的数量,double price代表产品的价格,如果在开发中我们想把这个Product转换成int类型,那么显而易见,对于开发者来说我们想要取得的是产品的数量。如果我们想把Product转成double型,同样我们肯定是希望获得其价格。

所以圆括号的重载在开发中用途广泛,它的作用就是把一个单参类型转换成当前对象类型。

语法格式:圆括号既然只能是成员形式,那么我们肯定这样想:int operator()(){},但是这是不行的,编译器不认可,圆括号的重载有固定的格式:operator 类型(){

             return 对象类型

       }

如果要重载圆括号返回int的话,就得这样写:

operator int(){

return count;

}

  1. /*圆括号运算符的重载,把一个单参类型转换成当前对象类型*/
  2. /*圆括号运算符的重载*/
  3. /*operator 类型(){
  4. return 类型的对象
  5. }*/
  6. #include <iostream>
  7. using namespace std;
  8. class Product{
  9. int count;
  10. double price;
  11. public:
  12. Product(int count = 0, double price = 0.0) :count(count), price(price){}
  13. /*重载()运算符*/
  14. //我们一般会这样写,这种写法编译器不会认可的
  15. //int operator()(){
  16. //return count;
  17. //}
  18. //但是得这样写,固定形式,没有为什么
  19. operator int(){
  20. return count;
  21. }
  22. /*转换成double的圆括号重载*/
  23. operator double(){
  24. return price;
  25. }
  26. };
  27. int main(){
  28. Product product(100, 1.15);
  29. /*现在如果我们想把这个product变成整数,我们想要的肯定是数量*/
  30. int count = (int)product;
  31. cout << count << endl;
  32. double price = (double)product;
  33. cout << price << endl;
  34. }

七、new delete运算符的重载

首先的明白一个问题就是new和delete比malloc和free多做了那些事

如果类的成员变量是类类型,则自动创建这个成员,自动调用构造函数。

delete 会去调用析构函数,free不会.

  1. #include <iostream>
  2. using namespace std;
  3. /*这种情况下如果c的编译器求sizeof(A)其大小是0
  4. 而用c++的编译器求siziof(A)则是1.这里表现出了编译器
  5. 对待内存分配的差异,我觉得这些细小的知识点展现出了
  6. 一名优秀c++程序设计的人员的素质*/
  7. class A{
  8. public:
  9. A(){ cout << "A()" << endl; }
  10. ~A(){ cout << "~A()" << endl; }
  11. };
  12. class B{
  13. A a;
  14. public:
  15. B(){ cout << "B()" << endl; }
  16. ~B(){ cout << "~B()" << endl; }
  17. };
  18. int main(){
  19. B* pb = static_cast<B*>(malloc(sizeof(B)));
  20. B * pb2 = new B();
  21. free(pb);
  22. delete pb2;
  23. }


重载new delete:

固定写法:void * operator new(size_t size)
                  void operator delete(void * ptr)
  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4. class Date{
  5. int year;
  6. int month;
  7. int day;
  8. public:
  9. Date(int year = 0, int month = 0, int day = 0) :year(year), month(month), day(day){
  10. cout << "Date()" << endl;
  11. }
  12. ~Date(){ cout << "~Date()" << endl; }
  13. };
  14. /*放到成员和全局是一样的*/
  15. void* operator new(size_t size){//具有自动识别大小的功能
  16. cout << "my operator new" << size << endl;
  17. return malloc(size);
  18. }
  19. void operator delete(void* ptr){
  20. cout << "operator delete" << endl;
  21. free(ptr);
  22. }
  23. int main(){
  24. Date * date = new Date();
  25. delete date;
  26. }

(0)

相关推荐