C++初阶:类与对象(中篇)

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

  1. 默认成员函数
    <1> 成员函数,即所属与定义类的函数,只针对所属类,只有所属类可以调用的函数,而默认成员函数即为定义类时类会默认生成自带的函数。
    <2> 既然会默认生成,那么一定的这些函数一定非常重要并且不可或缺,究竟什么样的函数会让类定义时要默认生成呢,我们接下来进行学习

2.1 构造函数

  1. 在C语言的学习中,我们定义过各种各样的结构体,这些结构体声明出来,只是单纯开辟了对应的空间,而空间里的值是随机的,因此对新创建结构体变量的初始化是必不可少的,会影响到后续操作。
  2. 对于C++中的类,也是如此,因此对实体化对象的初始化必不可少且十分重要,而我们自主编写时,会经常忘记定义或者调用,所以在C++中就添加了此函数的默认生成,自动调用的内容。

2.1.1 构造函数的定义方式

  1. 构造函数在整个对象的声明周期中会自动调用,且调用一次
  2. 构造函数只负责初始化不会开辟空间
  3. 构造函数的形式:
    <1> 函数名与类名相同
    <2> 无返回值
    <3> 实例化对象会自动调用相应的构造函数
    <4> 构造函数可以重载
  1. 默认生成的构造函数

内置类型:

class Date
{
public:
	int _year;
	int _month;
	int _day;

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

Date d1;
d1.Print();

在这里插入图片描述
自定义类型:

class stack
{
public:
	int _capacity;
	int _top;

	stack(int capacity = 0, int top = 0)
	{
		cout << "stack()" << endl;
		_capacity = capacity;
		_top = top;
	}
};

class queue
{
public:
	stack push_st;
	stack pop_st;
};

queue q1;

在这里插入图片描述

  1. 虽然编译器会帮助我们生成构造函数,可是默认生成的构造函数并不让人满意
    <1> 默认生成的构造函数对内置类型会赋予随机值
    <2> 对自定义类型会去调用它们的默认构造函数
  2. 所以,在日常中我们还是应该去自己给类定义需要适合的构造函数
  1. 构造函数的定义方式
[类名](传递形参)
{
	//函数操作
}
class Date
{
private:
	int _year;
	int _month;
	int _day;

	Date(int year = 0, int month = 0, int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
};
  1. 带参数构造函数的传参方式:在实例化对象是直接进行传参,同时也支持缺省参数
Date d1(2024, 3, 8);
Date d2(2024);
  1. C++11中,为默认构造函数对成员变量初始化为随机值的这一操作,做了补丁优化
    我们在定义类的成员变量时,可以在定义处赋予变量初始默认值,这样在我们没有定义构造函数时,自定义类型的成员变量也会有合法的初始值
class Date
{
private:
	int _year = 2024;
	int _month = 1;
	int _day = 1;
};
  1. 默认构造函数不止仅仅是只,我们未定义编译器默认生成的函数
    <1> 未定义编译器默认生成的构造函数
    <2> 没有参数的构造函数
    <3> 参数为全缺省参数的构造函数
    以上三者统称为默认构造函数

2.2 析构函数

  1. 存在资源申请,就一定会存在资源的返还,我们已经知晓了如何创建一个对象,那么,我们如何销毁它呢。
  2. 正常情况下创建的变量会在自己的声明周期结束自动销毁,可是我们动态开辟的空间,这类资源只要程序不结束,我们不去主动销毁那么它就会一直存在,当一段程序需要不停的运行时,这一点就是致命的,因此析构函数对实例化对象资源的管理就尤为重要。

2.2.1 析构函数定义方式

  1. 默认生成的析构函数不会进行资源的深度清理(指针所指向动态开辟的空间),只会进行资源的简单清理
class A
{
private:
	int* _a;
	int _b;

	A()
	{
		//销毁
		_b = 10;
		//所指向空间不会被释放
		_a = (int*)malloc(_b * sizeof(int));
	}
};
  1. 析构函数的特征及其定义方式
    <1> 函数名由:~ + 类名构成
    <2> 没有参数,没有返回值
    <3> 一个类只存在一个析构函数,不存在重载
    <4> 析构函数会在实例化对象生命周期结束时自动调用
~[类名]()
{
	//操作
}
class B
{
privare:
	int* _a;;

	B()
	{
		_a = (int*)malloc(sizeof(int));
		
	}

	~B()
	{
		cout << "~B()" << endl;
		free(_a)
	}
};
  1. 自定义类型的成员变量,默认析构函数会去调用它自己的析构函数
    拓展练习

2.3拷贝构造函数

  1. 使用另一个已经存在的实例化对象,将其作为模板,创建一个与之一样的新对象

2.3.1 拷贝构造函数的定义方式

  1. 拷贝构造函数的特征:
    <1> 拷贝构造函数为构造函数的函数重载
    <2> 拷贝构造函数只有一个参数,参数类型为所处类的类型引用
  1. 补充:拷贝构造函数参数类型必须为引用类型的原因(产生无穷递归)
//err
Date (const Date d)
{}

在这里插入图片描述

2.3.2 深拷贝与浅拷贝

  1. 拷贝构造也为类的默认成员函数之一,当我们不去自己定义时,编译器自主生成一个拷贝构造函数,可是此拷贝构造只会进行值拷贝不会对动态申请得进行拷贝

在这里插入图片描述

  1. 我们将值拷贝称为浅拷贝,将对原变量的深度资源也进行拷贝的操作称为深拷贝。
  2. 当自定义类中没有需要深度拷贝的资源时,我们也可以省略拷贝构造的定义,让编译器自动生成。
  3. 拷贝构造函数使用场景:
    <1> 用已存在的对象创建一个一样的新对象
    <2> 函数参数为类(生成临时变量)
    <3> 函数返回值为类(生成临时变量)
Date d1(2024, 3, 9);
//场景1:
Date d2(d1);

void Print(Date d)
{
	cout << d._year << endl;
}
//场景2
Print(d2);

//场景3:
Date f()
{
	Date d3(2024);
	
	return d3;
}
  1. 上述场景为生成临时变量而调用拷贝构造,在一些时候可以使用,引用传参(不改变值),引用返回(原对象不被销毁)来规避这一额外开销。

2.4 赋值运算符的重载

2.4.1 运算符重载

  1. 我们知道编程语言也是一门语言,是用来描述问题与计算机沟通的语言。
  2. C++中,我们引入了类与对象的概念,让计算机可以更好更贴切的描述我们所处的世界。
    <1> 对象是更加复杂,贴近于现实的变量,C中的内置类型变量有着自己运算操作符于运算规则
    <2>可是,内置类型的运算符不能使用于更复杂的类上,这就导致当对进行这类复杂对象间的计算无法进行,由此,C++引入了运算符重载的概念。
  1. 运算符重载的本质是函数,这类函数以类似于运算符的函数调用方式,来达到近似运算符的操作。

2.4.2 运算符的重载的定义方式

[函数返回值] operator[操作符](函数参数)
{
	//.......
}
  1. 运算符重载的注意事项:
    <1> 不能通过运算符重载来创建原本不存在的运算符,如operator¥
    <2> 不能通过运算符重载来更改原本内置类型运算符的含义
    <3> 操作符.*::sizeof: ?.,这五个运算符不能重载

2.4.3 默认成员函数:赋值运算符重载

  1. 赋值运算符:拥有两个运算数,会返回被赋值后的变量
  2. 赋值运算符的优化:
    <1> 传参时,因为不会改变参数原本的值,所以可以直接传引用(const 类&)
    <2> 因为被赋值的对象不会被销毁,所以可以返回引用(返回自身支持连续赋值)
    <3> 赋值时应进行检测,不能让对象自己给自己赋值
class A
{
public:
	int _a;
	int _b;
	
	A(int a = 0, int b = 0)
	{
		_a = a;
		_b = b;
	}
	
	A& operator=(const A& x)
	{
		if(&x != *this)
		{
			_a = x._a;
			_b = x._b;
		}
		
		return *this;
	}
};
  1. 注:重载的赋值运算符只能作为类的成员函数,不能作为全局函数(缺少this指针)
  1. 因为是默认成员函数,当我们没有定义时,编译器会自行生成,但与拷贝构造相似,默认生成的赋值运算符重载进行的也是浅拷贝,可能会导致资源的丢失

在这里插入图片描述

2.4.4 前置++,后置++的运算符重载

  1. 我们已经初步学习了运算符的重载,现在我们来学习一下运算符重载中比较特殊的两个运算符,前置++与后置++
  2. 每当需要对一个运算符进行重载时,我们在思考实现逻辑之前,首先应该确定的是运算符有几个操作数,是否有返回值。当我们以这样的视角去观察前置++与后置++这一对操作符时,我们发现它们重载时它们参数是相同,无法进行区分,C++语言在出现了逻辑漏洞,无法形成逻辑的闭环。
  3. 其实,上述无法逻辑闭环的逻辑漏洞在语言的设计上时有出现,这种情况下,一律都会采取特殊化处理,这里的两个++运算符同样如此。

前置++运算符的重载方式:

[返回值] operator++()
{
	//......
}

后置++运算符的重载方式:

[返回值] operator++(int)
{
	//......
}

2.5 类与对象穿插巩固练习:日期类的实现

实现目标:

  1. 构造,拷贝构造,析构函数
  2. 赋值运算符==,+,-,前置++,后置++,+=,<,>等运算符的重载

Date.h

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	int GetMonthDay(int year, int month)
	{
		int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

		int day = MonthDay[month];
		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
		{
			if (month == 2)
			{
				day++;
			}
		}

		return day;
	}

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

	Date(int year = 2024, int month = 1, int day = 1);
	//缺省参数在声明处定义

	Date(const Date& d);

	~Date();

	Date& operator=(const Date& d);

	Date operator+(int day);

	int operator-(const Date& d);
	Date operator-(int day);

	Date& operator+=(int day);

	Date& operator-=(int day);

	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);

	//前置
	Date& operator++();
	//后置
	Date operator++(int);

	Date& operator--();
	Date operator--(int);
};

Date.cpp

  1. 默认成员函数
Date::Date(int year, int month, int day)
{
	//检查日期合法
	assert(year >= 1);
	assert(month >= 1 && month <= 12);
	assert(GetMonthDay(year, month) >= day && day >= 1);

	_year = year;
	_month = month;
	_day = day;
}

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

Date::~Date()
{}

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

	return *this;
}
  1. 其他运算符重载
    补充:运算重载的复用顺序
    <1>先+,后+=:创建拷贝对象(拷贝构造1次)+ 返回临时变量(拷贝构造1次)+ 赋值给this指针(拷贝1次)+ 返回this指针
    <2> 先+=,后+:创建拷贝对象(拷贝构造1次)+返回临时变量(拷贝构造1次)
//2.
Date& Date::operator+=(int day)
{
	//1. 先加后合法化
	//2. 累加法
	_day += day;
	int month_day = GetMonthDay(_year, _month);
	while (_day > month_day)
	{
		_day -= month_day;
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}

		month_day = GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator+(int day)
{
	Date d(*this);

	return *this += day;
}

//先实现-=
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day < 1)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		int month_day = GetMonthDay(_year, _month);
		_day += month_day;
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date d(*this);

	return d -= day;
}

//差值法
//int Date::operator-(const Date& d)
//{
//	//确定先后,前 - 后
//	Date max(*this);
//	Date min(d);
//
//	if (max._year < min._year)
//	{
//		max = d;
//		min = *this;
//	}
//
//	//计算到1月1日的差值
//	int max_day = 0;
//	for (int i = 1; i < max._month; i++)
//	{
//		max_day += GetMonthDay(max._year, i);
//	}
//	max_day += max._day - 1;
//
//	int min_day = 0;
//	for (int i = 1; i < min._month; i++)
//	{
//		min_day += GetMonthDay(min._year, i);
//	}
//	min_day += min._day - 1;
//
//	//两年1月1日之间的差值
//	int year_day = 0;
//	while (min._year < max._year)
//	{
//		year_day += 365;
//		if ((min._year % 4 == 0 && min._year % 100 != 0) || min._year % 400 == 0)
//		{
//			year_day++;
//		}
//		min._year++;
//	}
//
//	return year_day - min_day + max_day;
//}

//累计法
int Date::operator-(const Date& d)
{
	Date max(*this);
	Date min(d);

	if (max._year < min._year)
	{
		max = d;
		min = *this;
	}

	int day = 0;
	while (max != min)
	{
		min++;
		day++;
	}

	return day;
}

-操作符重载差值法思路图解:

在这里插入图片描述

  1. 逻辑比较运算符:
bool Date::operator==(const Date& d)
{
	if (_year == d._year && _month == d._month && _day == d._day)
	{
		return true;
	}

	return false;
}

bool Date::operator>(const Date& d)
{
	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)
{
	return !(*this == d) && !(*this > d);
}

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

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

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

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

	return tmp;
}

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

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

2.6 const成员函数

  1. 被this指针被修饰const修饰的成员函数即被称为const成员函数(const Date* const this),被const修饰的this指针其指向的内容不能被改变。
  2. 因为this指针为隐藏参数,我们无法直接对其进行修饰,C++中采用了后缀修饰的方式,具体如下:
Date::Print() const
{
	count << _year << '-' << _month << '-' << _day << endl;
}
  1. 函数传参时的权限缩可以缩小但不能扩大,所以
    <1> const对象不可以调用非const成员函数(权限扩大,const Date* const this => Date* const this)
    <2> 非const对象可以调用const成员函数(权限缩小,Date* const this => const Date* const this)
    <3> const成员函数内不可以调用其它的非const成员函数(权限扩大)
    <4> 非const成员函数内可以调用其它的const成员函数(权限缩小)

2.7 默认成员函数:取地址操作符重载与const取地址

这两个默认成员函数一般不用我们自己定义,编译器默认生成的已经满足绝大部分情况。(当想要别人获得额外的一些信息时,才会选择重载)

Date* operator&()
{
	return this;
}

const Date* operator&() const
{
	return this;
}
  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值