【C++修行之道】类和对象(五)日期类的实现、const成员、取地址及const和取地址操作符重载

目录

一、 日期类的实现

Date.h

 1.1 GetMonthDay函数(获取某年某月的天数)

 问:这个函数为什么不和其他的函数一样放在Date.cpp文件中实现呢?

1.2 CheckDate函数(检查日期有效性)、Print函数(打印日期)

1.3 实现日期类的逻辑运算符重载

<运算符的重载

 ==运算符重载

其他运算符重载 

1.4 日期与天数加减操作符重载

1.5 日期相减时的操作符重载

1.6 前置运算符和后置运算符实现的区别

前置运算符的语义是“先操作,再返回”。

后置运算符的语义是“先返回,再操作”。

1.7 输入输出流重载

为什么参数顺序为(ostream& out, const Date& d)?

二、const成员

请思考下面的几个问题:

1. const对象可以调用非const成员函数吗? 

2. 非const对象可以调用const成员函数吗?

3. const成员函数内可以调用其它的非const成员函数吗? 

4. 非const成员函数内可以调用其它的const成员函数吗?

三、取地址及const取地址操作符重载 


一、 日期类的实现

Date.h

#define  _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<iostream>
#include<assert.h>

using namespace std;

class Date {

	// 友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	void Print() const;

	// 直接定义在类中,他默认是inline
	// 频繁调用
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);

		static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,30,31,31,30,31 };
		if ((month == 2) && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
			return 29;
		else
		{
			return monthDayArray[month];
		}
		return monthDayArray[month];
	}

	bool CheckDate();

	// 不使用引用修改成员的都能加上 const
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	//Date& operator=(const Date& d);

	// d1 += 100
	// 日期+=天数
	Date& operator+=(int day);

	// d1 + 100
	// 日期+天数
	Date operator+(int day) const;


	// d1 -= 100
	// 日期-=天数
	Date& operator-=(int day);

	// d1 - 100;
	// 日期-天数
	 Date operator-(int day) const;

	// d1 - d2
	// 日期-日期 返回天数
	int operator-(const Date& d) const;

	// ++d1
	Date& operator++();

	// d1++ -> d1.operator(1)
	// 为了区分,构成了重载,给后置++,强行增加了一个int形参
	// 这里不需要写形参名,因为接收值是多少不中呀,也不需要调用
	// 这个参数仅仅是为了前缀和后缀区分而存在的
	Date operator++(int);

	// --d1
	Date& operator--();

	// d1--
	Date operator--(int);

	 流插入
	//void operator<<(ostream& out);
	// 不建议, 因为Date* this占据了一个参数位置, 使用d<<cout不符合习惯
	

// private:
	int _year;
	int _month;
	int _day;
};

// 重载
ostream& operator<<(ostream & out, const Date & d);
istream& operator>>(istream& in, Date& d);

 1.1 GetMonthDay函数(获取某年某月的天数)

// 获取某年某月的天数 
int GetMonthDay(int year, int month)  
{  
    // 断言:确保传入的月份是有效的  
    assert(month > 0 && month < 13);  
  
    // 存储了每个月通常的天数  
    static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };  
  
    // 闰年检查:如果月份是2月,并且年份是闰年,则返回29天  
    if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {  
        return 29;  
    }  
    // 如果不是2月或年份不是闰年,返回静态数组中存储的对应月份的天数  
    else {  
        return monthDayArray[month];  
    }  
}

 问:这个函数为什么不和其他的函数一样放在Date.cpp文件中实现呢?

  • 内联函数的优化:在类定义中直接实现的成员函数默认为内联函数(inline)。内联函数通常用于减少函数调用的开销,因为它们在编译时会被“内联”到调用它们的代码中。GetMonthDay 函数可能预计会被频繁调用,这对于小型、频繁调用的函数特别有益,可以提高程序的执行效率。

1.2 CheckDate函数(检查日期有效性)、Print函数(打印日期)

bool Date::CheckDate()
{
	if (_month < 1 || _month > 12 // 检查_month成员变量是否小于1或大于12
    || _day < 1 || _day > GetMonthDay(_year, _month))
    // 检查_day成员变量是否小于1
    // 调用GetMonthDay(_year, _month)获取当前年份和月份对应的天数,并检查_day是否大于这个值 
	{
		return false;
	}
	else
	{
		return true;
	}
}

// Date类的构造函数,用于初始化Date对象
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
    
    // 调用CheckDate函数检查日期是否合法
	if (!CheckDate())
	{
		cout << "日期非法" << endl;
	}
}

// 打印日期
void Date::Print() const
{
	cout << _year << "-" << _month << "-" << _day << "\n";
}

1.3 实现日期类的逻辑运算符重载

只需实现两个运算符,就能借助这两个运算符去简易的实现其他运算符。

<运算符的重载

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

 ==运算符重载

// d1 == d2
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

其他运算符重载 

// d1 <= d2
bool Date::operator<=(const Date& d) const
{
	return *this < d || *this == d;
}

bool Date::operator>(const Date& d) const
{
	return !(*this <= d);
}

bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}

1.4 日期与天数加减操作符重载

// d1 += day
Date& Date::operator+=(int day) 
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
		// 进位
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}

// 1970以后
// d1 -= 100
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	while (_day <= 0)// 借位
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}

		//借上一个月的天数
		_day += GetMonthDay(_year, _month);
	}

	return *this;
}
  •  += 和 -= 是复合赋值运算符。
  • 功能:复合运算符直接修改调用它们的对象。在Date类的上下文中,+= 运算符将指定的天数加到当前日期上,并直接修改该日期对象。同样,-= 运算符从当前日期中减去指定的天数。
  • 效率:由于不需要创建新对象,复合运算符在性能上可能更高效,特别是在需要频繁更新日期的场景中。

 

Date Date::operator-(int day) const
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}

Date Date::operator+(int day) const
{
	Date tmp = *this;
	tmp += day;
	return tmp;
}
  •  + 和 - 是普通运算符
  • 功能:普通运算符不直接修改调用它们的对象,而是返回一个新的对象,该对象是原始对象与指定值进行运算后的结果。
  • 效率:由于需要创建新对象,因此在性能上可能略低于复合运算符。

1.5 日期相减时的操作符重载

// d1 - d2
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d) // 确定最大和最小日期
	{
		max = d;
		min = *this;
		flag = -1;
        // 即当前对象小于传入的日期,则得到负数天数差
	}

	int n = 0;
	while (min != max)// 实现日期的 != 重载
	{
		++min;
		++n;
	}

	return n * flag;
}

  • 当前对象的日期早于传入的日期对象。此时,将max设置为d,min设置为*this,并将flag设置为-1。这意味着最终的天数差将是负数。 
  •  在while循环体内,每次迭代都会对min进行自增操作(通过++运算符重载实现),同时计数器n也自增。这个过程会一直持续到min和max相等,即两个日期相同为止。

1.6 前置运算符和后置运算符实现的区别

// ++d1
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// d1++ -> d1.operator(1)
// 为了区分,构成了重载,给后置++,强行增加了一个int形参
// 这里不需要写形参名,因为接收值是多少不重要,也不需要调用
// 这个参数仅仅是为了前缀和后缀区分而存在的
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

// --d1
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

// d1--
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

为了区分,构成了重载,给后置++,强行增加了一个int形参。

Date Date::operator++(int)

这里不需要写形参名,因为接收值是多少不重要,也不需要调用。
这个参数仅仅是为了前缀和后缀区分而存在的。

前置运算符的语义是“先操作,再返回”。

  • 语义上:前置运算符的语义是先对对象进行递作,然后返回操作后的对象。这里的关键是“操作后的对象”
  • 效率:返回引用避免了不必要的创建和返回对象的拷贝。在C++中,对象的复制可能是一个昂贵的操作。
  • 链式操作:通过返回引用,可以支持链式操作。例如,可以这样写代码:--date = anotherDate; 
  • 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率

后置运算符的语义是“先返回,再操作”。

  • 语义上:后置运算符的语义是先返回操作后的对象,然后对对象进行递作。后置运算符的关键在于返回操作前的状态
  • 安全性:返回拷贝还避免了潜在的外部修改,此时得到的是一个独立的值,而不是对原始数据的直接引用。原始数据没有影响。
  • 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
    C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。
  • 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1。
  • 而temp是临时对象,因此只能以值的方式返回,不能返回引用

1.7 输入输出流重载

// 友元函数声明,声明后可访问私有成员变量
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);

// 重载输出流操作符<<,用于将Date对象以特定格式输出到输出流中
ostream& operator<<(ostream& out, const Date& d)
{
    // 向输出流中输出Date对象的年份、月份和日期,并添加中文字符和换行符进行格式化
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    // 返回输出流的引用,以便支持链式调用
	return out;
}

// 重载输入流操作符>>,用于从输入流中读取数据并设置到Date对象中
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:>";
	in >> d._year >> d._month >> d._day;
    
    // 调用Date对象的CheckDate()方法检查输入的日期是否合法 
	if (!d.CheckDate())
	{
		cout << "日期非法" << endl;
	}
    
    // 返回输入流的引用,以便支持链式调用
	return in;
}

为什么参数顺序为(ostream& out, const Date& d)?

  • 标准的流插入运算符 << 是左结合的,也就是说左侧应该是流对象(如 cout),右侧是我们想要输出的对象(如 Date)。改变参数顺序后,调用方式也必须相应改变即(<< cout),这与常规用法不符。

标准库中的 operator<< 已经定义好了左侧是 ostream&,右侧是要输出的对象。改变参数顺序后,编译器不会再将其识别为流插入运算符,导致无法正常使用链式调用等特性。

operator<< 的参数顺序反过来会导致函数不能正常作为流插入运算符使用,破坏标准库的调用方式和使用习惯。

二、const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
我们来看看下面的代码

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

请思考下面的几个问题:

1. const对象可以调用非const成员函数吗? 

const对象不能调用非const成员函数

  • 当你将一个对象声明为const时,实际上是在承诺不会修改这个对象的状态。因此,只能对这个对象调用const成员函数,因为这些函数承诺不会修改对象的状态。

2. 非const对象可以调用const成员函数吗?

const对象可以调用const成员函数

  • const成员函数的主要目的是确保函数不会修改对象的状态。因此,对于非const对象(即可以修改的对象)来说,调用const成员函数是安全的,因为即使对象本身可以被修改,const成员函数也承诺不会修改它。

3. const成员函数内可以调用其它的非const成员函数吗? 

不可以。在const成员函数中,不能直接调用同一个类的非const成员函数。原因是const成员函数承诺不会修改对象的状态,而如果它调用了非const成员函数,就会违背这个承诺,因为非const成员函数可能会修改对象

然而,有一种情况可以间接调用非const成员函数,那就是如果你将对象的const性质通过const_cast去除,然后调用非const成员函数。但这种做法是不推荐的,因为它破坏了const的正确性和对象的常量性质,可能导致未定义行为或程序错误。通常来说,应该尽量避免这种做法。

总结来说,const成员函数内不能直接调用非const成员函数,以保持const成员函数的承诺不修改对象状态。

4. 非const成员函数内可以调用其它的const成员函数吗?

是的,非const成员函数内部可以调用const成员函数

const成员函数的主要特点是它不会修改调用它的对象的状态。因此,从逻辑上讲,在可以修改对象状态的非const成员函数中调用一个不会修改对象状态的const成员函数是安全的。

三、取地址及const取地址操作符重载 

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

class A
{
public:
	// 我们不实现,编译器会自己实现,我们实现了编译器就不会自己实现了
	// 一般不需要我们自己实现
	// 除非不想让别人取到这个类型对象的真实地址
	A* operator&()
	{
		cout << "A* operator&()" << endl;

		return nullptr;
	}

	const A* operator&() const
	{
		cout << "const A* operator&() const" << endl;

		return (const A*)0xffffffff;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
	int _a3 = 3;
};

int main()
{
	A aa1;
	const A aa2;

	cout << &aa1 << endl;
	cout << &aa2 << endl;

	return 0;
}

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

今天就先到这了!!!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!

你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

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

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

相关文章

手机建站介绍

随着科技的不断进步和移动互联网的普及&#xff0c;手机应用已经成为人们生活中最不可或缺的一部分。而手机建站作为一种新兴技术&#xff0c;在这一领域也有着广泛的应用。本文将为大家介绍手机建站的概念、优势和应用。 什么是手机建站&#xff1f; 手机建站是指将传统的网络…

【启明智显彩屏应用】Model3A 7寸触摸屏在真空包装机上的应用解决方案

一、项目背景与需求 随着工业自动化水平的提升&#xff0c;对真空包装机的操作界面和控制精度要求也越来越高。为满足这一需求&#xff0c;我们提出了基于Model3A工业级HMI&#xff08;人机界面&#xff09;芯片方案的7寸触摸屏解决方案&#xff0c;旨在提高真空包装机的操作便…

VSCode中使用LaTeX编辑文章

工欲善其事必先利其器&#xff0c;成功在VSCode中使用LaTeX&#xff0c;遂做记录。 1. 先准备VScode的安装 下载地址&#xff1a;VScode地址 正常安装即可&#xff0c;一路next安装下去即可。 2. 准备安装latex 国内使用清华源&#xff0c;下载地址:https://mirrors.tuna.t…

Django学习三:views业务层中通过models对实体对象进行的增、删、改、查操作。

文章目录 前言一、Django ORM介绍二、项目快速搭建三、操作1、view.pya、增加操作b、删除操作c、修改操作d、查询操作 2、urls.py 前言 上接博文&#xff1a;Django学习二&#xff1a;配置mysql&#xff0c;创建model实例&#xff0c;自动创建数据库表&#xff0c;对mysql数据…

FreeRTOS基础(十一):消息队列

本文将详细全方位的讲解FreeRTOS的消息队列&#xff0c;其实在FreeRTOS中消息队列的重要性也不言而喻&#xff0c;与FreeRTOS任务调度同等重要&#xff0c;因为后面的各种信号量基本都是基于消息队列的。 目录 一、消息队列的简介 1.1 产生的原因 1.2 消息队列的解决办法 …

Django 部署指南

部署 Django 应用程序涉及将我们的应用程序从开发环境部署到生产环境&#xff0c;并确保它可以在生产服务器上安全运行和扩展。其实了解几种部署方案&#xff0c;相信你对将来的项目更得心应手。 1、问题背景 Django 是一款流行的 Python Web 框架&#xff0c;但对于新手来说&…

618网购节,电商能挡住恶意网络爬虫的攻击吗?

目录 爬虫盗取电商数据的步骤 电商平台如何发现网络爬虫&#xff1f; 如何拦截违法网络爬虫 2023年&#xff0c;杭州中院审结了两起涉及“搬店软件”的不正当竞争案件。本案的原告是国内某大型知名电子商务平台的运营主体&#xff0c;而被告则是开发了一款名为“某搬家快速商品…

【数据结构】——线性表(顺序表)——内有代码详解

目录 一、引言 二、线性表 2.1 定义 2.2 特点 三、顺序表 3.1 顺序表的概念 3.2 顺序表的特点 3.3 顺序表的定义 3.3.1 静态定义 3.3.2 动态定义 3.4 顺序表的初始化 3.4.1 静态初始化 3.4.2 动态初始化 3.5 顺序表的销毁 3.6 顺序表元素的打印 3.7 顺序表的插入…

百度AI大底座

“百度AI大底座”是源自百度多年产业深度实践积累、结合AI全栈技术科研成果打造的国内首个全栈自研的AI基础设施&#xff0c; 面向企业和产业AI开发与应用提供端到端自主可控、自我进化的解决方案&#xff0c;能够快捷、低成本地实现“AI能力的随用随 取”。AI大底座可助力企业…

Python 学习flask创建项目

1、使用pycharm创建flask项目 2、运行访问地址 3、可以看到访问地址内容 4、可以增加路由&#xff0c;尝试访问获取参数

树莓派4B_OpenCv学习笔记4:测试摄像头_imread加载显示图像_imwrite保存图片

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 今日对之前的测试CSI摄像头函数进行一些理解说明&#x…

Shell脚本文本处理三剑客(grep、awk、sed)和正则表达式

一、正则表达式 1.正则表达式基础 正则表达式&#xff08;regular expression&#xff09;描述了一种字符串匹配的模式&#xff08;pattern&#xff09;&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&…

【微信小程序】页面事件

下拉刷新 上拉触底 上拉触底距离指的是触发上拉触底事件时&#xff0c;滚动条距离页面底部的距离。 可以在全局或页面的json配置文件中&#xff0c;通过onReachBottomDistance属性来配置上拉触底的距离。 小程序默认的触底距离是50x,在实际开发中&#xff0c;可以根据自己的需…

【C++】─篇文章带你熟练掌握 map 与 set 的使用

目录 一、关联式容器二、键值对三、pair3.1 pair的常用接口说明3.1.1 [无参构造函数](https://legacy.cplusplus.com/reference/utility/pair/pair/)3.1.2 [有参构造函数 / 拷贝构造函数](https://legacy.cplusplus.com/reference/utility/pair/pair/)3.1.3 [有参构造函数](htt…

vue3 基于el-tree增加、删除节点(非TypeScript 写法)

话不多说&#xff0c;直接贴代码 <template><div class"custom-tree-container"><!-- <p>Using render-content</p><el-tree style"max-width: 600px" :data"dataSource" show-checkbox node-key"id" …

智能网联汽车信息安全风险识别与应对策略研究综述

摘要&#xff1a;随着智能网联汽车技术的飞速发展&#xff0c;其信息安全问题逐渐成为公众关注的焦点。本文概述了智能网联汽车技术的发展背景和信息安全风险的来源&#xff0c;采用STRIDE威胁分析方法对智能网联汽车的四层模型进行风险识别&#xff0c;进一步探讨了抗女巫攻击…

Renesas MCU之FreeRTOS的应用

目录 概述 1 FSP配置FreeRTOS 1.1 软件版本信息 1.2 配置FreeRTOS 2 FreeRTOS的Task 2.1 FSP下的项目结构 2.2 Task代码 2.2.1 Task测试案例配置 2.2.2 测试代码实现 3 自定义Task 3.1 编写代码 3.2 测试函数 4 测试 4.1 Task断点测试 4.2 板卡运行测试 概述 …

spring boot sso

代码&#xff1a;https://gitee.com/forgot940629/ssov2 授权服务 登录成功后&#xff0c;session中会存储UsernamePasswordAuthenticationToken&#xff0c;之后每次请求code时都会用UsernamePasswordAuthenticationToken生成OAuth2Authentication&#xff0c;并将OAuth2Aut…

动态规划(多重背包问题+二进制优化)

引言 多重背包&#xff0c;相对于01背包来说&#xff0c;多重背包是每个物品会有相应的个数&#xff0c;最多可以选那么多个&#xff0c;因而对于朴素多重背包&#xff0c;需要在01背包的基础上&#xff0c;再加一层物品的循环 朴素多重背包例题 P2347 [NOIP1996 提高组] 砝…

【FAS】《Liveness Detection on Face Anti-spoofing》

文章目录 原文总结与评价CNN-RNN vs 三维卷积作者的方法 原文 [1]欧阳文汉.反人脸图像欺诈的活体识别方法研究[D].浙江大学,2020.DOI:10.27461/d.cnki.gzjdx.2020.002675. 总结与评价 时序运动信息与传统的空间纹理信息相结合 基于相位平移的运动放大算法不错 视觉大小细胞…