【C++】类和对象--日期类Date补充及流提取、流插入

本篇引入:

之前我们详细地实现了Date日期类,然后我们这里继续补充一些细节。如果你是不清楚这里的Date类是什么,可以转到这篇文章【C++】类和对象--一篇带你解决运算符重载实例--日期类

目录

1. const成员

1.1 const修饰类的成员函数

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

3. 流提取和流插入运算符重载

3.1 流插入运算符重载

3.2 流提取运算符重载

3.3 测试:


1. const成员

1.1 const修饰类的成员函数

在Date日期类中,我们实例化一个普通对象。而普通对象调用普通成员函数需要传参,传参传给隐藏的this指针,下面给出一个例子:

class Date
{
public:
	Date(int year=0, int month=0, int day=0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;

};

int main() {
	//这里实例化的是一个普通对象
    Date d(2025, 8, 23);
	
    //此时d(普通对象)调用Print
    //传参给this指针
    //也就是将d1的地址传给了this
    d1.Print();
}

上面这种情况肯定是能够顺利运行的,但是如果实例化一个const对象呢,还能够顺利编译吗?

int main() {
	const Date d2(2025, 8, 23);
	d2.Print();
}

我们发现报错了:

这是因为将Date实例化为const,那么在调用Print()时,传入的地址类型应该是const Date *,但是this指针是Date的,那么这个转换是不被允许的(因为权限被放大了)

那么我们第一反应的解决方案肯定就是将这类的this指针类型修改成const Date *,但是我们学过这个this指针是由编译器自动生成的,我们没有办法直接修改。所以C++的设计者这样解决:

//在成员函数参数列表后面加上const修饰
//起作用就相当于void Print(const Date * const this )
//这样一来这个传参过程就能够顺利进行了

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

总结:建议所有不修改成员变量的成员函数都加上const

  • const -> const 权限大小不变
  • 普通 -> const 权限变小(是编译器允许的)

Q:思考下面几个问题

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
  4. 非const成员函数内可以调用其它的const成员函数吗?

A:答案

  1. 不能,权限被放大了
  2. 能,权限被缩小了
  3. 不能,权限被放大了
  4. 能,权限被缩小了

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

前面讲到,一共有6个默认成员函数,我们已经学习了4个,现在我们来看一看剩下两个(并不是很重要,默认生成的即可):

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

总结:这两个运算符一般不需要重载,使用编译器默认生成的取地址重载即可。只有在一些特殊情况才需要重载,例如想要让用户看到指定的信息!


3. 流提取和流插入运算符重载

Date d2(2025, 8, 23);
cout << d2 << endl;

会报错:

显然我们想要完成自定义类的输入和输出,就要进行流提取、流插入的运算符的重载。

但是我们想一下,内置类型都是可以直接使用流提取和流插入运算符的,这似乎好像也是重载哦!

int i = 1;
double d = 2.2;
cout << i;//自动识别类型
cout << d;

我们深入了解一下:

coutcin 是全局对象,他们包含在iostream头文件中,所以我们在写C++文件的时候会#include <iostream>。cin是istream的对象,cout是ostream的对象,istream和ostream是库中的流提取和流插入的两个类型

而<<和>>能够识别不同的内置类型的真正原因就是,把所有内置类型都重载了:

所以其实真实的情况就是:

那么针对Date日期类这种自定义类型,我们也只需要重载一下就可以了。


3.1 流插入运算符重载

问题1:

class Date
{
public:

    //参数列表中是一个ostream&(是一个别名)来接受传入的对象
	void operator<<(std::ostream& out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}
 
private:
	int _year;
	int _month;
	int _day;
};

但是这样有一个限制,那就是我们只能这两种方式来进行访问:

Date d2(2025, 8, 23);
d2.operator<<(cout);
d2 << cout;

使用我们一般的方式依旧是会报错的:

我们来继续思考:

我们之前讲过成员函数的第一个参数是this指针,放到这里讲的话,意味着重载后<<(双目运算符)左侧必须是当前对象,但是我们使用在输出时的习惯肯定是<<的左侧是cout类型。所以这里我们先想到,运算符重载写在全局上:

void operator<<(std::ostream& out,const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
}

全局函数的话,就没有这个限制,第一个参数就可以是ostream类,这样的话就能符合cout正常的使用习惯(cout << d)。

但是,_year、_month、_day是私有的成员变量,所以最后我们通过友元函数(友元函数是一个非成员函数,但它可以访问类的私有(private)和保护(protected)成员。它打破了类的封装性,但提供了必要的灵活性)来解决:

class Date
{
    public:
	Date(int year=0, int month=0, int day=0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    
    //友元函数的声明
	friend void operator<<(std::ostream& out, const Date& d);

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

    private:
	    int _year;
	    int _month;
	    int _day;

};

//友元函数的定义
void operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
}

运行结果:


问题2:

我们知道对于内置类型,流插入是可以连续访问的:

int i = 1;
double d = 2.2;
cout << i << "-" << d << endl;

但是通过我们上面的方法似乎不太行,依旧是会包没有匹配的参数列表。举一个例子:

Date d1(2025, 8, 21);
Date d2(2025, 8, 23);
cout << d1  << d2;

这里会先调用 cout<<d1 ,然后会返回一个返回值,这个返回值会作为下一次流插入的左参数。但是流插入的左参数同样也应该是一个cout(ostream)类型的,因此我们这里应该有一个返回值。

最终正确版本:

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

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

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

private:
	int _year;
	int _month;
	int _day;

};
//友元函数
//注意我这里的代码是从cpp文件中截取出来的
//是包含了头文件的
//如果没有包含头文件
//需要 std::
ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

3.2 流提取运算符重载

class Date
{
	//友元函数
	//流提取
	friend std::ostream& operator<<(std::ostream& out, const Date& d);
	//流插入
	friend std::istream& operator>>(std::istream& in, Date& d);
    
    //!!!
    //Date要赋值修改 所以不能const

public:
 
private:
	int _year;
	int _month;
	int _day;
};
 
std::ostream& operator<<(std::ostream& out,const Date&d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}
std::istream& operator>>(std::istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

3.3 测试:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year=0, int month=0, int day=0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//流插入
	friend ostream& operator<<(ostream& out, const Date& d);
	//流提取
	friend istream& operator>>(istream& in, Date& d);
	//注意这里的Date不能是const,因为要被修改
private:
	int _year;
	int _month;
	int _day;

};
//友元函数
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year  >> d._month >> d._day;
	return in;
}
int main() {
	Date d1, d2;
	cin >> d1;
	cout << d1;
	cin >> d2;
	cout << d2;
    return 0;
}


到这里我们的Date日期类就比较完整了,大家可以自己练习练习。

(本篇完)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值