C嘎嘎~~ [类 下篇之运算符重载]

简介: C嘎嘎~~ [类 下篇之运算符重载]

5.赋值运算符重载

5.1运算符重载

5.1.1 运算符的概念

C语言学习的时候, 我们可以用运算符来操作内置类型. 比如, 比较两个整数的大小, 比较两个整数是否想相等… …

那么我们能不能也用操作符来对自定义类型来进行操作??

class Date
{
public:
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2023, 5, 6);
  Date d2(2003, 5, 7);
  bool ret = d1 > d2;
}
*****
error C2676: 二进制“>”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换
*****

通过上面的代码, 我们可以发现是不行的~~.

其实, 不用代码来作佐证, 我们也可以猜想的出来 <== 因为不知道要比较什么啊. 到底是比较两个对象的年, 还是比较两个对象的月, 还是比较两个对象的日??

由此, 我们可以基本得出 ⇒ 内置类型可以直接使用操作符, 而自定义类型只能自己来写~~(当然, 这个有一个是意外的, 就是后面要讲的 赋值运算符重载)


为了解决自定义类型的 操作符使用, 引入了operator 这个关键字. operator + 操作符 ⇒ 就会导致该操作符重载


C++为了增强代码 的可读性引入了运算符重载, 运算符重载是具有特殊函数名的函数.


函数的一般形式: 返回值类型 + operator + 操作符 (参数列表)

函数是可以具有返回值类型的, 函数名, 以及参数列表


注意点:

1.不能创造新的操作符 , 比如 operator @

2.操作数至少有一个自定义类型 ⇐ 因为操作符重载就是用于自定义类型的, 如果都是内置类型, 我们就不用且不能改变, 编译器已经自己完成.

3.作为成员函数时, 形参比原本的操作数少一个⇐ 因为作为成员函数, 第一个形参就已经确定了, 就是this指针, 然而this指针是不在参数列表中显示的

4.名字虽然是重载, 但是这个重载却跟函数重载的重载不是一个意思. 这个重载的意思是: 让用户更加直观地知道这个运算符是等同功能作用于自定义类型的.

5.运算符重载函数是可以构成重载的

6.有五个操作符是不可以进行运算符重载的, .*(这个是 点乘, 不知道有啥用)) ::(作用域限定域) sizeof(计算变量 或 类型的大小) ?:(三目运算符) .(访问符)


5…1.2 重载运费符的位置

一般来说, 重载运费符函数是可以放在类中充当成员函数的, 也可以直接放在全局变量中的


1.重载运费符在全局变量

class Date
{
public:
    // 构造函数
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
//private:
  int _year;
  int _month;
  int _day;
};
bool operator== (const Date& x1, const Date& x2)
{
  if (x1._year == x2._year && x1._month == x2._month && x1._day == x2._day)
    return true;
  return false;
}
int main()
{
  Date d1(2023, 5, 6);
  Date d2(2003, 5, 7);
  bool ret = d1 == d2;
  cout << ret << endl;
}
*****
0
*****

2.重载运算符在类中

class Date
{
public:
  bool operator==(const Date& x)
  {
    if (_year == x._year && _month == x._month && _day == x._day)
      return true;
    return false;
  }
  // 构造函数
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2023, 5, 6);
  Date d2(2003, 5, 7);
  bool ret = d1 == d2;
  cout << ret << endl;
}
*****
0
*****

通过上面两组代码的对比, 我们可以发现:


1.相比较写在全局变量中, 充当成员函数, 我们就用少写一个操作数

2.写在全局变量中, 我们就不能用对象去访问类中的私有变量(我把私有放开了~)

3.其实, 用对象去访问私用变量有两种方式: 一是充当成员函数, 二是用友元函数(这个建议能不用就不用, 会破坏封装的)


5.1.3运算符重载的实质

我们已经知道的是用内置类型使用操作符是将 例如 " a > b"这样的操作符抓换成指令.

那么, 我们使用运算符重载的实质又是什么??


前面, 我们已经知道运算符重载是用函数来实现的, 我们就大胆猜想⇒ 使用运算符重载的实质就是调用函数

b87d03c1d35a433ebd99f5457feb9a81.png

7a2a80049044454e9edb8de6c6241cea.png



通过上面的佐证, 我们发现了 使用运算符重载 和 调用函数 的指令是一样的 ⇒ 使用运算符重载的实质就是调用函数


5.2 赋值运算符重载

有些老铁就有疑问: 我们之前的默认成员函数不是还没有讲完嘛, 怎么又讲这个运算符重载??

在日常的应用中, 赋值是非常常见的 ⇒ 所以赋值运算符是一个默认成员函数

运算符重载就是 为下面的赋值运算符重载打基础的~~


赋值运算符重载的格式:


参数类型: const 类名&, 传递引用可以提高效率

返回值类型: 为了支持连续赋值, 返回类类型; 建议返回类型传引用, 也是提高效率

返回 *this: 也是为了支持连续赋值

也要注意是否自己给自己赋值

普通的运算符重载的位置可以是在全局变量中的, 但是赋值运算符重载只能充当成员函数⇐ 因为它是默认成员函数, 如果写在外面 会和编译器生成的默认赋值运算符重载构成歧义.


5.2.1深刻理解—编译器生成的默认赋值运算符重载

编译器默认生成的运算符重载的行为 跟 编译器默认生成的默认拷贝构造函数是一样的

对内置类型: 按内存字节去拷贝, 相当于memcpy, 是浅拷贝, 也叫值拷贝

对自定义类型: 会调用自己的拷贝构造函数

处理:

对于没有资源申请的类型: 可以使用编译器默认生成的赋值运算符重载

对于有资源申请的类型: 自己来写一个赋值运算符重载函数


5.2.2深刻理解—拷贝构造和赋值运算符重载

拷贝构造: 用一个对象去初始化另一个对象(就是在对象实例化的时候, 直接初始化)

赋值运算符重载: 已经存在的,两个对象的复制

class Date
{
public:
  // 赋值运算符重载
  Date operator=(const Date& x)
  {
    if (this != &x)
    {
      _year = x._year;
      _month = x._month;
      _day = x._day;
    }
    return *this;
  }
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  // 构造
  Date d1(2023,5,9);
  Date d4;
  d1.Print(); // 2023 5 9
  d4.Print(); // 2023 5 5 
  // 拷贝构造
  Date d2(d1);
  Date d3 = d1;
  d2.Print(); // 2023 5 9
  d3.Print(); // 2023 5 9
  // 赋值运算符重载
  d1 = d4;
  d1.Print(); // 2023 5 5
  d4.Print(); // 2023 5 5
}

有人就会有疑问: d3 = d1 为什么是拷贝构造啊? 明明用的就是赋值啊?

抓重点: 拷贝构造就是初始化对象啊, 赋值就是两个已存在的对象之间的操作~~


5.2.3深刻理解—传参和返回值用引用修饰

class Date
{
public:
  // 赋值运算符重载
  Date& operator=(const Date& x)
  {
    if (this != &x)
    {
      _year = x._year;
      _month = x._month;
      _day = x._day;
    }
    return *this;
  }
  Date(int year = 2023, int month = 5, int day = 5)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2023, 5, 6);
  Date d2(2003, 5, 7);
  Date d3;
  d1 = d2 = d3;
  d1.Print();
  d2.Print();
  d3.Print();
}
*****
2023 5 5
2023 5 5 
2023 5 5
*****

传参用引用: 相信大家应该还记得 自定义类型传值传参和赋值是会自动调用拷贝构造的, 如果我们不写拷贝构造函数, 将会使用浅拷贝.⇒ 所以自定义类型传参的时候就要传递引用, 由于是赋值, 传递的对象是不会发生变化的, 建议用const修饰一下

返回值用引用: 如果我们返回的是一个引用的话, 就不会生成一个临时拷贝, 那么也就不会调用拷贝构造(浅拷贝看的不是很明显, 深拷贝的代价更高)

疑问区:


写到这里, 有些小伙伴应该会问: 这里为什么可以返回 *this啊, 不是出了函数就已经销毁了吗? ⇒ 那么这样就不能返回引用啊??

this 指针作为形参, 作用域是在函数内部没错, 但是我们返回的是 *this, *this 就是 (以 d3 = d4为例子), 外面的对象d3啊, 所以是可以传 *this回去的.


判断是否自己赋值给自己, 为啥非要用 this != &d ?? 为什么不用 *this != d ??

this 和 &d 这两个都是内置类型, this 是 (以 d3 = d4为例子) d3的地址, 而&d 是d4的地址⇒ 可以直接使用操作符进行操作

*this 和 d 其实都是自定义对象了, *this 就是d3, 而 d是d4的引用⇒ 两个自定义类型使用操作符 !=, 我们还要对 != 进行操作符重载.

⇒ 比较下来, 还是比较两者的地址比较方便


68b5d5dbdfc141b4a15a20fd3639757f.png



誓言是世界上最靠不住的东西,只有你对别人还有用的时候,别人才会遵守誓言。


相关文章
|
关系型数据库 MySQL Java
MySQL单表膨胀优化之MyCat分库分表
MySQL单表膨胀优化之MyCat分库分表
362 0
|
关系型数据库 Go PostgreSQL
golang pgx自定义PostgreSQL类型
golang的pgx驱动提供了大约70种PostgreSQL类型支持,但还是有一些类型没有涵盖,本文介绍如何自己编写代码支持特殊的类型。
Stream方法使用-filter、sorted、distinct、limit
Stream方法使用-filter、sorted、distinct、limit
371 0
|
8月前
|
移动开发 安全 API
VMware vCenter Server 7.0U3u 发布 - 集中管理 vSphere 环境
VMware vCenter Server 7.0U3u 发布 - 集中管理 vSphere 环境
280 0
VMware vCenter Server 7.0U3u 发布 - 集中管理 vSphere 环境
|
消息中间件 NoSQL Kafka
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
893 0
|
自然语言处理 物联网 图形学
.NET 技术凭借其独特的优势和特性,为开发者们提供了一种高效、可靠且富有创造力的开发体验
本文深入探讨了.NET技术的独特优势及其在多个领域的应用,包括企业级应用、Web应用、桌面应用、移动应用和游戏开发。通过强大的工具集、高效的代码管理、跨平台支持及稳定的性能,.NET为开发者提供了高效、可靠的开发体验,并面对技术更新和竞争压力,不断创新发展。
666 7
|
安全 Java 索引
让星星⭐月亮告诉你,(示例实战)enum枚举类九大特性详解
本文详细解析了Java枚举类的九大特性,包括定义枚举、迭代、Switch语句、枚举操作方法、构造函数和方法、抽象方法等,并通过实战示例展示了如何在Java中定义和使用枚举。通过阅读本文,你将能够掌握枚举类的使用技巧,提升编码水平。关键词:Java, 枚举类, Enum, 特性, 实战示例。
211 0
|
移动开发 前端开发 JavaScript
使用React Native进行跨平台移动开发:技术探索与实践
【8月更文挑战第10天】React Native以其跨平台、高性能、易学习等优势,在移动开发领域取得了显著的成果。通过合理使用React Native,开发者可以更加高效地开发出高质量、低成本的移动应用。然而,在享受React Native带来的便利的同时,我们也需要关注其潜在的挑战和限制,并通过不断学习和实践来提升我们的开发能力。
|
消息中间件 SQL 分布式计算
2021年全网最详细大数据常见端口汇总
2021年全网最详细大数据常见端口汇总
1157 1
2021年全网最详细大数据常见端口汇总
|
人工智能 安全
【保真】揭秘目前唯一能使用Sora的官方渠道 —— OpenAI Red Teaming Network
本文介绍了唯一官方认证的使用Sora的方法——加入OpenAI Red Teaming Network,同时警告读者避免被虚假的Sora使用渠道所骗。文章详细说明了加入OpenAI Red Teaming Network的流程、目的以及所需条件,为AI领域的专家和爱好者提供了一个独一无二的机会,提前体验和评估OpenAI的最新AI技术。