【C++】——类与对象(中)+日期类对象的实现

1. 前言

今天我们来继续学习C++类与对象(中)的相关知识点。

2. 类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

6个默认成员函数:

初始化和清理:构造函数主要完成初始化工作、析构函数主要完成清理工作。
拷贝赋值:拷贝构造是使用同类对象初始化创建对象、赋值重载主要是把一个对象赋值给另一个对象。
取地址重载:主要是普通对象和const对象取地址,这两个很少会自己实现。

3. 构造函数

构造函数是一个特殊(不能以普通函数的定义和调用规则去理解)的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性
1.函数名与类名相同。
2.无返回值,也不用写void
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载。

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

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

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

int main()
{
	Date d1;
	Date d2(2023, 4, 27);
	d1.Print();
	d2.Print();
	return 0;
}

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
C++把类型分成内置类型(基本类型)和自定义类型。
内置类型就是语言提供的数据类型如:int / char…,自定义类型就是我们使用class / struct / union等自己定义的类型。
默认生成的构造函数:1、内置类型成员不做处理,2、自定义类型成员会去调用他的默认构造函数
6.C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

class Date
{
public:
	//Date()
	//{
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}
	//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 = 2;    //此处不是初始化,是给缺省值
	int _month = 2;
	int _day = 2;
};

int main()
{
	Date d1;
	//Date d2(2023, 4, 27);
	d1.Print();
	//d2.Print();
	return 0;
}

4. 析构函数

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数是特殊的成员函数
特性
1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
注意:析构函数不能重载、内置类型不处理、自定义类型去调用他的析构函数
4.对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

int main()
{
	TestStack();
	return 0;
}

5. 拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数
特性
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3.若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。而自定义类型是调用其拷贝构造函数完成拷贝的。
4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以,一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
5.拷贝构造函数典型调用场景:
使用已存在对象创建新对象、函数参数类型为类类型对象、函数返回值类型为类类型对象

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date(const Date a)错误
	Date(const Date& a)//正确
	{
		_year = a._year;
		_month = a._month;
		_day = a._day;
	}

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

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


int main()
{
	Date a1(2023, 5, 10);
	Date a2(a1);
	a1.Print();
	a2.Print();
	return 0;
}

6. 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表。
其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

注意
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.(.*)(::)(sizeof)(?:)(.) 注意以上5个运算符不能重载

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

	Date(const Date& a)
	{
		_year = a._year;
		_month = a._month;
		_day = a._day;
	}

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

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

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

int main()
{
	Date a1(2023, 5, 10);
	Date a2(2024, 5, 10);
	//内置类型可以直接使用运算符运算,编译器知道要如何运算
	//自定义类型无法直接使用运算法,编译器也不知道要如何运算。想支持,自己实现运算符重载即可
	cout << (a1 == a2) << endl;
	return 0;
}

6.1 赋值运算符重载

1.参数类型:const T& ,传递引用可以提高传参效率
2.返回值类型:T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回* this :要复合连续赋值的含义
注意
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
2.赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现。

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

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

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

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

int main()
{
	Date a1(2023, 5, 10);
	Date a2;
	a2.Print();
	a2 = a1;
	a2.Print();
	return 0;
}

7. const成员

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

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

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

	//void Print(const A* const this)
	//修饰了this指向的内容,保证了成员函数内部不会修改成员变量,const对象和非const对象都可以调用这个成员函数
	void Print()const
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}

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


int main()
{
	A a1(2023,5,13);
	const A a2(2022, 5, 13);
	a1.Print();
	a2.Print();

	return 0;
}

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

这两个默认成员函数一般不用重新定义,编译器默认会生成。
特殊使用场景:不想让别人取到这个类型对象的地址

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

	A* operator&()
	{
		return nullptr;
	}

	const A* operator&()const
	{
		return nullptr;
	}
	
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	A a1;
	const A a2;

	cout << &a1 << endl;
	cout << &a2 << endl;

	return 0;
}

9. 日期类对象的完整实现

下面我通过前面所学知识点来实现了一个完整的日期类对象,大家可以通过这个完整的类对象,来和之前所学知识点对比学习。

9.1 头文件

#include <iostream>
using namespace std;

class Date
{
	// 友元函数 -- 这个函数内部可以使用Date对象访问私有保护成员
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	//全缺省的构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	//获取某个月的天数
	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		int day = days[month];
		if (month == 2 && ((year % 4 == 0) && (year % 100 != 0) || year % 400 == 0))
		{
			day += 1;
		}
		return day;
	}
	
	//打印日期
	void Print()const;

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

	//==运算符重载
	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;

	//日期+=天数
	Date& operator+=(int day);

	//日期+天数
	Date operator+(int day)const;

	//日期-=天数
	Date& operator-=(int day);

	//日期-天数
	Date operator-(int day)const;

	//日期前置++和后置++
	//前置++和后置++直接按特性重载,无法区分,所以要特殊处理,使用函数重载区分,后置++重载增加一个int参数跟前置构成函数重载进行区分
	Date& operator++();
	Date operator++(int);

	//日期前置--和后置--
	Date& operator--();
	Date operator--(int);

	//日期-日期
	int operator-(const Date& d)const;

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

//流插入重载
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)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

9.2 源文件

#include "Date.h"

void Date::Print()const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

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

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


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

bool Date::operator>(const Date& d)const
{
	if ((_year > d._year)
		|| (_year == d._year && _month > d._month)
		|| (_year == d._year && _month == d._month && _day > d._day))
	{
		return true;
	}
	else
	{
		return false;
	}
}

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

	_day += day;

	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

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

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

	_day -= day;

	while (_day < 0)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

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

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

Date Date::operator++(int)
{
	Date ret = *this;
	*this += 1;
	return ret;
}

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

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

int Date::operator-(const Date& d)const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		min++;
		n++;
	}
	return n * flag;
}

9.3 测试代码

int main()
{
	Date d1(2023, 5, 13);
	Date d2(2022, 5, 13);
	Date d3 = d1;
	d3.Print();
	cout << (d1 == d2) << endl;
	cout << (d1 != d2) << endl;
	cout << (d1 == d3) << endl;
	cout << (d1 < d2) << endl;
	cout << (d1 > d2) << endl;
	cout << (d1 >= d3) << endl;
	cout << (d1 <= d3) << endl;

	(d1 + 4000).Print();
	(d1 - 4000).Print();
	(++d1).Print();
	(--d1).Print();
	cout << (d1 - d2) << endl;

	(d1 + 40000).Print();
	(d1 - 40000).Print();

	cout << d1 << endl;
	cin >> d1;
	cout << d1 << endl;

	return 0;
}

测试结果如下
在这里插入图片描述

10. 结尾

本篇博客所讲内容我认为是C++类与对象板块乃至C++初学阶段最重要的地方,6个默认成员函数必须掌握,并能自己实现一个完整的简单的类对象,此处一定要多复习,多思考。
最后,感谢各位大佬的耐心阅读和支持,觉得本篇文章写的不错的朋友可以三连关注支持一波,如果有什么问题或者本文有错误的地方大家可以私信我,也可以在评论区留言讨论,再次感谢各位。

  • 46
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 42
    评论
在面向对象的编程C语言并不直接支持类和抽象的概念。引用提到,final关键字用来修饰方法,表示该方法不能在子类被覆盖。而abstract关键字用来修饰抽象方法,表示该方法必须在子类实现。然而,在C语言,没有对应的关键字来实现类和抽象的概念。 相反,C语言通过结构体来模拟类的概念。结构体是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。通过结构体,我们可以将相关的数据和功能组合在一起。然而,C语言的结构体不支持继承和多态等面向对象的特性。 在C语言,我们可以使用函数指针来模拟抽象类和接口的概念。函数指针可以指向不同的函数,通过使用函数指针,我们可以实现多态性,即在运行时根据函数指针指向的具体函数来执行不同的操作。 综上所述,C语言并不直接支持面向对象的类和抽象的概念,但可以使用结构体和函数指针来实现类似的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [面向对象——类和对象](https://blog.csdn.net/shouyeren_st/article/details/126210622)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [面向对象编程原则(06)——依赖倒转原则](https://blog.csdn.net/lfdfhl/article/details/126673771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_Fiora

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

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

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

打赏作者

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

抵扣说明:

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

余额充值