初识C++ - 类与对象(中篇·下半)

目录

赋值运算符重载

以下是一个日期类的运算符重载的案例(重点)

关于流插入与流提取的使用

方法一:定义与声明分离 

方法二:使用内联函数

const成员

概念 

关于上述日期类代码为什么需要在函数后面加入const 

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

结束语


以下是对上半的补录 -- 补充一点细节

赋值运算符重载

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

赋值构造(和拷贝构造很类似,但是有区别)

	//拷贝构造函数(默认生成的是浅拷贝 - 在一些情况(有动态内存开辟的)下是不够用的)
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//上下两个很像,但是有区别,意义不一样
	//赋值重载
	void operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
//测试赋值重载与拷贝构造的区别
void TestDate2()
{
	Date d1;
	Date d2(2022, 10, 11);

	Date d3(d2); //拷贝构造(初始化)  一个拷贝初始化另一个马上要创建的对象
	Date d4 = d2;	//这里是一个拷贝构造,要看意义
		
	d1.print();	// 赋值重载(也是默认构造函数,没有写会自动生成一个)
	d1 = d2;	// 赋值重载(复制拷贝) 已经存在两个对象之间拷贝
	d1.print();
}

但是以上的写法是不支持连续赋值的,但是连续赋值确实我们经常使用的,比如我们写的

int i ,j; i = j = 10;          //但是上面的写法是不支持这样用的,为此我们需要再改进一步

	//拷贝构造函数(默认生成的是浅拷贝 - 在一些情况(有动态内存开辟的)下是不够用的)
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//上下两个很像,但是有区别,意义不一样
	//赋值重载
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;	//传值返回(代价大),会调用一次拷贝构造,所以不要这样写
						//但是我们需要注意这里的 *this 所指的对象还在不在,this会销毁,因为函数出了作用域自动就销毁了
		
		//return d;     //理论上是可以返回d的(值一样),但是由于权限的放大就会报错,除非在函数的最前端加上 const 
						//但是一旦加上了const,就会导致这样的玩法不行 (i = j = 10)++ ,我们需要让它的返回值是可以进行修改的(左值)
						//正常情况下都是返回 操作符 左边那一个数
	}

栈上面的赋值重载需要自己写(有动态内存的开辟)

原因 

正确的栈的赋值重载

//深拷贝
typedef int DataType;
class Stack
{
public:
	//构造函数
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		_array[_size] = data;
		_size++;
	}

	//拷贝构造 -- 深拷贝
	Stack(const Stack& st)
	{
		_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			exit(-1);
		}
		memcpy(_array, st._array, sizeof(DataType) * st._size);	//利用memcpy进行拷贝,利用 st._size(数据的个数)更好点

		_size = st._size;
		_capacity = st._capacity;
	}

	//Stack(有动态内存开辟) 需要自己写一个赋值重载,默认生成的是不行滴!
	Stack& operator=(const Stack& st)
	{
		if ( this != &st)	//要加一层判断
		{
			//为防止这样写 st1 = st1 (自己给自己赋值,语法上是通过的,但是会致使_array的值变为随机值了,因为一开始就free掉了)
			//但是还是在用,造成了越界访问(访问已经释放掉的空间,所以会是随机值)
			free(_array);	//这里是不能free(this)的,因为this是* const(在星的右边)类型的,本身是不能被修改的

			_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
			if (nullptr == _array)
			{
				perror("malloc申请空间失败");
				exit(-1);
			}
			memcpy(_array, st._array, sizeof(DataType) * st._size);	//利用memcpy进行拷贝,利用 st._size(数据的个数)更好点
			_size = st._size;
			_capacity = st._capacity;

		}

		return *this;
	}

	//析构函数
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size = 0;
	size_t _capacity = 0;
};

以下是一个日期类的运算符重载的案例(重点)


Date.h(声明)

#pragma once
#include <iostream>
using std::endl;
using std::cout;
using std::cin;
using std::ostream;
using std::istream;
//using namespace std;

//以下为一个声明与定义分离的日期类写法

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
			_year = year;
			_month = month;
			_day = day;
		//检查日期是否合法 -- 这里考虑的并不是很完全
		if (!(_year >= 1
			&& (_month >= 1 && _month <= 12)
			&& (_day>=1 && _day<= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}

	//赋值重载
	Date& operator=(const Date& d)
	{
		if (this != &d) //理论上加上好一点(防止自己给自己赋值),但是日期类用默认赋值函数就好了
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;	//传值返回(代价大),会调用一次拷贝构造,所以不要这样写
						//但是我们需要注意这里的 *this 所指的对象还在不在,this会销毁,因为函数出了作用域自动就销毁了

	}

	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		static int monthDayArray[13] = { 0, 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 monthDayArray[month];
		}
	}

	//打印
	void Print() const	//C++是允许在这里加const的,用以修饰 *this 指针,其他的参数是不受影响的
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}



	Date& operator+=(int day);	

	Date operator+(int day) const;	

	Date operator-(int day) const;

	Date& operator-=(int day);

	Date& operator++();

	Date operator++(int);

	Date operator--(int);

	Date& operator--();

	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;

	int operator-(const Date& d) const;

	//友元函数可以声明在类的任意位置
	//friend ostream& operator<<(ostream& out, const Date& d);
	//friend istream& operator>>(istream& in, Date& d);	

	friend inline ostream& operator<<(ostream& out, const Date& d);
	friend inline istream& operator>>(istream& in, Date& d);	

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

Date.cpp(定义)

#include "Date.h"

//以下为一个声明与定义分离的日期类写法

//等于 ==
bool Date::operator==(const Date& d) const
{
	return _year == d._year		//_year 前面默认有一个 this指针,下面同理 
		&& _month == d._month
		&& _day == d._day;
}

//大于  >
bool Date::operator>(const 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;
	}
	return false;
}

//大于等于 >=
bool Date::operator>=(const Date& d) const
{
	return  *this > d || *this == d;	//所以类里面是允许使用 this指针 的,因为有需求
}

//小于等于 <=
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);
}

// 加等 +=
Date& Date::operator+=(int day)	//需要传引用返回
{
	//加一层判断
	if (day < 0)
	{
		return *this -= abs(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) const	//要使用单纯的传值返回了,就好比  3(a) + 2 = 5; 这里的a的值是不变的
{
	Date ret(*this);
	ret += day;
	return ret;		//传值返回
}


//减等 -=
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this += abs(day);
	}

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

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

// 减 -
Date Date::operator-(int day) const	//日期减 天数
{
	Date ret(*this);
	ret -= day;
	return ret;		//传值返回	
}

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

// 后置++
Date Date::operator++(int)	//关于为什么是 int 这是在创建好时候约定的,没有为什么
{
	Date tmp(*this);
	*this += 1;
	return tmp;		//传值返回	
}

// 后置--
Date Date::operator--(int)	//关于后置为什么多一个返回值,也是约定的,没有为什么
{
	Date ret(*this);
	*this -= 1;
	return ret;		//传值返回	
}

// 前置--
Date& Date::operator--()		//通过这里我们也可以知道,关于用前置++(--),还是后置++(--)的选择,后置需要拷贝
{								//当然这里是自定义类型回如此,内置类型是不会这样的,不过即使这样还是推荐使用前置的
	return *this -= 1;
}

// 日期减日期 -
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)
	{
		++n;
		++min;
	}
	return n * flag;
}


///

//方法一:定义与声明分离
//流插入 <<
//ostream& operator<<(ostream& out, const Date& d)
//{
//	out << d._year << "年" << d._month << "月" << d._day << endl;
//	return out;
//}
//
//流提取 >>
//istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
//{
//	in >> d._year >> d._month >> d._day;
//	return in;
//}

以下是测试案例

#include "Date.h"

//以下为一个声明与定义分离的日期类写法的测试
/

void TestDate1()
{
	Date d1(2022, 10, 8);
	Date d3(d1);
	Date d4(d1);

	d1 -= 10000;
	d1.Print();

	Date d2(d1);
	/*Date d3 = d2 - 10000;d3.Print();*/

	(d2 - 10000).Print();	//甚至可以这样只写
	d2.Print();

	d3 -= -10000;
	d3.Print();

	d4 += -10000;
	d4.Print();
}

void TestDate2()
{
	Date d1(2022, 10, 8);
	Date d2(d1);
	Date d3(d1);
	Date d4(d1);

	(++d1).Print(); // d1.operator++()	这里要考虑运算符的优先级
	d1.Print();

	(d2++).Print(); // d1.operator++(1)
	d2.Print();
}



void TestDate3()
{
	Date d1(2022, 10, 10);
	Date d2(2023, 7, 1);

	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;
}

void TestDate4()
{
	Date d1, d2;
	//cin >> d1;  // 流提取
	//cout << d1;   // 流插入

	//d1 << cout; // d1.operator<<(cout);

	cin >> d1 >> d2;
	cout << d1 << d2 << endl; // operator<<(cout, d1);
	cout << d1 - d2 << endl;
}


void TestDate5()
{
	Date d1(2022, 10, 10);
	d1.Print();

	const Date d2(2022, 10, 10);
	d2.Print();

	//d2 += 10;
	cout << d2 + 1000 << endl;

	cout << &d1 << endl;
	cout << &d2 << endl;
}



int main()
{
	//TestDate1();
	//TestDate2();

	//TestDate3();
	//TestDate4();

	TestDate5();

	return 0;
}

关于流插入与流提取的使用

关于流插入与流提取,这两家伙是被封装成类的,在使用时候需要注意 (比较复杂就不深入了)

当然还可以在类里面定义一个(日期)提取函数,这种方法Java语言中经常被使用 

以下代码是直接放在上面日期类代码一起的 

//为了重载流插入与流提取,需要加入这几句
using std::ostream;
using std::istream;
using std::cin;

在实现<<与>>俩个的重载需要明白下面这个注意事项

方法一:定义与声明分离 

需要注意的是,这里因为里面的成员对象是私有的,在外部是使用不了的,为此需要用到友元函数


//在日期类里面定义这两句声明
	friend ostream& operator<<(ostream& out, const Date& d);

	friend istream& operator>>(istream& in, Date& d);	


 

Date.h 

//全局函数不要写在.h文件里面,会有重命名的报错

//写在全局的原因是因为,this指针会占用第一个参数,导致这样写的函数重载用起来会很别扭
// d << out ,显然这样写是不符合我们使用流插入的习惯的

//流插入
ostream& operator<<(ostream& out, const Date& d);
//流提取
istream& operator>>(istream& in, Date& d);    //这里就不用加const,因为需要该d里面的值

Date.cpp

//流插入 <<
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << endl;
	return out;
}

//流提取 >>
istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
{
	in >> d._year >> d._month >> d._day;
	return in;
}

方法二:使用内联函数

该方法只需要在.h里面写就行了 (方法一里面的代码记得屏蔽,不然会报错)

Date.h

	friend inline ostream& operator<<(ostream& out, const Date& d);
	friend inline istream& operator>>(istream& in, Date& d);

//方法二:使用内联函数
//使用内联函数,也可以解决重命名的问题,因为内联函数也是不会进入符号表的
//内联与static的方法很像,不报错的原因都是不进入符号表,但是两者的意义不一样
//内联函数在使用的时候就会自动展开
//流插入
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << endl;
	return out;
}
//流提取
inline istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
{
	in >> d._year >> d._month >> d._day;
	return in;
}

const成员

概念 

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

关于上述日期类代码为什么需要在函数后面加入const 

  

总结:凡是内部不改变成员变量,即*this对象数据的,这些成员函数都应该加const

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

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

class Date
{ 
public :

 Date* operator&()
 {
     return this ;
 }

 const Date* operator&()const    //这个版本是给const类型变量调用的
 {
     return this ;
 }

private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

  

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

样例

	// 要求这个类的对象不让取地址
	Date* operator&()
	{
		return nullptr;
		//return this;
	}

	const Date* operator&() const
	{
		return nullptr;
		//return this;
	}

结束语

夫天未欲平治天下也;如欲平治天下,当今之世,舍我其谁也?

                                                                                                        ——《孟子》

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清风玉骨

爱了!

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

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

打赏作者

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

抵扣说明:

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

余额充值