C++运算符重载

引言

C++中运算符重载时面向对象编程的核心特征之一,它允许为自定义类型(如类结构体)重新定义运算符的行为,让对象可以像内置类型一样直观的参与运算

 #include <iostream>
 ​
 using namespace std;
 ​
 int main()
 {
     string s1 = "hello";
     string s2 = "world";
 ​
     //运算符操作
     cout << s1 + s2 << endl;
     //调用类内方法
     cout << s1.append(s2) << endl;
     
     return 0;
 }

输出结果

上述代码中分别使用了不同方法实现了同一操作,相比之下使用运算符使得代码可读性提升,符合日常思维习惯

基本语法

运算符重载通过operator关键字定义,格式如下

 返回类型 operator 运算符(参数列表) { ....... }

移动运算符重载

 class Matrix
 {
     public:
     //移动赋值运算符重载(operator=)
     Matrix& operator=(Matrix&& rhs)
     {
         if(this != &rhs)
         {
             delete[] data_;         //释放现有资源
             data_ = rhs.data_;      //获取右值资源
             rhs.data_ = nullptr;    //置空源对象
         }
         return *this;
     }
     private:
     float* data_;
 };
 ​
 //使用场景
 Matrix a = std::move(b);  //触发移动赋值

上述代码就是之前几期文章中实现过的等号运算符重载,可以减少程序开销。

语法规则

  • 操作数量约束

    1. 一元运算符(0个显式参数)

 class Timer
 {
     public:
     //前缀 ++ (无形参)
     Timer& operator++()
     {
         ++seconds_;
         return *this;
     }
     
     //后缀 -- (int占位符表后缀)
     Timer operator--(int)
     {
         Timer tmp = *this;
         --seconds_;
         return tmp;
     }
     
     //前缀递减运算符重载(无int占位符表示前缀)
     Timer& operator--()
     {
         --seconds_;
         //返回修改后的对象引用
         return *this;
     }
 };

调用逻辑:

 Timer t;
 +=t;    //等价于 t.operator++()
 t--;    //等价于 t.operator--(0)

通过观察前缀自减重载运算符和后缀重载运算符的实现方法我们不难发现,前缀运算符的实现要比后缀运算符更能减少程序开销,因为在后缀运算符实现的过程中需要调用中间变量,不如后缀直接返回更简洁,所以当我们在循环中使用自增变量时前置自加或者自减时优先

 //会调用中间变量,效率较低
 for(int i ; i < 1000 ; i++); { ...... }
 //情况允许优先使用,减小开销
 for(int i ; i < 1000 ; ++i); { ...... }
  1. 二元运算符(一个显式参数)

    成员函数形式:左操作数 = this, 右操作数 = 显示参数

     class Vector
     {
         public:
         //隐含this作为左操作数
         Vector operator+(const Vector & rhs)
         {
             return Vector(x + rhs.x, y + rhs.y);
         }
         
         private:
         double x,y;
     };
     ​
     //调用方式
     Vector a, b;
     Vector c = a + b;  //a.operator+(b)

全局函数形式:显示声明两个操作数

 //全局函数:显示提供两个参数
 COmplex operator+(const Complex& lhs, const Complex& rhs)
 {
     return Complex(lhs.real() + rhs.real(),
                   lhs.imag() + rhs.imag());
 }

调用逻辑:

 Complex d = a + b;     //等价于operator+(a, b)
  1. 特殊运算符:函数调用operator() (可变参数)

 class MatrixMultiplier 
 {
     public:
     //支持任意数量参数(三元操作实例)
     double operator() (int i, int j, int k)
     {
         return matrixA_[i][j] * matrixB_[j][k];
     }
 };

调用逻辑:

 MatrixMultiplier matmul;
 double val = matmul(1, 2, 3);    //等价于 matmul.operator()(1,2,3)

友元函数重载

上文提到了成员函数,这里再补充一下友元函数的相关操作

 class Logger
 {
     private:
     string log_;
     public:
     //友元声明
     friend ostream & operator<<(ostream & os, const Logger & log);
 };
 ​
 //全局函数实现
 ostream & operator<<(ostream & os, const Logger & log)
 {
     return os << "Log :" << log.log_;   //访问私有成员
 }
 ​
 //调用方式
 Logger log;
 cout << log;   //operator(cout , log)

友元函数重载后任然会面临和之前一样的问题,就是友元函数这种类似于走后门的操作会破坏程序的封装性

特性 成员函数重载 友元函数重载
归属关系 类成员(隐含this指针) 全局函数(需声明为friend)
访问权限 直接访问所有成员(包括private) 需要显示声明friend才能访问private成员
左操作数绑定 必须是当前类对象 可以为任意类型(支持隐式类型转换)
面向对象特性 封装性强 破坏封装性

注意事项

  1. 不可重载的运算符(部分)

     ::  .*  ?  :  sizeof  typedef  noexcpet
  2. 重载后的优先级与结合性不变

     v1 + v2 * v3       //重载之后仍旧先执行乘法运算
  3. 重载时注意功能冲突

在重载时注意不要出现反直观的重载方式,例如将加法操作重载给"-"运算符

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐