自学内容网 自学内容网

C++ -运算符重载

博客主页:【夜泉_ly
本文专栏:【C++
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

📚 前言

本文内容:
在这里插入图片描述

本来就想写赋值运算符重载的,结果发现运算符重载都没写🤣,那就先来看看什么是运算符重载吧。
对内置类型,下面这些操作是很常见的:

int a, b;
a = 1, b = 5; // 赋值
a += b; // 加等
cout << a; // 流插入

而对于类,有时也需要进行类似的操作,于是C++使用operator关键字对运算符进行了重载,使类对象也可以像内置类型一样使用运算符。
讲概念有点太空了,来看看具体的例子吧——日期类:
根据上篇文章讲的构造、析构、拷贝构造,先搭个架子:

class Date
{
public:
Date(int y = 1, int m = 1, int d = 1) // 全缺省的默认构造
{
_year = y;
_month = m;
_day = d;
}
Date(const Date& d) // 拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print(){cout << _year << '/' << _month << '/' << _day << endl; } //一个不重要的打印函数
private:
int _year;
int _month;
int _day;
};

🔄 默认赋值运算符重载

现在我想将一个日期赋值给另一个日期,该怎么做?赋值运算符重载(operator=)。
幸运的是,由于将一个类对象赋值给另一个类对象的操作太过常见,编译器提供了默认的赋值运算符重载。
其行为和拷贝构造类似:

  • 对内置类型,逐字节拷贝。
  • 对自定义类型,调用对应的赋值运算符重载
    在这里插入图片描述

于是,就可以这样做:

Date day1(2000, 9, 13);
Date day2;
day2 = day1; // 将day1赋给day2
day2.print();

运行结果:

2000/9/13

为了便于理解,还是来写一下吧:
运算符重载的本质是一个具有特殊名字的函数,既然是函数,首先要确定三点:函数名、参数列表、返回类型
规定:函数名为operator加需要重载的运算符。
而此处,函数名就是 operator=
再来看看返回类型:

cout << typeid(day2 = day1).name() << endl;

运行结果:

class Date

返回日期类对象。但是不是日期类对象的引用呢?
运算符重载这块,如果有不清楚的,可以用 int 来对应其行为。
例如,下面就验证了= 返回的是引用:

void Test()
{
int num;
int& ref = (num = 1);
ref = 666;
cout << num;
}

运行结果:

666

可以看见,修改了返回值的引用 ref ,将num也修改了。

⚙️ 常见运算符返回类型

掌握了返回类型,就初步掌握了如何写运算符重载,所以我在这里稍微整理了一下:

  • 赋值运算符 (=)

    • 返回:左操作数的引用。

    • int a;
      int& ref = (a = 1);  // ref 是 a 的引用
      ref = 666;
      cout << a;
      

      运行结果:

        666
      
  • 复合赋值操作符(+=, -=…)

    • 返回:左操作数的引用。
    • 道理同上
  • 算术运算符 (+, -, *, /, %)

    • 返回:一个临时的值(右值)。

    • int a;
      const int& ref = a + 6; 
      

      ref 是临时变量的 const 引用,相当于延长了临时变量的生命周期

      如果想这样写:

      int& ret = a + b;
      

      会报错,因为引用不能放大权限:
      在这里插入图片描述

  • 自增自减运算符 (++, --)

    • 前缀形式:返回左操作数的引用。
    • 后缀形式:返回一个临时的值(右值)。
    • int a = 666;
      int& ref1 = ++a; 
      const int& ref2 = a++;  
      
      ref1a 的引用,ref2 是临时变量(存放 a 原来的值)的引用
  • 关系运算符 (==, !=, <, >, <=, >=)

    • 返回:一个布尔值(bool)。
    • int a = 666;
      bool result = (a > 6);  // result 是 true
      
      如果想这样写:
      bool& ref = (a > 6);
      
      会报错,因为引用不能放大权限:
      在这里插入图片描述
  • 逻辑运算符 (&&, ||, !)

    • 返回:一个布尔值(bool)。
    • int a = 666;
      int b = 666;
      bool result = (a > 0 && b > 0);  // result 是 true
      
      这个和关系运算符是一样的。
  • 位运算符 (&, |, ^, ~, <<, >>)

    • 返回:一个临时的值(右值)。

    • int a = 666;
      int result = (a & 0);
      const int& ref = (a & 0);
      

      如果想这样写:

      int& ref = (a & 0);
      

      会报错,因为引用不能放大权限:在这里插入图片描述

  • 流插入,流提取(<<>>

    • << 返回 ostream 的引用,>> 返回 istream 的引用(在这里这样认为就够了)

    • int a;
      cout << typeid(cin >> a).name() << endl;
      cout << typeid(cout << "").name() << endl;
      

      运行结果:

        class std::basic_istream<char,struct std::char_traits<char> >
        class std::basic_ostream<char,struct std::char_traits<char> >
      
  • 条件运算符 (? :)

    • 返回:取决于两个表达式。
    • int a = 666;
      int b = 0;
      int& ref1 = (a > b) ? a : b;   // 这样可以
      int& ref2 = (a > b) ? a++ : b; // 这样不可以
      int& ref3 = (a > b) ? a : b++; // 这样不可以
      
  • 逗号运算符 (,)

    • 返回:取决于最后一个表达式。
    • int a = 666;
      int& ref1 = ("HaHa", a); // 这样可以
      int& ref2 = (a, a++);    // 这样不可以
      
  • 函数调用运算符 (()), 下标运算符 ([]), 成员访问运算符 (->, .)

    • 这些我也不清楚,应该是取决于对应函数或对象的具体实现吧。

在这里插入图片描述

✍️ 赋值运算符重载

额。。扯远了,我们再来看看怎么写赋值运算符重载。
首先函数名和返回类型我们已经解决了,那么剩下的就是参数。
关于这点,我认为一般传引用就行,如果不修改,那就再加个 const。而对于栈之类需要深拷贝的类,可以考虑用一用传值传参,然后再交换,这个在之后简单模拟实现容器的时候应该会讲。
对于日期类,就是将一个日期赋给另一个日期:

Date& operator=(Date& tgt, const Date& src);

但这样写会有问题,因为定义在类外面的函数不能访问类的私有成员。
所以一般会将运算符重载写进类里面:

class Date{
public:
Date& operator=(const Date& src);
...
}

再看看实现:

Date& Date::operator=(const Date& src)
{
_year = src._year;
_month = src._month;
_day = src._day;
return *this;
}

调用的话有两种方式,一种是将其当成函数调用:

day2.operator=(day1);

用得更多的是将其当成运算符调用:

day2 = day1;

如果写对了,那么应该可以连续赋值,并且返回值可以调成员函数,来验证一下:

void Test()
{
Date day1(2000, 9, 13), day2, day3;
(day3 = day2 = day1).print();
}

运行结果:

2000/9/13

💡 其他运算符重载示例

再来写写其他的。
比如 加 +

Date operator+(int add);

具体实现。。懒得搬了,就当我写了吧。
重点看看前置++和后置++:
这两个都是单目操作符,而为了将这两个区分,C++直接给出了规定:
这个——operator++()——是前置++。
这个——operator++(int)——是后置++。
而返回类型,根据刚刚的总结,前置是自增后引用,后置是自增前的临时变量:

Date& operator++();
Date operator++(int);

具体实现可以直接复用 operator+

再看看关系运算符,这里总共有五个,但只需要写两个:

bool operator<(const Date& d);
bool operator==(const Date& d);

然后,> 就是 !(<||==) ,>= 就是。。。总之复用就行。
在这里插入图片描述

最后来看看流插入和流提取。
假设已经实现好了,那么应该像下面这样使用:

cin >> day;
cout << day;

很明显,操作符的左边不是类对象,因此不能写成成员函数,我们可以将其定义为全局,并声明为类的友元:

class Date{
friend istream& operator>>(istream& in , const Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
...
}

<< 的具体实现:

ostream& operator<<(ostream& out, const Date& d) {
    out << d._year << '/' << d._month << '/' << d._day << endl;
    return out;
}

注意这里只能返回 ostream 的引用。

再来看看取地址操作符重载和const取地址操作符重载:
大概长这样:

Date* operator&() { return this; }
const Date* operator&() const { return this; }

这两个也是类的默认成员函数,如果不写,编译器会自动生成。
一般情况下,用编译器自动生成的就行,如果你不想让别人取地址,也可以自己写一个,然后返回空或者不存在的地址。

⚠️ 注意事项

最后来看看运算符重载的注意事项:

  • 名字由operator和后⾯要定义的运算符共同构成
  • 不能创造新的操作符
  • . .* ?: :: sizeof 不能运算符重载
  • 不能重载内置类型,如 int operator+(int a, int b);
  • 双目操作符,左边的传入第一个参数,右边的传入第二个参数
  • 自增自减,()为前置,(int)为后置
  • 流插入、流提取,需要重载为全局
  • 重载后最好有意义(一个日期可以减一个日期,但加?)

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!


原文地址:https://blog.csdn.net/2401_86587256/article/details/143982929

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!