C++从零开始的打怪升级之路(day5)

2024-01-10 04:48:03

这是关于一个普通双非本科大一学生的C++的学习记录贴

在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料

那么开启正题

今天继续学习了类和对象,对c++的理解更深了一点点,由于考试周影响进度更新有点慢,下面开始分享

1.拷贝构造函数

1.1概念

拷贝构造函数::只有一个形参(this指针不算),该形参是对本类类型对象的引用,一般加上const修饰,在类类型对象创建新对象时由编译器自动调用

1.2特性

1.拷贝构造函数是构造函数的一个重载形式

2.拷贝构造函数的参数只有一个,且是类类型对象的引用,使用传值方式编译器会直接报错,因为会引发无穷递归调用

class Date
{
public:
	Date(int year = 0, 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;
	}

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

int main()
{
	Date d1(2024, 1, 9);
	Date d2(d1);
	Date d3 = d2;//这两种方式,编译器都会自动识别调用拷贝构造函数

	return 0;
}

关于为什么不能使用值传递,对于内置类型的值传递,编译器可以进行处理,而对于自定义类型的值传递需要借助函数来传递,这里的函数就是拷贝构造函数,而我们要实习的拷贝构造函数,这里就涉及到先有鸡还是先有蛋的问题,显然我们是只能使用引用传参的

3.如果我们未显式定义,编译器会生成默认的构造拷贝函数

注意::默认的拷贝构造函数对象按内存存储方式按字节完成拷贝,这种叫做浅拷贝,或者值拷贝(对应的肯定有深拷贝的存在,这里我们还涉及不到,后面再了解)

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

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

int main()
{
	Date d1(2024, 1, 9);
	Date d2(d1);
	Date d3 = d2;//这两种方式,编译器都会自动识别调用拷贝构造函数

	return 0;
}

这样的代码可以正常运行,并且得到我们的预期结果

4.编译器生成的默认构造函数已经可以完成字节序的值拷贝,但是有很多情况下,但但的值拷贝是不够的

class Stack
{
public:
	Stack(int capacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_capacity = capacity;
		_size = 0;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _capacity;
	int _size;
};

int main()
{
	Stack s1;
	Stack s2(s1);

	return 0;
}

上面这段代码运行起来就会出错,当然拷贝构造函数是可以正常调用的,但是到了析构函数清理资源的时候,就有问题了,两个对象的*_a是指向同一块内存的,数据的调用肯定会出现错乱,并且,在析构函数清理资源阶段,*_a被释放了两次,程序就崩溃报错了

实际运用当中,这种涉及动态空间管理的拷贝,我们应使用深拷贝,具体怎么实现,在后面再深入

5.拷贝构造函数的典型调用场景

void Func1(Stack s)
{
	;
}

Stack Func2()
{
	;
}

int main()
{
	Stack s1;
	Func1(s1);
	Func2();

	return 0;
}

a.使用已经存在的对象创建新对象

b.函数参数类型为类类型对象

c.函数返回值类型为类类型对象

注意::为了提高效率,一般能使用引用传参的情况,尽量使用引用传参

2.赋值运算符重载

2.1运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字,以及参数列表

函数名字::关键字operator后面接需要重载的运算符符号

函数原型::返回值类型operatorca操作符(参数列表)

这里还有一些需要注意的点

a.不能C++本来就没有的运算符(@)

b.重载操作符必须有一个类类型参数(this指针也算)

c.用于内置类型的运算符,不能改变其含义

d.作为类函数重载时,其形参看起来比操作数少1,因为函数形参列表隐含this

e.(.*)(::)(sizeof)(?:)(.)这五个运算符不能被重载

2.2赋值运算符重载

1.赋值运算符重载格式

参数类型::const T& ,传递引用可以提高传递效率

返回值类型,T&,返回值引用提高传递效率,目的时为了支持连续赋值

检查是否自己给自己赋值

返回*this,要符合连续赋值的含义(从右向左赋值,返回值是左值)

利用上述描述,我们可以对类类型日期写出如下代码

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

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

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

		return *this;
	}

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

当然以上代码是有问题的,其中*this != d编译器无法处理,明天我们会写出 != 的重载函数

2.赋值运算符只能重载成类的成员函数不能重载成全局函数

因为赋值运算符如果不显式实现,编译器会生成一个默认的,此时我们再自己实现一个全局的赋值运算符重载,就和编译器类中得默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

3.我们没有显式实现得时候,编译器会生成一个默认赋值运算符重载,以值得方式逐字节拷贝

注意::内置类型得变量是直接赋值得,而自定义类型成员函数需要调用对应类的赋值运算符重载完成赋值

注意::如果类中未涉及到资源管理,赋值运算符重载是否实现都可以,但是涉及到资源管理必须要直接实现,这与拷贝构造函数相似

2.3前置++和后置++重载

后置++的重载因为是++后返回目标,我们可以返回引用,不需要创造临时变量

前置++的重载因为是++前返回目标,我们必须要创建临时变量存储操作前的类,再进行操作,最后返回临时变量,因为临时变量出作用域后不在拥有访问权限,所以我们只能值传递,不能传引用

综上::在能使用后置++的情况下我们尽量使用后置++,这样有利于提高程序运行效率

总结::拷贝构造函数和赋值运算重载让我们对类的使用更加方便,但是它有很多细节值得我们注意,作为后面的打下基础,我们应该深刻理解学习

今天的博客就到这里了,后续内容明天分享,最近因为考试周原因不能更新太多内容,等考试周结束了再"快马加鞭"

新手第一次写博客,有不对的位置希望大佬们能够指出,也谢谢大家能看到这里,让我们一起学习进步吧!!!

文章来源:https://blog.csdn.net/2301_80764270/article/details/135492021
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。