C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值

目录

类的6个默认成员函数

 构造函数

析构函数 

析构函数使用场景 

场景1:不需要清理,但会执行默认析构函数 

 场景2:需要析构函数

默认生成析构函数特点 

 构造函数和析构函数的顺序问题

拷贝构造函数

特征 

 拷贝构造在传值传参和传引用传参的区别

拷贝构造在传值返回和传引用返回的区别 

运算符重载 

运算符重载应用 

 赋值运算符重载

赋值重载连续赋值 


类的6个默认成员函数

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

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第1张图片

 构造函数

 我们在使用类时,一般会先使用初始化函数,但是有人会忘记进行初始化,有些情况下忘记初始化会导致程序崩溃或出现一些错误C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第2张图片

 C++中为了解决这个问题,提出了构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

5.一个类里面的默认构造只能有一个,不然调用的时候编译器会凌乱
构造函数的函数名和类名相同,此时并未使用初始化函数,传参的时候直接写到对象名后面

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第3张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第4张图片

 构造函数也满足缺省参数的特性

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第5张图片

 这俩种同时存在会存在歧义,因为如果不传参数,此时编译器会凌乱,不知道调用那个函数

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第6张图片

 以下这种情况也会报错,这是因为有俩个构造函数

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第7张图片

 这种情况也是因为有俩个构造函数C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第8张图片

 一般这三种方式,只写一个

构造函数写栈

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第9张图片

 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成(如果没有构造函数,编译器自动生成一个构造函数,如果有构造函数,编译器就不会自动生成

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第10张图片

 此时会自动默认生成,但是产生的值是随机值

C++类型分类:1.内置类型:int/double/char/指针等等

                          2.定义类型:struct/class主要是类类型

默认生成构造函数,对内置类型不做处理,对自定义类型成员回去调用它的默认构造函数,

这是C++早期的一个缺陷,默认生成构造函数,本来应该内置类型也一并处理

C++11打了补丁这样做

class Date
{
public:
	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 ;   // 注意这里不是初始化,给缺省值
	int _month ;
	int _day ;
};
typedef int DataType;
class Stack
{
public:
	Stack(int capacity=4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_size = 0;
		_capacity = capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

// C++类型分类:
// 内置类型/基本类型:int/double/char/指针等等 
// 自定义类型:struct/class

class MyQueue
{
private:
	Stack _st1;
	Stack _st2;
};
int main()
{
	MyQueue q();
	return 0;
}

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第11张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第12张图片

 默认构造函数有三类:

1.我们不写,编译器自动生成的那个

2.我们自己写的全缺省构造函数

3.我们自己写的,无参的构造函数

默认构造函数特点:不传参数就可以调用

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第13张图片

当我们做修改后,此时程序会报错,因为这里,既不是无参的,也不是全缺省的,而且由于构造函数名和类同名,这里相当于我们自己写了一个构造函数,所以编译器在这里不会生成默认构造函数

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第14张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第15张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第16张图片

析构函数 

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
 析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数(这是因为没有参数)。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

析构函数使用场景 

场景1:不需要清理,但会执行默认析构函数 

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

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	~Date()
	{
		//日期类的没什么需要清理的
		cout << "~Date()" << endl;
	}
private:
	int _year = 1;   // 注意这里不是初始化,给缺省值
	int _month = 1;
	int _day = 1;
};
void fun()
{
	Date d1;
}
int main()
{
	fun();
	return 0;
}

 此时程序执行完fun函数

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第17张图片

再按F11,程序直接来到了析构函数这里

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第18张图片

 最终结果

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第19张图片

 默认的析构函数什么都不做

 场景2:需要析构函数

默认的析构函数不会处理这里,这里的数组在堆上

typedef int DataType;
class Stack
{
public:
	Stack(int capacity=4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		free(_array);
		_capacity = _size = 0;
		_array = nullptr;
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void fun()
{
	Date d1;
	Stack st;
}
int main()
{
	fun();
	return 0;
}

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第20张图片

执行完fun函数后,先进入Stack的析构函数,再进入Date的析构函数 

之前博客里写栈需要写初始化Init函数和释放空间的Destory函数,而在C++中写一个构造函数和析构函数不需要调用即可,析构函数就会代替Destory的作用来释放空间(要自己写)

默认生成析构函数特点 

跟构造函数类似,内置类型不处理,自定义类型处理,自定义类型会调用自己的构造 ,指针属于内置类型,指针不处理,因为指针有的是指向动态开辟空间,有的指向一个数组,还有文件指针

当写栈,队列,链表,顺序表,二叉树用自己写的析构函数就比较方便

 构造函数和析构函数的顺序问题

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第21张图片 先构造的后析构,后构造的先析构

 这是因为这里的内容存在栈中,要满足先进后出

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第22张图片

 main 先调用f1,f1再调用f2,然后f2销毁返回f1,满足先进后出

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第23张图片

 先初始化全局的,当进入main函数后按顺序初始化

对于析构,变量销毁后就进行析构,aa2和aa1在栈区,栈帧结束后要清理资源先清理aa2,再清理aa1,全局变量和静态变量在函数结束以后才销毁,之后清理资源,所以先清理aa0再清理aa3

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第24张图片

 构造顺序:3 0 1 2 4 1 2

 析构顺序:2 1 2 1 4 0 3

拷贝构造函数

 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

特征 

 拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
 

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第25张图片

对于get1,形参是实参的一份的临时拷贝,get2,形参是实参的别名

参数也是类类型,用d1去初始化d即传参的时候,就需要构造函数(初始化函数),get1的构造是拷贝构造,get2还没传参的时候就已经构造好了,在Date d1这条语句里已经构造好了

验证上面的结论

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第26张图片

这条语句之后按F11,直接跳到拷贝构造这里来

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第27张图片

 先构造d1,再给get1构造C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第28张图片

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第29张图片

若想把d1拷贝过去,这俩种写法都可以 

若拷贝构造函数这样写,则会进入死循环,因为形参必须是类类对象的引用

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第30张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第31张图片 解决这种传参问题有俩种办法:1.引用

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第32张图片

2.指针(这种方式不是拷贝构造),虽然也能完成,但是不建议这种方法,这种方法比较奇怪,更容易出现错误

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第33张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第34张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第35张图片

 有时候拷贝构造函数容易写错,所以要加const

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第36张图片

 正确形式C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第37张图片

 未定义拷贝构造

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第38张图片

 

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第39张图片

对内置类型按直拷贝进行拷贝的:就是一个字节,一个字节拷贝过去

对自定义类型,调用自定义类型自己的拷贝构造函数完成

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
拷贝构造还有深浅拷贝问题,后面的博客里会写

浅拷贝举例

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第40张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第41张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第42张图片

 这里能正常拷贝,但是在析构的时候会崩溃

这是因为这里执行了俩次析构函数,由于这里是直拷贝,st和st1指向了同一块空间,而这快空间被释放了俩次, 因为这里是内置类型,指针是内置类型,把*array所指向的空间给st1拷贝过去了,然后st1和st指向了同一块空间,析构的时候又free了俩次,直接崩溃

 拷贝构造在传值传参和传引用传参的区别

#include
using namespace std;
class A
{
public:
	A(int a = 0)
	{
		_a = a;
		cout << "A(int a=0)>>" <<_a<< endl;
	}
	A(const A& aa)
	{
		_a = aa._a;
		cout << "拷贝构造A(int a=0)>>" << _a << endl;
	}
	~A()
	{
		cout << "~A()>>" <<_a<< endl;
	}
private:
	int _a;
};
void func1(A aa)
{

}
void func2(A& aa)
{

}

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第43张图片

 传值传参有一次拷贝构造,这个拷贝构造是针对aa1的拷贝构造

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第44张图片

引用传参没有拷贝构造 

拷贝构造在传值返回和传引用返回的区别 

#include
using namespace std;
class A
{
public:
	A(int a = 0)
	{
		_a = a;
		cout << "A(int a=0)>>" <<_a<< endl;
	}
	A(const A& aa)
	{
		_a = aa._a;
		cout << "拷贝构造A(int a=0)>>" << _a << endl;
	}
	~A()
	{
		cout << "~A()>>" <<_a<< endl;
	}
private:
	int _a;
};
A fun3()
{
	static A aa;
	return aa;
}
A& fun4()
{
	static A aa;
	return aa;
}
int main()
{
	A aa1(1);
	fun3();
	cout << endl;
	fun4();
	return 0;
}

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第45张图片

 传值返回时,进行了一次拷贝构造

第一个:A(int a=0)>>1是A aa1(1)的默认构造

第二个:A(int a=0)>>0是fun3函数里面对 static A aa的默认构造

拷贝构造A(int a=0)>>0是fun3函数执行return语句时,对aa的拷贝构造

这是因为传值返回在返回时,会拷贝一份aa,然后返回的是拷贝的aa,对于aa本身,在函数结束后就会销毁,对于这份拷贝的aa,就要调用拷贝构造函数

对于拷贝的aa,也是有生命周期的,它的生命周期在main函数fun3();这一行,当这一行调用结束之后,就要拷贝的aa进行析构

稍作修改,使观察更清晰

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第46张图片

 对于引用返回,不需要拷贝构造函数,直接调用构造函数,调用过程如下图

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第47张图片

 析构过程如下C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第48张图片

 这里aa(3)和aa(4)本身在静态区,所以后析构,如果aa出了fun4()的作用域就销毁,那么引用返回就有问题,前面博客里有提到过

运算符重载 

 这里有俩个日期,d1和d2,我们比较它们的大小关系

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第49张图片

 C++中规定:内置类型可以直接使用运算符运算,编译器知道如何运算,自定义类型无法直接使用运算符,编译器不知道如何运算

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表),返回值类型由运算符特点决定,如比较大小一般返回true或false,若想知道差值,返回int
注意:
不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this.*   ::   sizeof   ?:   . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

我们可这样来对日期判断是否相等,但是这里会报错,因为日期这些是私有的

解决方法:

1.写一个函数来获取这些私有数据 ,月和天也同理

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第50张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第51张图片

 将上面的私有数据改为共有,再进行测试

using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "构造函数" << endl;
	}
	//Date(const Date& d)//拷贝构造函数
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	int _year = 1;   // 注意这里不是初始化,给缺省值
	int _month = 1;
	int _day = 1;
};
int main()
{
	Date d1(2022,7,23);
	Date d2(2022, 8, 23);
	cout<<(d1 == d2) << endl;
	return 0;
}

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第52张图片

 编译器实际会将cout<<(d1==d2)<

本质是函数调用

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第53张图片

编译器再工作时,如果发现d1,d2是内置类型,直接转换为相对应的指令,如果不是内置类型就看它是否满足运算符重载的条件,如果满足就执行

 为什么不直接写一个调用函数呢?

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第54张图片

 如果写成这样,就没什么价值了,写成运算符重载,就能像内置类型一样去调用运算符,写成运算符重载可读性更好,如果写成函数,有人会不规范写函数名,导致其他人看不懂

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第55张图片

 在传参的时候,如果没有引用就会进行拷贝构造,如果是深拷贝,就会很麻烦,所以在传参的时候要引用,为了防止别人写错,最好加上const

但运算符重载,还存在一个问题,就是内置类型,一般是私有,上面为了演示效果我们改为了共有,现在改回来,但是会报错

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第56张图片

 为了解决这个问题,我们把运算符重载函数写道类里面,还会报错,参数太多

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第57张图片

 参数太多是因为,还有一个隐藏的参数this指针

我们做以下修改即可

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第58张图片

_year==d2._year,这里的_year实际是this->_year,this就是d1

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第59张图片

运算符重载应用 

 对于日期类,我们可以比较大小,也可以去查询N天以后/以前是几几年几月几号

思路:1.直接把天数加到day

           2.若day超过了该月的总天数,用day-该月总天数,再给月+1,12月要把1,给年加1

	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		//多次调用的时候,每次都要创建数组,现在改为静态,放到静态区

		if (month == 2
			&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//闰年2月
		{
			return 29;
		}
		else
		{
			return days[month];
		}
	}
	Date operator+(int day)
	{
		
		_day += day;
		while (_day > GetMonthDay(_year,_month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)//12月特殊处理
			{
				_month = 1;
				_year++;
			}
		}
	}

这样写需要一个返回值,但是我们把日期全部加到了_year,_month,_day,因此要把这些给传回去,我们直接return *this,this是指针,*this是对象

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第60张图片

 先测试一下

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第61张图片

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第62张图片

 现在写的这个,改变了d1,如果不想改变d1,把结果返回回去,做如下修改,main函数可以这样写,用一个东西来接收,符合拷贝构造

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第63张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第64张图片

我们拷贝一份d1,就行,这样就不会改变d1了

	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		//多次调用的时候,每次都要创建数组,现在改为静态,放到静态区

		if (month == 2
			&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//闰年2月
		{
			return 29;
		}
		else
		{
			return days[month];
		}
	}
	Date operator+(int day)
	{
		Date ret(*this);
		ret._day += day;
		while (ret._day > GetMonthDay(ret._year,ret._month))
		{
			ret._day -= GetMonthDay(ret._year,ret. _month);
			++ret._month;
			if (ret._month == 13)//12月特殊处理
			{
				ret._month = 1;
				ret._year++;
			}
		}
		return ret;
	}
int main()
{
	Date d1(2022,7,23);
	Date ret = (d1 + 50);
	Date ret1(d1 + 50);
	return 0;
}

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第65张图片

 赋值运算符重载

1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
 C语言中把一个数的值,赋值给另一个数,用=就可以,C++中把一个类的值,赋值给另一个类需要用到赋值重载

如这里,要把d3赋值给d1

void TestDate1()
{
	Date d1(2022, 7, 24);
	Date d2(d1);//拷贝构造
	Date d3(2022, 8, 24);
	d1 = d3;//赋值
}
int main()
{
	TestDate1();
	return 0;
}
#include
using namespace std;
class Date
{
public:
	//构造函数会频繁调用,所以放在类里面作为inline
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第66张图片

 main函数里d1=d3;会被转换成d1.operatpr=(&d1,d3);

传d1地址是因为有this指针

赋值重载连续赋值 

 写成连续赋值就会报错 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第67张图片

 对于普通变量这种连续赋值是可以的

 连续赋值是这样的:k=j=i,把i赋值给j,然后把j的返回值赋值给k

d2=d1=d3,也遵循上面的道理

d3赋值给d1,d1的返回值赋值给d2

这里应该这样修改,this是d1的指针(地址),*this就是d1

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第68张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第69张图片

此时不会报错 ,如果不引用传参就要调用好多次拷贝构造

为了防止有人写成d2=d2,我们加上判断条件,d2=d2这条语句编译器是不会对其报错的

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第70张图片

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

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第71张图片

 写成全局的会报错

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

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第72张图片

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

屏蔽掉赋值重载函数后,仍然可以拷贝
 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第73张图片

 添加一个类进行测试

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第74张图片

 C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第75张图片

 此时调用,结论跟上面一模一样,自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第76张图片

 其实相面的time也不需要赋值重载,会默认生成一个赋值重载

对于栈这样的类型,需要写赋值重载

#include"date.h"
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack st1;
	Stack st2;
	st2.Push(1);
	st2.Push(2);
	st1 = st2;

	return 0;
}

因为默认的赋值重载,会对内置类型,实行直拷贝,指针也属于内置类型,如果对指针直拷贝,会导致俩个指针指向同一块空间,在析构的时候会析构俩次,所以会报错

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第77张图片

 对于MyQueue这样的类,可以不写赋值重载,因为如果栈的赋值重载写好了,MyQueue直接可以躺平

C++——类和对象2|构造函数|析构函数|拷贝构造函数|运算符重载|赋值运算符重载|赋值运算符连续赋值_第78张图片

 对于拷贝构造和赋值重载一些类需要些,比如栈

一些类不需要写,如Date这样的类,默认完成直拷贝/浅拷贝,如mYqueue不需要写,默认会调用自定义类型Stack的拷贝和赋值

你可能感兴趣的:(C++,c++,linux)