【C++之类和对象篇003】

C++学习笔记---006

  • C++知识类和对象篇
    • 1、Date日期类实现
      • 1.1、Date.h
      • 1.2、Date.cpp
      • 1.3、main.cpp
    • 2、"<<"运算符重载和">>"运算符重载
    • 3、const 成员函数
    • 4、取地址及const取地址操作符重载

C++知识类和对象篇

前言:
前面篇章学习了C++对于C语言的语法优化,接下来继续学习,C++的类和对象中Date日期类实现、const成员函数和运算符重载等知识。
/知识点汇总/

1、Date日期类实现

声明和定义分离
Date.cpp Date.h Date_main.cpp
Date日期类实现目的是学习,运算符<,<=,>,>=,==,!=,重载及逻辑复用

1.1、Date.h

#pragma once
#include 
#include 
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		if (!checkInvalid())
		{
			cout << "构造函数日期非法" << endl;
			//exit(-1);
		}
	}

	//赋值重载
	Date& operator=(const Date& d);

	//添加检查函数,检查操作的日期是否合法
	bool checkInvalid();
	//运算符<, <= , >, >= , == , != ,重载及逻辑复用
	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);

	//处理年、月、日应用运算符重载实现
	//GetMonthDay频繁调用,可用内敛,然后如果把函数写成类成员函数本质也就是inline函数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		//int monthdays[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 };
		//放在静态区,细节节省空间资源的重复调用开辟。因为此函数本身会被高频调用,所以处理好细节就更优化
		static int monthdays[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 };
		//闰年
		//if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
		//细节决定成败,把month写在&&之前,有利于执行效率
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;
		}
		return monthdays[month];
	}
	//d1 += 100
	Date& operator+=(int day);
	//d1 + 100
	Date operator+(int day);

	//d1 - 100
	Date operator-(int day);
	//d1 -= 100
	Date& operator-=(int day);

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

	//d1++
	//这里特殊处理,强制添加int型参数与编译器自洽,构造函数重载,以区分前置++,后置++
	Date operator++(int);

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

	//d1--
	//这里特殊处理,同理,强制添加int型参数与编译器自洽
	Date operator--(int);

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

	//流插入"<<"运算符重载,和流提取">>"运算符重载
	//属于C++库中ostream的对象
	//这里就写成成员函数,测试流的方向。
	/*void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}*/

	//友元声明:可间接的突破私有的访问限定符私。
	friend ostream& operator<<(ostream& out, const Date& d);

	//友元声明
	friend istream& operator>>(istream& in, Date& d);

	//void Print();
	//const修饰成员函数权限平替,实质为this const Date*
	void Print() const;

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

//分析可知,不能写成成员函数。则写成全局函数,声明和定义分离,或者写成引用inline内联在.h展开都可以
//当然,作为全局参数就可以加入类名(调用的类),也可以让ostream作为第一个参数
//void operator<<(ostream& out, const Date& d);
//解决类似于连续赋值的,连续流插入,则需改返回值,作为下一个流插入的对象

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

//同理:流提取
istream& operator>>(istream& in, Date& d);

1.2、Date.cpp

#include "Date.h"

//在类外调用需要适用于操作符
//巧用复用处理大于小于等于的关系

Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
}

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

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

//d1 < d2
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;
}

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

//d1 > d2
bool  Date::operator>(const Date& d)
{
	return !(*this <= d);//逻辑取反
}

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

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

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

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

//d1 + 100
//Date Date::operator+(int day)更简便的思路和写法,就是像<,<=,>,>=,==,!=,重载一样,引用逻辑复用
//复用+=,实现+
Date Date::operator+(int day)
{
	//拷贝构造
	Date tmp = *this;
	//+=复用
	tmp += day;
	return tmp;
}

//写法二:
//d1 + 100
//Date Date::operator+(int day)
//{
//	//拷贝构造,创建临时的类实现加运算,就不会改变原时期的值
//	Date tmp(*this);
//	//等价写法:
//	//Date tmp = *this;//拷贝构造是一个已存在的对象拷贝给创建的新对象
//
//	tmp._day += day;
//	while (tmp._day > GetMonthDay(tmp._year, _month))
//	{
//		tmp._day -= GetMonthDay(tmp._year, tmp._month);
//		++tmp._month;
//		if (tmp._month == 13)
//		{
//			tmp._month = 1;
//			++tmp._year;
//		}
//	}
//	//局部对象,函数结束后,生命周期就结束,所以不能用引用返回
//	return tmp;
//}

//那么如果想要复用+,实现+=?
//d1 += 100
//Date& Date::operator+=(int day)
//{
//	//赋值重载 + 复用+
//	*this = *this + day;
//	return *this;
//}


//上面两种写法,哪个更好呢?
//答:写法一更好;因为比较直观的对比开辟的额外空间。
//即:两个方法的+都开辟同样的资源,但而方法二的+=还需要复用+,所以比第一个方法再额外开辟一次空间资源等消耗,所以方法一更优。



//d1 -= 100
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		//先处理借位关系,再获取day
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
//d1 - 100
Date Date::operator-(int day)
{
	//拷贝构造函数
	Date tmp = *this;
	tmp -= day;
	return tmp;
}


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

//d1++
//这里特殊处理,强制添加int型参数与编译器自洽,构造函数重载,以区分前置++,后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

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

//d1--
//这里特殊处理,同理,强制添加int型参数与编译器自洽
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

//d1 - d2
//日期-日期=天数
int Date::operator-(const Date& d)
{
	int flag = 1;//正
	//假设法,假定大小日期
	Date max = *this;
	Date min = d;
	//假设错误纠正,始终保证max放大的日期,min放小的日期
	if (max < min)
	{
		flag = -1;//负
		max = d;
		min = *this;
	}
	int n = 0;
	//这里复用!=,比复用<=更利用效率
	while (min != max)//小日期一直++,直到与大日期相等,即可得天数
	{
		//复用++
		++min;
		++n;
	}
	return n * flag;
}


//void Date::operator<<(ostream& out, const Date& d)
//不是成员函数,不用域操作符了
//void operator<<(ostream& out, const Date& d)
//继续解决类似于连续赋值的,连续流插入,则需改返回值,作为下一个流插入的对象
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
//继续解决,类的成员变量私有访问限定符的问题
//方法1:
//1.像之前一样写一个回调函数,返回成员变量
//Getyear()、Getmonyh()、Getday()

//2.友元声明 --> friend关键字
//friend ostream& operator<<(ostream& out, const Date& d)


//同理:流提取
istream& operator>>(istream& in, Date& d)
{
	cout << "请输入年/月/日:>" ;
	in >> d._year >> d._month >> d._day;
	if (!d.checkInvalid())
	{
		cout << "输入的日期非法" << endl;
		exit(-1);
	}
	else
		return in;
}

1.3、main.cpp

#include "Date.h"
int main()
{
	Date d1;
	Date d2;

	//运算符<,<=,>,>=,==,!=,重载及逻辑复用
	cout << (d1 < d2) << endl;
	cout << (d1 <= d2) << endl;
	cout << (d1 > d2) << endl;
	cout << (d1 >= d2) << endl;
	cout << (d1 == d2) << endl;
	cout << (d1 != d2) << endl;
	return 0;
}

处理年、月、日应用运算符重载实现计算

#include "Date.h"
int main()
{
	Date d1(2024, 1, 29);
	Date d2 = d1 + 60;
	Date d3 = d1 += 20;
	d1.Print();
	d2.Print();
	d3.Print();

	Date d5 = d2 - 60;
	d5.Print();

	d2 -= 60;
	d2.Print();

	d5++;
	d5.Print();

	d5--;
	d5.Print();

	++d5;
	d5.Print();

	--d5;
	d5.Print();

	Date d6(2024, 1, 29);
	Date d7(2024, 8, 1);
	cout << d7 - d6 << endl;

	return 0;
}

2、"<<“运算符重载和”>>"运算符重载

流插入"<<“运算符重载,和流提取”>>"运算符重载
cout:C++库中ostream的对象
cin:C++库中istream的对象

#include "Date.h"

int main()
{
	Date d1(2024, 1, 29);
	Date d2 = d1 + 20;
	d1.Print();
	d2.Print();

	//cout << d2 - d1 << endl;
	//流插入"<<"运算符重载
	//属于C++库中ostream的对象

	//cout << d2;//此时编译重载失败,报错:二元“ << ”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)

	//分析原因:
	//作为成员函数重载,this指针占据第一个参数,则Date参数就必须是左操作数了
	//void operator<<(ostream& out)
	//d1.operator<<(cout);
	//d1 << cout;//发现翻过来写,被流插入到了d1类里了,与实际想要的不服合。
	//我们要的是,cout输出d1才对。现在反而是cout改变了d1,正确的应该d1流插入cout,然后cout输出。

	//所以必须以ostream作为第一个参数,使得this不能作为第一个参数。但是作为成员函数规则规定,this必须作为成员函数的第一个参数。
	//所以:
	//1.提出static修饰,使其归入静态区。
	//2.写成全局函数

	cout << d2;//此时重载成功

	//d1 = d2 = d3类似于连续赋值的操作,所以返回值类型应该是类
	cout << d2 - d1 << endl;
	cout << d2 - d1 << d2 - d1 << endl;

	//同理:流提取">>"运算符重载
	//C++库中istream的对象

	//cin >> d2;//此时编译重载失败,报错:二进制“ >> ”: 没有找到接受“std::ostream”类型的左操作数的运算符(或没有可接受的转换)

	//解决
	//必须以istream作为第一个参数,使得this不能作为第一个参数。但是作为成员函数规则规定,this必须作为成员函数的第一个参数。
	//所以:
	//1.提出static修饰,使其归入静态区。
	//2.写成全局函数

	cin >> d2;//此时重载成功
	cout << d2;

	//其它情况正常使用,原本的规则。
	int i = 0;
	cout << "请输入一个整数:";
	cin >> i;
	cout << i << endl;

	//增加日期类的合理性,添加检查函数,检查操作的日期是否合法
	//CheckInvalid()

	Date d3(2024, 2, 31);
	d3.Print();

	return 0;
}

3、const 成员函数

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

#include "Date.h"

int main()
{
	//const 修饰成员函数
	const Date d1(2024, 1, 31);

	//d1.Print();//此时Print()无法调用
	//因为被const修饰的类,再去调用类中的成员函数,属于权限的放大;而权限只能兼容缩小,不能使其放大造成了非法访问/调用

	//解决方法,理解为权限平替,使其权限范围平行。
	//即:void Print() const;

	d1.Print();//此时Print()正常访问调用

	//另外,虽然此时Print()被const修饰,但是当大权限的类访问时,是可以兼容的。
	//即,权限的缩小
	Date d2(2024, 2, 1);
	d2.Print();

	const int i = 0;
	//因为i这里拷贝,j的改变不影响,所以不存在权限的范围变化
	int j = i;

	//权限放大 -- 通常是指针或引用才存在权限放大
	//r的改变要影响i
	//int& r = i;

	//权限放大 -- 通常是指针或引用才存在权限放大
	const int* p1 = &i;
	//int* p2 = p1;

	return 0;
}

小结:是否给成员函数加const修饰,取决于成员函数本身,是否需要被修改。
简单理解为,类似于文件的,只读或读写。

1.成员函数如果是一个 对成员函数只进行读访问的 函数–>建议加上const,这样const对象和非const对象都能使用;
2.成员函数如果是一个 对成员函数进行读/写访问的 函数–>不能加const,否则const对象和非const对象都不能访问成员变量。

  1. const对象可以调用非const成员函数吗?不能,权限放大
  2. 非const对象可以调用const成员函数吗?能,权限缩小
  3. const成员函数内可以调用其它的非const成员函数吗?不能,权限放大
  4. 非const成员函数内可以调用其它的const成员函数吗?能,权限缩小

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

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

#include 
using namespace std;

class A
{
public:
	//日常不需要自己写构造函数,编译器默认生成的已经够用了。
	//除非使&,不需要拿地址。(或一些整蛊真假地址)
	A* operator&()
	{
		return this;
	}

	const A* operator&() const
	{
		return this;
	}
};

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

	//日常不需要自己写,编译器默认生成的已经够用了。
	//除非使&,不需要拿地址。
	cout << &aa1 << endl;
	cout << &aa2 << endl;

	return 0;
}

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

你可能感兴趣的:(C++基础专栏,c++,算法,const成员函数,运算符重载,取地址操作符重载,日期类的实现,笔记)