运算符重载(下)

简介: 运算符重载

🖊④-=和-


//借位-天数
Date& Date::operator-=(int day)
{
  if (day < 0)
  {
  return *this += -day;
  }
  _day -= day;
  while (_day <= 0)
  {
  --_month;
  if (_month == 0)
  {
    --_year;
    _month = 12;
  }
  _day += GetMonthDay(_year, _month);
  }
  return *this;
}
Date Date::operator-(int day)
{
  Date ret(*this);
  ret -= day;
  return ret;
}


🖊⑤日期-日期


如果我想知道两个日期之间的差值,该如何计算呢?

int operator-(const Date& d)
  {
  int flag = 1;
  Date max = *this;
  Date min = d;
  if (*this < d)
  {
    max = d;
    min = *this;
    flag = -1;
  }
  int day = 0;
  while (min < max)
  {
    ++(min);
    ++day;
  }
  return day * flag;
  }


👓2.2前置++和后置++重载


我们知道前置++返回的是++之后的结果,后置++返回的是++之前的结果。这里C++的标准形式是:


1、前置++:类型&   operator++()


2、后置++:类型      operator++(int)


以日期类为例:

//前置++
Date& operator++()
 {
     _day += 1;
     return *this;
 }
//后置++
Date operator++(int)
 {
     Date temp(*this);
     _day += 1;
     return temp;
 }

1、为什么后置++会有参数int呢?有什么含义呢?


它并无多大意义,甚至说毫无用处,这是C++规定的,后置++重载时多增加一个int类型的参数,但调用参数时该参数不用传递,编译器自动传递。


2、前置++为什么返回引用,而后置++返回值呢?


①、前置++返回+1之后的结果,因为this指向的对象函数结束之后不会销毁,故以引用方式返回提高效率。


②、后置++是先使用后++,需要返回+1之前的旧值,故需在实现时先将this拷贝一份,然后给this++。


当然,前置--和后置--也与之类似:


//后置--
Date operator--(int)
 {
     Date temp(*this);
     _day -= 1;
     return temp;
 }
//前置--
Date& operator--(int)
 {
     _day -= 1;
     return *this;
 }


🏆三、<<重载和>>重载


int main()
{
  int a = 10;
  double d = 2.28;
  char c = 'f';
  cout << a <<" "<< d <<" "<< c << endl;
  return 0;
}

之前博主讲述的在C++阶段,引入了cout 和<<用于打印,cin和>>用于输出,它们拥有自动识别类型的功能。那么,它们真的有这么神奇,可以自动识别吗?当然不是,它们本身也是用C语言封装的。他们的头文件分别是<ostream>和<istream>。合起来也就是我们常引的


<iostream>头文件。


1669269333044.jpg


通过头文件,我们也可以看出来C++的cout和cin可以对内置类型(int,double,float,char等)类型进行识别,如果我现在想打印一个类对象,或者输入一个类对象成员,编译器本身是不支持,而我们现实是有这种需求的,所以就有了<<重载和>>重载用于解决这类问题。当然,我们要想实现也需要解决一些问题。


👓3.1 <<重载和>>重载在全局定义


可能有的老铁困惑了,之前讲的重载都在类里面定义,怎么到了这里不在类里面定义呢?这是有原因的。我们可以在类里面定义,只不过它不太符合习惯,没可读性。为什么呢?


1669269349183.jpg


当我们写到类里面的时候,我们按照正常去调用,(cout<<d1)会报错。


1669269360545.jpg


当我们采用这种别扭的调用方式时(d1.operator << (cout)或者d1 << cout),可以调用成功,为什么呢?这里就要说到一个问题:类成员函数存在隐式传参,也就是之前提及的this,this默认以第一个参数传递,所以我们如果定义在类里面,只能以这样别扭的形式调用。怎么解决?定义到全局!!


这里博主为了方便后面的讲解,将会以三个文件来讲解:


1669269369593.jpg


这是博主创建的三个文件。之后的说明将围绕这三个文件展开。


定义到全局的问题:


1、无法访问私有,C++规定在类外无法访问private内的内容。


2、定义到头文件的函数如果在两个以及两个以上的文件中包含这个头文件在链接时会有重定义错误,怎么解决。


👓3.2无法访问私有


当定义到全局我们无法访问私有怎么办?常用的方式有两种:


1、在类里面定义一个函数,通过这个函数把private内的成员带出来,我们再通过这个函数访问到private,这是Java常喜欢的方式,我们C++也可以,但我们不这么干。


2、友元声明。友元的关键字是friend,当我们哪个函数需要访问到private内部时,我们对这个函数的声明加一个friend即可。


1669269385700.jpg


当我们加上友元之后,就可以访问private了,但是这里还有报错,这就涉及到另一个问题:重定义链接的问题。


👓3.3重定义链接问题


这是一个在C语言预处理常说的老问题:定义到头文件的函数,如果在两个及两个以上头文件包含这个头文件,就会在合并符号表,链接的时候出现重定义问题。


解决方案有三种。


1、声明和定义分离,声明在头文件,定义在实现函数的文件。


2、定义为static。


3、内联函数


第一个方案是常用的一种手段,一般的函数都是这么处理的,这里主要谈一下第二和第三种方案。


🖊①定义为static


1669269405086.jpg


为什么定义为static可以呢?


我们知道static可以改变变量的生命周期,将其存储到静态区,但是我们很容易忽略的一个很重要的特点是:


在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。也就是说,被static关键字修饰的函数,只能在当前文件展开,不能在其他文件展开,也就避免了链接重定义的问题。


🖊②定义为内联函数


内联函数也是有效避免重定义问题的方法,为什么这里要把<<重载和>>重载定义为内联函数呢?除了重定义,还有一个关键是这两个重载函数是经常被调用使用的,而这一点契合内联函数的使用场景。

/*Date.h*/
class DateB
{
  friend void operator<<(ostream& out, const DateB& d);
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
inline void operator<<(ostream& out, const DateB& d)
{
  out << d._year << d._month << d._day << endl;
}
/*test.c*/
#include"Date.h"
void TestDate1()
{
  DateB d1(2022, 10, 11);
  //d1.operator<<(cout);
  cout<<d1;
}
int main()
{
  TestDate1();
  return 0;
}

这里还有最后一个问题没解决,就是和前面讲的赋值重载问题一样,返回值问题,返回void只能输出或输入一个类对象,我们应该让其返回类类型还是cout/cin类型呢?


1669269432673.jpg


这是我们常见的连续打印的模式,如果很显然从左往右打印,当打印完d1之后,很显然cout<<d1返回cout才能继续打印d2,所以返回的应该是ostream&.


>>重载和<<重载大致一样,只不过>>重载cin的类型是istream,同时传参的时候也不能加const限制,否则无法修改。


以日期类为例,>>重载和<<重载的形式:

class DateB
{
  friend ostream& operator<<(ostream& out, const DateB& d);
  friend istream& operator>>(istream& in, DateB& d);
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
inline ostream& operator<<(ostream& out, const DateB& d)
{
  out << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;
  return out;
}
inline istream& operator>>(istream& in, DateB& d)
{
  in >> d._year >> d._month >> d._day;
  return in;
}

🏆四、const 成员


/*Date.h*/
class DateB
{
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  void Print()
  {
  cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
/*test.c*/
void TestDate1()
{
  DateB d1(2022, 10, 11);
  const DateB d2(2022, 10, 8);
  d1.Print();
  d2.Print();
}
int main()
{
  TestDate1();
  return 0;
}

1669269466068.jpg

当我const定义一个类对象d2,当我去调用类成员函数的时候,会发现会报错,这是为什么呢?


调用类成员函数会默认传this,this的定义是什么呢?


Date * const this,而我们隐式传参传递的是const Date*d2,这里就考察const的理解,我们this这个const只是限定不能去修改this名称,所以这里涉及到权限的放大问题,我们知道权限放大是被禁止的,可以缩小不能放大,那么这里怎么解决呢?


我们只需在函数后面加个const就可以缩小this的权限。


C++规定:将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰的是该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。


1669269475389.jpg


那么我们可以这样操作:

/*Date.h*/
class DateB
{
public:
  DateB(int year = 1, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  void Print()const
  {
  cout << _year << " " << _month << " " << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
/*test.c*/
void TestDate1()
{
  DateB d1(2022, 10, 11);
  const DateB d2(2022, 10, 8);
  d1.Print();
  d2.Print();
}
int main()
{
  TestDate1();
  return 0;
}

1669269492605.jpg


这里还有一个问题,为什么d1也可以调用呢?因为指针和引用的权限授予可以缩小而不能放大.所以我们在以后的类成员函数中可以在后面加const限制this,以提高代码的安全性。


🏆五、取地址及const取地址操作符重载


这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{ 
public :
 Date* operator&()
 {
 return this ;
 }
 const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
}


这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。

相关文章
|
3天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
2天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1473 6
|
4天前
|
安全 数据可视化 网络安全
安全无小事|阿里云先知众测,为企业筑牢防线
专为企业打造的漏洞信息收集平台
1306 2
|
3天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
320 160
|
3天前
|
人工智能 自然语言处理 API
n8n:流程自动化、智能化利器
流程自动化助你在重复的业务流程中节省时间,可通过自然语言直接创建工作流啦。
366 4
n8n:流程自动化、智能化利器
|
12天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
1485 7
|
5天前
|
人工智能 API 开发工具
Skills比MCP更重要?更省钱的多!Python大佬这观点老金测了一周终于懂了
加我进AI学习群,公众号右下角“联系方式”。文末有老金开源知识库·全免费。本文详解Claude Skills为何比MCP更轻量高效:极简配置、按需加载、省90% token,适合多数场景。MCP仍适用于复杂集成,但日常任务首选Skills。推荐先用SKILL.md解决,再考虑协议。附实测对比与配置建议,助你提升效率,节省精力。关注老金,一起玩转AI工具。
|
2天前
|
Linux 数据库
Linux 环境 Polardb-X 数据库 单机版 rpm 包 安装教程
本文介绍在CentOS 7.9环境下安装PolarDB-X单机版数据库的完整流程,涵盖系统环境准备、本地Yum源配置、RPM包安装、用户与目录初始化、依赖库解决、数据库启动及客户端连接等步骤,助您快速部署运行PolarDB-X。
229 1
Linux 环境 Polardb-X 数据库 单机版 rpm 包 安装教程
|
13天前
|
人工智能 Rust 运维
这个神器让你白嫖ClaudeOpus 4.5,Gemini 3!还能接Claude Code等任意平台
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
1349 17
|
3天前
|
自然语言处理 监控 测试技术
互联网大厂“黑话”完全破译指南
互联网大厂黑话太多听不懂?本文整理了一份“保姆级”职场黑话词典,涵盖PRD、A/B测试、WLB、埋点、灰度发布等高频术语,用大白话+生活化类比,帮你快速听懂同事在聊什么。非技术岗也能轻松理解,建议收藏防踩坑。
279 161

热门文章

最新文章