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

这篇具有很好参考价值的文章主要介绍了【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;
}

测试结果如下
【C++】——类与对象(中)+日期类对象的实现

10. 结尾

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

到了这里,关于【C++】——类与对象(中)+日期类对象的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员

      目录 1、前言 2、全缺省的构造函数 3、打印接口 4、拷贝构造 5、赋值运算符重载(operator=) 5.1赋值重载是默认成员函数,重载格式: 5.2 赋值重载不能为全局函数 5.3 编译器默认生成 6、析构函数 7、operator 8、operator== 9、operator= 10、operator 11、operator= 12、operator!= 13、operato

    2024年02月13日
    浏览(46)
  • 【C++类和对象】日期类的实现

    hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥 个人主页 :大耳朵土土垚的博客 💥 所属专栏 :C++入门至进阶 这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖 通过下面的学习我们将构建简单日期计算器的各种

    2024年04月23日
    浏览(51)
  • C++类和对象 练习小项目---日期类的实现.

    🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻推荐专栏1: 🍔🍟🌯C语言初阶 🐻推荐专栏2: 🍔🍟🌯C语言进阶 🔑个人信条: 🌵知行合一 🍉本篇简介::为了更好的理解 C++ 类和对象的知识,我们可以动手实现一下 C++ 的一个简单的日期类,完成相应的函数,更好的帮助我们理解类和对

    2024年02月14日
    浏览(33)
  • C++初阶学习第三弹——类与对象(上)——初始类与对象

    前言: 在前面,我们已经初步学习了C++的一些基本语法,比如内敛函数、函数重载、缺省参数、引用等等,接下来我们就将正式步入C++的神圣殿堂,首先,先给你找个对象 目录 一、类与对象是什么? 二、类的各部分组成 1、类的定义 2、类的访问限定符及封装 3、类的作用域

    2024年04月26日
    浏览(50)
  • 如何在C++中将int类型的变量转换为string类型呢?今天我们就来介绍两种方法。

    如何在C++中将int类型的变量转换为string类型呢?今天我们就来介绍两种方法。 第一种方法是使用C++11标准引入的std::to_string()函数。这个函数可以将数字类型的变量转换为对应的字符串类型。下面是一个使用示例: 上面的代码将整型变量num转换为字符串类型,并输出到控制台

    2024年02月08日
    浏览(58)
  • c++类与对象详解

    在C++中,对象是类的实例。定义对象的语法为: 其中,class_name 是定义类时指定的类名,object_name 是对象的名称。 例如,在以下示例中,我们定义了一个名为 Rectangle 的类和两个对象 rect1 和 rect2: 这里我们创建了两个 Rectangle 类的对象:rect1 和 rect2。 在C++中,类可以通过以

    2024年02月13日
    浏览(36)
  • c++类与对象

    🐶博主主页: @ᰔᩚ. 一怀明月ꦿ  ❤️‍🔥 专栏系列: 线性代数,C初学者入门训练,题解C,C的使用文章 🔥 座右铭: “不要等到什么都没有了,才下定决心去做” 🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀  目录 🐰类与对象 🌸

    2023年04月08日
    浏览(45)
  • C++类与对象(下)

    您好这里是limou3434的博文,感兴趣可以看看我的其他博文系列。 这一部分是对C++类的一些补充:初始化列表、友元、内部类。 在给对象进行初始化的时候,如果选择函数体内进行初始化是一件很麻烦的事情。 但是这样写是没有办法过的,要成功运行的话只能写一个默认构造

    2024年02月11日
    浏览(43)
  • 【C++】类与对象(下)

      若想了解什么是类、封装的意义可以移步  【C++】类与对象(引入)   若对六大成员函数或const成员函数有疑问的这篇文章可能可以帮到你  【C++】类与对象(上) 目录 系列文章 前言 1.初始化列表 1.1概念 1.2特性  1.2.1必须使用初始化列表的情况 1.2.2初始化的顺序 2.expli

    2023年04月14日
    浏览(33)
  • 【C++】类与对象(上)

    之前的文章中讲解了,什么是类、类的实例化,以及封装的意义,若仍有不理解的部分可以移步上一篇文章  【C++】类与对象(引入) 目录 系列文章 1.默认成员函数 2.构造函数 2.1定义 2.2特性 2.2.1重载构造函数 2.2.2与缺省参数混合使用 2.2.3默认构造函数 3.析构函数 3.1定义

    2023年04月15日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包