C++类与对象基础(6)

       (注:本篇文章介绍部分内容时,需要用到上盘文章中日期类的代码,文章链接如下:C++类与对象基础(5)——日期类的实现-CSDN博客​​​​​​

目录

1. 运算符重载的相关补充:

1.1流运算符重载出现的问题:

1.2 针对上述问题的解决方法:

1.2.1 通过创建友元函数来实现对私有变量的访问:

2. const成员函数:

2.1 const成员函数基本介绍:

2.2 什么类型的函数需要加:

3.取地址及const取地址操作符重载:


4

1. 运算符重载的相关补充:

1.1流运算符重载出现的问题:

       在对对象进行打印时,一般会在类中编写一个用于打印的成员函数用于打印,即:

void Date::Print()
{
	cout << _year << " " << _month << " " << _day << endl;
}
int main()
{
	Date d(2024, 1, 8);
	d.Print();

	return 0;
}

运行结果如下:

在针对C++系列的第一篇文章中,就提到了在C++中,输出变量的方法不光只有printf函数,也可以使用流插入cout。上面所展示的代码虽然用到了cout,但是并不是直接调用,而是将cout封装在一个类的成员函数中,进行调用的。如果针对上面的对象d,直接利用cout进行打印,即:

int main()
{
	cout << d;

	return 0;
}

 运行代码,此时编译器会显示错误,即:

错误的原因在之前介绍运算符重载的时候提到过:对于自定义类型不能直接调用操作符,而是需要利用运算符重载。因此,为了实现自定义类型变量,即:d,在类中加入一个运算符重载,即:

(注:本篇文章所有的运算符重载都采用声明和定义分离的方式)

void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

 运行下方代码:

int main()
{
	Date d1(2024, 1, 8);
	cout << d1;
	return 0;
}

此时仍然显示运行错误,但是将上方代码更改为下面的形式:

int main()
{
	Date d1(2024, 1, 8);
	/*cout << d1;*/

	d1 << cout;
	return 0;
}

代码成功运行,结果如下:

这是因为,对于双操作数的运算符,第一个参数是左操作数,第二个参数是右操作数,之前的文章中多次提到,对于成员函数来说,通常会有一个隐藏的参数,即this指针。所以,针对上面的运算符重载,其完整的参数应该为:

void Date::operator<<(Date* this, ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

所以,这就解释了为社么上面打印自定义类型d1时,cout<<d1这种形式会造成编译错误,而d1<<cout可以正常运行的原因。虽然将代码改为上述形式后,可以正常使用运算符cout,但是,与平时利用cout的使用习惯不符,改进的方法将在下一小节中进行介绍

1.2 针对上述问题的解决方法:

    上面提到,造成问题的原因时因为成员函数会有一个隐藏的参数this,为了避免此问题,可以将运算符重载的声明放在类之外,即作为一个全局函数,而非一个成员函数。对于全局函数,没有隐藏的参数this,因此可以人为定义参数的顺序,即:
 

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

虽然解决了操作数的顺序问题,但是由于在运算符中,三个成员变量  _year, _month, _day会受到访问限定符private的影响。因此,还需要解决成员变量的访问问题。对于此问题的解决,文章给出一种方法:


1.2.1 通过创建友元函数来实现对私有变量的访问

实现方法只需要在类中加上友元函数即可,即:
 

friend void operator<<(ostream& out, const Date& d);

(注:对于友元的相关知识将在下一篇文章中进行介绍,本文中只给出使用方法)

在加入了友元函数后,上述的运算符重载即可正常进行使用,即:

int main()
{
	Date d1(2024, 1, 8);
	cout << d1;
	return 0;
}

运行结果如下:

对于流提取,采用和流插入一样的实现方法,即:
创建运算符重载:

void operator>>(istream& in,Date& dd)
{
	in >> dd._year >> dd._month >> dd._day;
}

 在类中加入友元函数:

friend void operator>>(istream& in, Date& dd);

测试流提取的功能:
 

int main()
{
	Date d1(2024, 1, 8);
	cin >> d1;
    cout << d1;
	return 0;
}

运行结果如下:

2. const成员函数:

2.1 const成员函数基本介绍:

文章在介绍刘运算符重载的时候举了一个例子,即创建一个对象,并且将其打印,即:

Date d1(2024, 1, 8);

在之前对于引用进行介绍的文章中,提到了常饮用这个概念,即在引用的前面加上const,如果在上面给出的自定义类型d1前加上const,再对这个自定义类型进行打印,即:
 

int main()
{
	const Date d3(2024, 1, 9);
	d3.Print();
	return 0;
}

运行代码,此时编译器会显示错误。

具体错误原因与成员函数的隐藏变量this以及权限变化有关。在常饮用那一节就提到过:权限平移或者缩小而不能放大,对于前面加了const的自定义类型d3,当d3向函数Print传递参数时,其参数类型为const \, Date*,而函数Printthis的类型为Date* this,因此,在传递参数的过程中,涉及了权限放大。对于此问题的解决方法需要在函数后面加上一个const

(注:如果函数的声明和定义分离,则在声明和定义后都需要加上const

//函数声明
void Print() const;
//函数定义
void Date::Print() const
{
	cout << "void Date::Print() const" << endl;
	cout << _year << " " << _month << " " << _day << endl;
}

运行结果如下:

上面提到,权限不能放大,但是可以缩小或者平移,因此,非const类型的变量可以调用const类型的函数,例如:

int main()
{
	Date d4(2222, 2, 22);
	d4.Print();

	return 0;
}

运行结果如下:

 

2.2 什么类型的函数需要加const

在日期类中,给了很多的关于运算符重载的例子,例如:                               

bool operator>(Date& d);

利用上述运算符对下面两个自定义类型变量进行比较,即:

int main()
{
	const Date d3(2024, 1, 9);
	d3.Print();
	Date d4(2222, 2, 22);
	d4.Print();

	bool ret1 = (d3 > d4);
	cout << ret1 << endl;

	return 0;
}

运行代码,此时编译器会报错。

错误原因依旧涉及权限放大的问题,通过两个自定义类型的传递参数的顺序可以发现,d3在传递参数时,也涉及了权限的放大,因此,也需要将上述函数进行更改,即:

bool operator>(Date& d)const;
bool Date::operator>(Date& d) const
{
	if (_year > d._year)
	{
		return true;
	}
	else if ((_year == d._year) && (_month > d._month))
	{
		return true;
	}
	else if ((_year == d._year) && (_month == d._month) && (_day > d._day))
	{
		return true;
	}
	else
	{
		return false;
	}
}

通过上面的两个例子可以看到,const类型的对象可以调用const类型函数,非const型的对象也可以调用const类型函数。因此,对于能够定义成const类型的成员函数,一般都需要加上const

前面的例子中,不难发现,在函数声明后面加上const的作用,主要是修饰*this,因此,如果一个成员函数涉及到对象的更改,则不能用const修饰。因此,对于日期类中的所有函数类型,可以加const的如下:

	bool operator==(Date& d)const;
	bool operator!=(Date& d)const;
	bool operator>(Date& d)const;
	bool operator>=(Date& d)const;
	bool operator<=(Date& d)const;
	bool operator<(Date& d)const;
    Date operator+(int day)const;
    Date operator-(int day)const;

需要注意,文章上面给出的流插入、流提取这两个函数由于不是成员函数,故不能用const修饰。

3.取地址及const取地址操作符重载:

       在前面的部分,介绍了默认成员函数中的4个,本部分将给出剩余的两个,由于这两个默认成员函数的实用意义远小于构造、析构、拷贝构造、赋值重载,因此文章只给出这两个函数的格式以及简单的应用:

      格式如下:

	//取地址操作符重载
	Date* operator&()
	{
        cout << "Date* operator&()" << endl;
		return this;
	}

	//const取地址操作符重载
	const Date* operator&() const
	{
        cout << "const Date* operator&() const" << endl;
		return this;
	}

       需要注意的时,这两个函数不光类型不同,其参数类型也不同,对于const取地址操作符重载,第一个const用于修饰函数的返回值,第二个const修饰的是*this。所以二者的参数类型不同。

下面给出测试代码来对取地址操作符重载以及const取地址操作符重载的调用进行演示:
 

const Date d3(2024, 1, 9);
	//d3.Print();
	Date d4(2222, 2, 22);
	//d4.Print();
	cout << &d3 << endl;
	cout << &d4 << endl;

运行结果如下:

        不难发现,自定义类型d3const修饰,因此优先调用const取地址操作符重载,自定义类型d4没有被const修饰,优先调用取地址操作符重载。 加入,在这两个函数中去掉一个,例如去掉取地址操作符重载,即:

//取地址操作符重载
	/*Date* operator&()
	{
		cout << "Date* operator&()" << endl;
		return this;
	}*/

	//const取地址操作符重载
	const Date* operator&() const
	{
		cout << "const Date* operator&() const" << endl;
		return this;
	}

再运行下面的代码:

const Date d3(2024, 1, 9);
	//d3.Print();
	Date d4(2222, 2, 22);
	//d4.Print();
	cout << &d3 << endl;
	cout << &d4 << endl;

运行结果为:

当两个函数都去掉后,即:
 

	//取地址操作符重载
	/*Date* operator&()
	{
		cout << "Date* operator&()" << endl;
		return this;
	}*/

	//const取地址操作符重载
	/*const Date* operator&() const
	{
		cout << "const Date* operator&() const" << endl;
		return this;
	}*/

再次运行下方代码:
 

cout << &d4 << endl;
	cout << &d3 << endl;
	

运行结果如下:

此时代码依旧正常运行。这是因为取地址操作符重载和const取地址操作符重载是默认构造函数,他的性质与构造函数类似,当不人为编写上述两个重载时,编译器会自动生成,当认为编写两个重载时,编译器会去调用已经编写好的。 

4. 勘误:

  由于个人能力有限,书中难免出现汉字拼写错误、代码意义解释错误、内容逻辑以及理解错误等不同类型的错误。首先感谢各位大佬能花掉自己宝贵的时间阅读此文章,愿大佬们斧正,发现错误可以通过私信联系,本人不胜感激。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303417.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言爬虫程序采集58商铺出租转让信息

为了找到一个好店铺好位置&#xff0c;往往要花费很大精力和财力过去寻找&#xff0c;就有有某些出租平台但是一个个查找下来也是十分麻烦&#xff0c;所以我利用我们的C语言基础&#xff0c;给大家写个商品转租的爬虫程序&#xff0c;让找店铺不在那么费时费力&#xff0c;至少…

图像去噪——CBDNet网络训练自己数据集及推理测试,模型转ONNX模型(详细图文教程)

CBDNet 主要由两个子网络组成&#xff1a;噪声估计子网络和去噪子网络。噪声估计子网络用于估计图像的噪声水平&#xff0c;而去噪子网络用于去除图像中的噪声。 CBDNet 的优势在于&#xff1a; 它采用了更真实的噪声模型&#xff0c;既考虑了泊松-高斯模型&#xff0c;还考虑…

37-数据类型,一元运算符typeof,字符串string,布尔Boolean,未定义undefined,空null,数组Array

<body><script>// 0.1加0.2不等于0.3&#xff0c;正确的运算方法如下console.log(0.10.2);var x 0.1;var y 0.2;console.log((x*10y*10)/10);</script> </body> 简单数据类型&#xff08;5种&#xff09;&#xff1a;数字number&#xff0c;字符串s…

MFC Socket和合信CTMC M266ES 运动控制型PLC通信进行数据交换

前言 1、前两篇文章通过对Snap7和S7-1200/S7-1500PLC的通信进行了详细的介绍。Snap7的优点开源性强、使用方便易于上手&#xff0c;跨平台和可移植性性强。但是Snap7也有个缺点就是只能访问PLC的DB、MB、I、Q区进行数据读写&#xff0c;不能对V区进行读写,有人说可以读写V区&am…

【Java 设计模式】23 种设计模式

文章目录 设计模式是什么计算机行业里的设计模式创建型模式&#xff08;共 5 种&#xff09;结构型模式&#xff08;共 7 种&#xff09;行为型模式&#xff08;共 11 种&#xff09; 总结 设计模式是什么 “每一个模式描述了一个在我们周围不断重复发生的问题&#xff0c;以及…

docker打包介绍

最近在做一个开源项目&#xff0c;遇到开发者问各种问题&#xff0c;发现都是系统和软件版本的差异引起的。于是了解了一下docker的使用&#xff0c;发现docker真是个好东东&#xff0c;基本解决了各种版本差异的问题&#xff0c;真正做到了一键部署使用。 先熟悉一下docker里…

c++学习:容器stack栈+queue+map(简易输入法)+deque

目录 stack 模板原型 头文件 模板的成员类型和成员对象和成员函数 栈类模板的容器对象 实例 queue 模板原型 头文件 模板的成员类型和成员对象和成员函数 队列类模板的容器对象 实例 map 模板原型 头文件 模板的成员类型和成员对象和成员函数 关联类模板的容器…

debian cups 打印机共享

apt update apt install -y cups localhost:631 add printer root 密码 添加打印机 然后在windows上设置 http://ip:631/printers/HP_LaserJet_1022

Vue3---安装路由

介绍 在Vue3项目中安装路由 示例 第一步&#xff1a;执行npm命令安装路由 npm install vue-router4第二步&#xff1a;在项目的src文件夹下创建router子文件夹 第三步&#xff1a;创建index.js和routes.js文件&#xff0c;以下为文件的代码 //通过vue-router插件实现模板…

Elementui Radio单选框取消选中

问题&#xff1a; 最近开发一个后台项目的时候用到了单选框&#xff0c;而客户的要求是默认选择一个选项&#xff0c;然后点击可以取消选中。不想自己在手写一个Radio组件&#xff0c;只能在elementui的单选框上修改一下下啦。 1. .native的作用 .native的作用是在给组件添加修…

Typecho 最新XC主题 去除域名授权全解密源码

Typecho 最新XC主题 去除域名授权全解密源码 这是一款多样式主题&#xff0c;首页支持六种主题样式&#xff0c;支持Pjax优化访问速度&#xff0c;多种单页&#xff0c;如友链、说说等。评论支持表情&#xff0c;自定义编辑器&#xff0c;支持其他样式功能。该主题功能性挺高&…

代码随想录算法训练营第二十五天 | 216.组合总和III、17.电话号码的字母组合

216.组合总和III 题目&#xff1a; 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 思路&…

3D Surface Subdivision Methods 3D 曲面细分方法

文章目录 1 介绍2 细分法3 一个简单的例子&#xff1a;Catmull-Clark 细分4 Catmull-Clark 细化5 Refinement Host6 Geometry Policy7 四种细分方法8 示例&#xff1a;自定义细分方法9 实施历史 原文地址: https://doc.cgal.org/latest/Subdivision_method_3/index.html#Chapte…

美创科技葛宏彬:夯实安全基础,对医疗数据风险“逐个击破”

导读 解决医疗机构“临床业务数据合规流动”与“重要数据安全防护”两大难题。 2023年11月11日&#xff0c;在2023年南湖HIT论坛上&#xff0c;HIT专家网联合杭州美创科技股份有限公司&#xff08;以下简称美创科技&#xff09;发布《医疗数据安全风险分析及防范实践》白皮书…

小程序学习基础(页面加载)

打开首页&#xff0c;然后点击第一个按钮进去心得页面 进入心得页面以后 第一个模块是轮播图用的是swiper组件&#xff0c;然后就是四个按钮绑定点击事件&#xff0c;最后就是下拉刷新&#xff0c;下拉滚动&#xff0c;上拉加载。代码顺序wxml,js,wcss,json。 <!--pages/o…

Python——python编译器安装教程

1.前往python官网下载安装程序 python官网 python编译器安装程序下载网站 2.找到自己需要的版本&#xff0c;下载对应的安装程序&#xff0c;运行程序 打开安装包&#xff0c;切记要勾选add python 3.9 to PATH 可选择自动安装&#xff08;Install Now&#xff09;或点击自定义…

Intellij-idea 如何编译maven工程

在IntelliJ IDEA中编译Maven工程的过程如下所示&#xff1a; 打开IntelliJ IDEA并导入Maven工程。选择"File"&#xff08;文件&#xff09;菜单&#xff0c;然后选择"Open"&#xff08;打开&#xff09;或者"Open Project"&#xff08;打开项目…

JVM工作原理与实战(十):类加载器-Java类加载器

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、介绍 二、扩展类加载器 三、通过扩展类加载器去加载用户jar包 1.放入/jre/lib/ext下进行扩展 2.使用参数进行扩展 四、应用程序类加载器 总结 前言 ​JVM作为Java程序的运行…

WPF常用控件-Window

常用属性 这里重点记录一些关键且容易忘记的属性&#xff0c;那些很常用的如Title啥的就不在这里一一说明了。 任务栏按钮 ShowInTaskbar&#xff1a;是否在任务栏中显示应用按钮&#xff0c;默认为True。 层级 Topmost&#xff1a;应用是否始终在所有应用的最上层&#x…

MongoDB分片集群架构详解

分片简介 分片&#xff08;shard&#xff09;是指在将数据进行水平切分之后&#xff0c;将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于&#xff0c;MongoDB 本身就自带了分片管理的能力&#xff0c;对于开发…