【C++基础】类和对象(3) (赋值运算符重载、日期类实现、取地址运算符重载)

目录

一、赋值运算符重载

1.1  运算符重载

1.2  赋值运算符重载

1.3  前置++和后置++重载

二、日期类的实现

 三、取地址运算符重载

3.1  const成员函数

3.2  取地址运算符重载


一、赋值运算符重载

1.1  运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
运算符重载是具有特名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他
函数一样,它也具有其返回类型和参数列表以及函数体。
函数原型:返回值类型 operator操作符(参数列表)
注意:

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

 . *  运算符表示对象成员解引用操作,一般用作成员函数指针,一般成员函数回调的时候使用 ) , 举个栗子:(这里大家了解就行)

class A
{
public:
	void func()
	{
		cout << "A::func()" << endl;
	}
};
typedef void(A::* PF)(); //成员函数指针类型
int main()
{
	// C++规定成员函数要加&才能取到函数指针
	PF pf = &A::func;
	A obj;//定义ob类对象temp
	// 对象调用成员函数指针时,使用.*运算符
	(obj.*pf)();
	return 0;
}

接下来看  全局的operator== 

#include<iostream>
using namespace std;

class Date
{
public:
	Date(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;
};
// 重载为全局的面临对象访问私有成员变量的问题
// 有几种方法可以解决:
// 1、成员放公有
// 2、Date提供getxxx函数
// 3、友元函数
// 4、重载为成员函数
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 7, 5);
	Date d2(2024, 7, 6);
	// 运算符重载函数可以显示调用
	operator==(d1, d2);
	// 编译器会转换成 operator==(d1, d2);
	d1 == d2;
	return 0;
}

1.2  赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。

赋值运算符重载的特点:
1. 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引用,否则会传值传参会有拷贝
2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
3. 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

赋值运算符重载格式:
        参数类型: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;
	}

这里注意区分:

赋值运算符只能重载成类的成员函数不能重载成全局函数

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.3  前置++和后置++重载

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。
C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分

举个栗子:

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

二、日期类的实现

Data.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会重复定义, 导致报错

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

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

 三、取地址运算符重载

3.1  const成员函数

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数,Print隐含的this指针由Date* const this 变为const   Date* const this

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

#include<iostream>
using namespace std;
class Date
{

public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// void Print(const Date* const this) const
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 这里非const对象也可以调用const成员函数是一种权限的缩小
	Date d1(2024, 7, 5);
	d1.Print();
	const Date d2(2024, 8, 5);
	d2.Print();
	return 0;
}

3.2  取地址运算符重载

取地址运算符重载分为普通取地址运算符重载const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址

#include<iostream>
using namespace std;
class Date
{
public:
	Date* operator&()
	{
		return this;
		// return nullptr;
	}
		const Date * operator&()const
	{
		return this;
		// return nullptr;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

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


本篇完,如有问题欢迎在评论区指正,下篇见!感谢!

                                                    

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值