当前位置: 首页 > news >正文

240414 类和对象

一、运算符重载

1. 简介

函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
【注意事项】
  • 不能通过连接其他符号来创建新的操作符,比如 operator@
  • 重载操作符必须有一个类类型参数,反例:int operator+(int i, int j) 
  • .* :: (域作用限定符),  sizeof ?: (单目选择), . (访问对象成员) 这5个运算符不能重载

 .* 操作符示例

class OBJ {
public:void func() {cout << "void func()" << endl;}
};typedef void(OBJ::*PtrFunc)(); //定义成员函数指针int main() {//函数指针//void (*ptr)();//成员函数要加&才能取到函数指针PtrFunc fp = &OBJ::func;OBJ tmp;//定义OBJ类型对象tmp//取出后用.*调用成员函数指针(tmp.*fp)();return 0;
}

【注】fp 不是 tmp 的成员,*fp 才是

在C++中,当尝试将操作重载为全局函数时,会面临无法直接访问类的私有成员的问题。以下是几种解决策略:

1. 实现访问器和修改器(Getter 和 Setter)方法

为了安全地访问和修改类的私有成员变量,可以定义一对公共的成员函数,即访问器(getter)和修改器(setter)。这些函数将提供对私有成员的间接访问,同时保持封装的完整性。

2. 使用友元函数

可以将特定的全局函数声明为类的友元。这样,该全局函数将获得访问类内部私有和保护成员的权限,从而可以在不破坏封装的前提下,实现对私有成员的直接操作。

3. 重载为成员函数(标准实践)

在C++编程实践中,通常推荐将操作重载为类的成员函数。这种方式不仅允许直接访问类的私有成员,而且维护了类的封装性,符合面向对象设计的原则。此外,成员函数还可以利用this指针访问当前对象的成员,提高了代码的清晰度和可维护性。

重载函数优先在类里寻找

原因:作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

2. 赋值重载介绍

Date d1(2021, 2, 21);
//拷贝构造:基于一个已存在的对象创建初始化一个新的对象实例
Date d2(d1);
Date d3 = d1;Date d4(2024, 4, 13);
//赋值拷贝(赋值重载):一个已经存在的对象,拷贝赋值给另一个已经存在的对象
d1 = d4;

 赋值操作的返回值是左操作数

为了连续赋值,可以将赋值重载函数调整如下:

Date operator=(const Date& d) {if (this != &d) {_y = d._y;_m = d._m;_d = d._d;}return *this;
}

 3. 引用返回

(1)介绍

为了减少拷贝采用引用返回:

局部变量 dfunc() 函数返回之前就已经创建,在函数结束时就会被销毁。因此,在 main() 函数中使用 ret 引用时,实际上是在使用一个不再存在的对象的引用。

【总结】

  1. 返回对象是一个局部或临时对象,出了当前 func 函数作用域就析构销毁了,那么不能用引用返回,用引用返回存在风险,因为引用对象在 func 函数栈帧已经销毁了
  2. 出了函数作用域,返回对象还没有析构,才能用引用返回减少拷贝,提高效率
    Date& func() {static Date d(2024, 4, 14);return d;
    }int main() {Date& ret = func();ret.Print();return 0;
    }

    返回对象生命周期到了,会析构,传值返回;生命周期没到,不会析构,传引用返回

(2)对比
int main() {Date d1(2024, 4, 1);Date d2(d1);Date d3(1000, 10, 10);d1 = d2 = d3;return 0;
}【1】
Date& operator=(const Date& d) // 引用返回
结果:
Date(const Date& d)
~Date()
~Date()
~Date()【2】
Date operator=(const Date& d) // 传值返回
结果:
Date(const Date& d)
Date(const Date& d)
Date(const Date& d)
~Date()
~Date()
~Date()
~Date()
~Date()

4. 赋值重载特性

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

【注】内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。跟拷贝构造类似,Date 或者 MyQueue 默认生成的赋值就够用了,但是类似 Stack/List 等都需要自己实现赋值重载。

赋值运算符不能重载成全局函数。

二、日期类

1. 日期比较大小

【注】可以复用已实现的重载函数

bool Date::operator<(const Date& d) {if (_y < d._y) {return true;}else if (_y == d._y) {if (_m < d._m) {return true;}else if (_m == d._m) {return _d < d._d;}}return false;
}bool Date::operator<=(const Date& d) {return *this < d || *this == d;
}bool Date::operator>(const Date& d) {return !(*this <= d);
}bool Date::operator>=(const Date& d) {return !(*this < d);
}bool Date::operator==(const Date& d) {return _y == d._y && _m == d._m && _d == d._d;
}bool Date::operator!=(const Date& d) {return !(*this == d);
}
2. 日期加减天数
(1)数学计算简例说明

先加到天上,天满进位月,月满进位年

   2024 5 21

             +60

= 2024 5 81

              -31【注:5月份对应天数是31天】

= 2024 6 50

              -30【注:6月份对应天数是30天】

= 2024 7 20

   2024 5  21

               -60

= 2024 5 -39

              +30【注:4月份对应天数是31天】

= 2024 4  -9

              +31【注:3月份对应天数是31天】

= 2024 3 22

(2)+与+=,-与-=
Date& Date::operator+=(int days) {if (days < 0) {return *this -= -days;}_d += days;while (_d > GetMonthDay(_y, _m)) {_d -= GetMonthDay(_y, _m);++_m;if (_m > 12) {++_y;_m = 1;}}return *this;
}Date Date::operator+(int days) {// tmp出作用域销毁,用传值返回 Date tmp = *this;// 拷贝构造/*tmp._d += days;while (tmp._d > GetMonthDay(tmp._y, tmp._m)) {tmp._d -= GetMonthDay(tmp._y, tmp._m);++tmp._m;if (tmp._m > 12) {++tmp._y;tmp._m -= 12;}}*/// 复用精简版tmp += days;return tmp;
}Date& Date::operator-=(int days) {if (days < 0) {return *this += -days;}_d -= days;while (_d < 0) {--_m;if (_m < 1) {--_y;_m = 12;}_d += GetMonthDay(_y, _m);}return *this;
}Date Date::operator-(int days) {Date tmp = *this;tmp -= days;return tmp;
}
3. 日期++/--

前置和后置++/--的本质区别在于返回值不同,下面以++为例:

使用场景:

  • 当需要立即使用变量的更新后的值时,应该使用前置 ++
  • 当需要使用变量的当前值,并且同时希望在稍后某个点该变量的值增加 1 时,可以使用后置 ++
// ++d
// 自定义类型中能用前置++尽量使用前置++
Date& Date::operator++() {*this += 1;return *this;
}// d++
// 为了和前置++区分构成重载,给后置++增加了一个int形参
Date Date::operator++(int) {Date tmp = *this;*this += 1;return tmp;
}++d1 => d1.operator++()
d1++ => d1.operator++(1)
// 这里形参名不重要,接收值不影响,也不需要用到
// 该参数仅仅是为了和前置++区分构成重载

【注】区分函数重载和运算符重载

函数重载:让函数名相同、参数不同的函数存在

运算符重载:让自定义类型可以用运算符,并且控制运算符的行为,增强可读性

多个同一运算符重载可以构成函数重载,比如下方示例:

Date operator-(int days) // 日期减天数
int operator-(const Date& d) // 日期减日期
4. 日期相减
int Date::operator-(const Date& d) {Date max = *this;Date min = d;int flag = 1;if (*this < d) {max = d;min = *this;flag = -1;}int cnt = 0;while (min != max) {++min;++cnt;}return cnt * flag;
}
5. 流插入、流提取
(1)std::ostream::operator<<

C++标准库中的一个成员函数,定义在 ostream 类中,用于实现向输出流插入(插入操作符,也称为“流插入器”)各种类型的数据,如字符串、整数、浮点数等。当你使用标准输出流 std::cout 时,实际上就是在调用这个操作符。

内置类型可以直接使用,因为库里面已经写好了

自动识别类型,本质是因为这些流插入重载构成函数重载

void Date::operator<<(ostream& out) {out << _y << " 年 " << _m << " 月 " << _d << " 日" << endl;
}

运算符重载中,参数顺序和操作数顺序是一样的

operator<< 想重载成成员函数可以,但使用起来不符合正常逻辑,建议重载成全局函数

Date ostream 顺序(不符合习惯)

d1.operator<<(cout);
d1 << cout;

ostream Date 顺序

cout << d1;

写成成员函数能让 ostream 变为第一位吗?

不行,因为隐含的 Date* this 已经把第一个参数给占用了


为了解决链式流插入的问题,返回 ostream& 类型的 out

// 链式流插入
ostream& operator<<(ostream& out, const Date& d) {out << d._y << " 年 " << d._m << " 月 " << d._d << " 日" << endl;return out;
}

友元

class Date {// 友元函数声明(针对私有成员变量访问)friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
(2)std::ios_base::sync_with_stdio

std::ios_base::sync_with_stdio 主要用于控制C++标准I/O流(如 cin, cout, cerrclog)是否应该与C语言的标准I/O库(如 stdin, stdout, stderr)同步。默认情况下,C++标准I/O流是与C标准I/O库同步的,这意味着它们共享底层文件描述符。

这个函数可以接受一个布尔值作为参数:

  • 如果参数为 true(这是默认行为),那么C++标准I/O流将与C标准I/O流同步。
  • 如果参数为 false,则会取消这种同步。

在某些情况下,取消同步是有益的。例如,在程序需要高性能输入输出时,取消同步可能会减少一些不必要的锁操作,从而提高性能。这是因为C++ I/O系统和C I/O系统在处理缓冲区时可能有不同的内部锁定机制,当它们同步时,为了保证线程安全,可能会有额外的开销。

在实际编程中,特别是在编写一些需要高效读写的竞赛代码或高性能应用时,开发者可能会选择调用 std::ios_base::sync_with_stdio(false) 来禁用同步以提升性能。

此外,通常还会配合 std::cin.tie(nullptr); 使用,这会解除 cincout 之间的绑定,因为在默认情况下,cincout 是绑定在一起的,即当 cout 进行输出后,cin 在没有输入前会阻塞等待。

 6. const 成员函数

【问题场景】

【解决办法】

 

 const 修饰 *this,本质上改变 this 的类型

const Date* const this

const 对象可以调用Print( ) => 权限的平移

非 const 对象亦可调用Print( ) => 权限的缩小

对于常量const d1和非常量d2,若operator<未声明为const成员函数,则表达式d1 < d2将引发编译错误,而d2 < d1可正常执行。编译错误发生在d1 < d2是因为常量对象不能调用可能修改其状态的非const成员函数,而d2 < d1没有这个问题,因为d2不是常量。

不修改成员的成员函数建议声明为 const

7. 取地址操作符重载*
class B {
public:// 我们不写编译器会自己实现,写了编译器就不会自己实现// 实践中一般不会自己实现,除非不想别人取到该类型对象的真实地址B* operator&() {cout << "B* operator&()" << endl;return this;}const B* operator&() const{cout << "const B* operator&() const" << endl;return this;}
private:int _B1 = 1;int _B2 = 2;int _B3 = 3;
};int main() {B b1;const B b2;cout << &b1 << endl;cout << &b2 << endl;return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • rancher搭建k8s及jenkins自动化部署
  • 【nginx】
  • Python中的装饰器及其应用场景
  • chrome 插件开发入门
  • C语言-第六章-加餐:其他自定义类型
  • C语言第一周课
  • 用RPC Performance Inspector 优化你的区块链
  • java设计模式day02--(创建型模式:工厂模式、原型模式、建造者模式)
  • 【AIGC半月报】AIGC大模型启元:2024.09(上)
  • 探究零工市场小程序如何改变传统兼职模式
  • vscode安装使用plantuml插件
  • 集成电路学习:什么是SDK软件开发工具包
  • vivado 添加多循环路径
  • 滑动窗口——632. 最小区间
  • 【原创】edge-tts与基于mpv的edge-playback,使命令行和Python的Text To Speech唾手可得
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • GraphQL学习过程应该是这样的
  • JavaScript-Array类型
  • java中的hashCode
  • js数组之filter
  • Node 版本管理
  • redis学习笔记(三):列表、集合、有序集合
  • Ruby 2.x 源代码分析:扩展 概述
  • 订阅Forge Viewer所有的事件
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 仿天猫超市收藏抛物线动画工具库
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 为什么要用IPython/Jupyter?
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • 大数据全解:定义、价值及挑战
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #考研#计算机文化知识1(局域网及网络互联)
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • (k8s中)docker netty OOM问题记录
  • (poj1.2.1)1970(筛选法模拟)
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (接口封装)
  • (利用IDEA+Maven)定制属于自己的jar包
  • (十六)串口UART
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (转)树状数组
  • (转)一些感悟
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET Reactor简单使用教程
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .net通过类组装数据转换为json并且传递给对方接口
  • .Net语言中的StringBuilder:入门到精通
  • @ComponentScan比较
  • @SuppressWarnings注解
  • [ A*实现 ] C++,矩阵地图
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解