C++第五弹 -- 类与对象中篇下(赋值运算符重载函数 const成员函数 取地址操作符重载函数)

前言

本文将深入探讨C++中的运算符重载,重点讲解赋值运算符、前置/后置++运算符、取地址运算符的重载方法,以及const成员函数的定义和使用方法。通过日期类的实现示例,展示运算符重载和const成员函数在实际应用中的具体代码实现,帮助读者更好地理解和运用这些C++特性。

博客主页: 酷酷学!!!

感谢关注~


正文开始

一. 赋值运算符重载

1. 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义(这样只是更加规范)
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
    现。 (. *运算符表示对象成员解引用操作,一般用作成员函数指针 )

例如:成员函数需要加&才能取到函数指针, 而普通函数函数名就是函数首地址

class OB
{
public:
	void func() 
	{
		cout << "void func()" << endl;
	}
};

typedef void(OB::*PtrFunc)() ;//成员函数指针类型

//int main()
//{
//	// 函数指针
//	// void (*ptr)();
//
//	// 成员函数要加&才能取到函数指针
//	PtrFunc fp = &OB::func;//定义成员函数指针p指向函数func
//
//	OB temp;//定义ob类对象temp
//
//	(temp.*fp)();
//	
//
//	return 0;
//}

在这里插入图片描述

全局的运算符重载operator==


 重载成全局,无法访问私有成员
 1、提供这些成员get和set
 2、友元  后面会讲
 3、重载为成员函数(一般使用这种)
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;
}

int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);

	// 显式调用
	operator==(d3, d4);

   // 直接写,装换调用,编译会转换成operator==(d3, d4);
	d3 == d4;//一般习惯上会用这种

	return 0;
}

这里会发现运算符重载成全局的就需要成员变量是公有的, 那么就违背了类的封装性, 这里其实我们可以使用友元, get/set函数, 但是最用的还是将它重载成成员函数

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// bool operator==(Date* const this, const Date& d2)
	// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date & d2)//const修饰d2的内容d2,其内容不可被修改, 传引用用于提高效率)
	{
		return _year == d2._year;
		&& _month == d2._month
			&& _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);

	// 显式调用
	d3.operator==(d4);

	// 转换调用 等价于d3.operator==(d4);
	d3 == d4;

	int i = 0, j = 1;
	bool ret = i == j;

	return 0;
}

将它重载成成员函数时需要注意, 这里这里第一个参数为隐藏的this指针, 所以实际上我们只需要写一个参数位置就可以了.

2. 赋值运算符的重载

  1. 赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义

代码如下:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	// d1 = d4;
	// d1 = d2 = d4;
	// d1 = d1
	// Date operator=(const Date& d)
	/*Date& operator=(const Date& d)
	//我们可以看到这里我们需要将赋值后的对象进行返回才能进行连续赋值,那么是传引用还是传值呢?看下面分析
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}*/

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
		_year = -1;
		_month = -1;
		_day = -1;
	}

左: 如下面这两个函数, Date出了作用域就被销毁了 只能传值返回, 返回的是d的一份临时拷贝
右: 如果传引用返回,但是d已经被销毁了, 所以ref是一个随机值

在这里插入图片描述
传引用返回可以减少拷贝次数, 提高程序运行效率

在这里插入图片描述
在这里插入图片描述
这里Date在main函数中, 显然生命周期是整个程序, 所以传引用返回提高效率

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因如下:
赋值运算符如果不显示实现, 编译器会生成一个默认的, 此时用户再在类外实现一个全局的赋值运算符重载, 就和编译器在类中生成的默认赋值运算符重载冲突了, 故赋值运算符重载只能是类中的成员函数.

在这里插入图片描述

  1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
    意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
    重载完成赋值。

跟拷贝构造类似, Date或者MyQueue默认生成的赋值就够用了, 但是类似Stack/List等都需要我们自己实现赋值重载

3. 前置++ 和 后置++ 重载

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
		// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
		//       而temp是临时对象,因此只能以值的方式返回,不能返回引用
		Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++;    // d: 2022,1,13   d1:2022,1,14
	d = ++d1;    // d: 2022,1,15   d1:2022,1,15
	return 0;
}

二. 日期类的实现

  • Date.h
#pragma once

#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();

	//因为要重复使用,所以定义在类中,相当于内联函数
	//获取某年某月有多少天
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		static int Month[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
		//静态变量声明周期为整个程序,不需要频繁创建销毁
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;
		}
		else
		{
			return Month[month];
		}
	}

	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);

	Date& operator+=(int day);
	Date operator+(int day);
	Date& operator-=(int day);
	Date operator-(int day);
	Date& operator=(const Date& d);
	Date& operator++();//前置++
	//返回+1之后的结果
	//注意:this指向的对象函数结束后不会被销毁,故以引用的方式返回提高效率

	Date operator++(int);//后置++
	//前置++和后置++都是一元运算符, 为了让前置++与后置++形参正确重载
	//C++规定:后置++重载时多增加一个int类型的参数, 但调用函数时该参数不用
	//传递,编译器自动传递
	//注意:后置++是先使用后+1,因此需要返回+1之后的旧值, 故需要在实现时
	//先将this保存一份,然后再给this+1,而tmp是临时对象,因此只能以值的方式
	//返回,不能返回引用
	bool CheckDate();
	Date& operator--();
	Date operator--(int);

	//d1 - d2
	int operator-(const Date& d);

	//满足连续调用的条件d1<<d2<<d3,故需要返回值,不会析构,引用返回
	ostream& operator<<(ostream& out);
private:
	int _year;
	int _month;
	int _day;
};

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

内联函数和static修饰的成员函数不具备外部链接属性,没有函数名修饰, 只能在内部使用, 而普通函数在.h文件中写之后, 会被展开到, test.cpp和Date.cpp会重复定义, 导致报错

  • Date.cpp

这里计算两个日期相加比较难算, 可以对比数的进位
在这里插入图片描述

#include"Date.h"

bool Date::CheckDate()
{
	if (_month < 1 || _month>12 || _day<1
		|| _day > GetMonthDay(_year, _month))
		return false;
	else return true;
}

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (!CheckDate())
	{
		cout << "日期非法" << endl;
	}
}

void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

bool Date::operator<(const Date& d)
{
	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;
}

bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
//直接进行复用
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 !(*this == d);
}

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;
}

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

Date& Date::operator-=(int day)
{
	//比对加法实现
	_day -= day;
	while (_day < 1)
	{
		_month--;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

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

Date& Date::operator=(const Date& d)
{
	if (this != &d)//自己给自己赋值无意义
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;//将自己返回
}

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

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

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

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

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 n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

//d1<<d2<<d3
//ostream& Date::operator<<(ostream& out)
//{
//	out << _year << "年" << _month << "月" << _day << "日" << endl;
//	return out;
//}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}//但是这样写无法访问成员, 怎么办? 友元 或者 将成员置为public
//返回out的引用是将多个输出连接在一起,形成链式调用

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	if (!d.CheckDate())
	{
		cout << "日期非法" << endl;
	}
	return in;
}

在这里插入图片描述

  • test.cpp
#include"Date.h"

int main()
{
	Date d1;
	Date d2(2024, 7, 11);

	int ret = d1 == d2;
	int ret1 = d1 < d2;
	int ret2 = d1 <= d2;

	cout << ret << ret1 << ret2 << endl;

	cout << d1;//这样写会报错,因为参数顺序不匹配
	d1 << cout;//为了解决这个问题,将cout重载写在类外

	return 0;
}

在这里插入图片描述

三. const成员函数

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

但是成员函数中, this指针变量已经被隐藏了, 怎么实现呢?

void Print() const //在函数旁边写const

注意 这里修饰的是this里面所指向的内容, 而编译器的this指针是修饰this指针本身

const Date* this 修饰内容
Date* const this 修饰this
而此处相当于双const
const Date* const this
在这里插入图片描述

如果对象是常量对象,则会调用带有const关键字的Print函数;
如果对象是非常量对象,则会调用不带const关键字的Print函数。
这种行为被称为"常量重载"。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*void Print()//Date* const this
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}*/
	void Print() const //const Date* const this
	{
		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()
{
	const Date d1(2022, 1, 13);//常量对象
	d1.Print();//调用没有const修饰的print涉及权限放大,报错, 调用有const修饰的print可以
	
	Date d2(2022, 1, 13);//非常量对象
	d2.Print();//调用没有const修饰的成员函数权限平移,可以,调用有const修饰的权限缩小,可以
}

四. 取地址及const取地址操作符重载

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

class Date
{
public:
	Date* operator&()
	{
		return this;
	}

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

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

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;
}

总结

C++中的运算符重载可以增强代码可读性,提高代码效率。
赋值运算符重载只能是类成员函数,不能是全局函数。
前置/后置++运算符重载需要分别定义两个函数,前置++返回引用,后置++返回对象拷贝。
取地址运算符一般不需要重载,使用编译器生成的默认重载即可。
const成员函数修饰的是this指针,表示该函数不能修改类的成员变量。
const成员函数可以被常量对象调用,也可以被非常量对象调用。


完, 如有其他问题, 感谢各位楷模指出

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷学!!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值