【C++】构造与析构

【C++】构造与析构

1、默认成员函数

class test
{};

假若一个类什么成员都没有,那便称为空类。

但编译器也会为空类生成6个默认成员函数。

image-20230204152717703

2、构造函数

2.1 构造函数概念

构造函数是一个特殊的成员函数名字与类名相同

创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

类对象被创建的时候,编译系统对象分配内存空间,并自动调用构造函数,由构造函数完成成员的初始化工作

构造函数的作用:初始化对象的数据成员

2.2 构造函数特性

  1. 名字与类名相同,可以有参数,但是不能有返回值(连void也不行)。
  2. 构造函数是在实例化对象时自动执行的,不需要手动调用。
  3. 作用是对对象进行初始化工作,如给成员变量赋值等。
  4. 如果定义类时没有写构造函数,系统会生成一个默认的无参构造函数,默认构造函数没有参数,不做任何工作。
  5. 如果定义了构造函数,系统不再生成默认的无参构造函数.
  6. 对象生成时构造函数自动调用,对象一旦生成,不能在其上再次执行构造函数
  7. 一个类可以有多个构造函数,为重载关系。

2.3 构造函数分类

2.3.1 默认构造

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数

#include<iostream>

using namespace std;

class build
{
public:
		// 无参构造函数
		// 如果创建一个类你没有写任何构造函数,则系统自动生成默认的构造函数,函数为空
		// 如果自己显示定义了一个构造函数,则不会调用系统的构造函数
	build()
	{}
};

2.3.2 有参构造

一般构造分为两种:

  1. 列表初始化
  2. 内部赋值

两者不能混用

#include<iostream>

using namespace std;

class build
{
public:
		//一般构造函数
		//可以采用两种方式进行对类内成员的赋值,一种是列表初始化,而另一种是内部赋值,但二者不能同时存在
	build(int a,int b)
	{
		_x = a;
		_y = b;
	}//内部赋值

	build(int a,int b):_x(a),_y(b)
	{}//列表初始化

private:
	int _x;
	int _y;
};

2.4 拷贝构造函数

2.4.1 概念

拷贝构造函数:

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

  • 一种特殊的构造函数,当对象之间复制时会自动调用拷贝构造函数。
  • 若类中没有显示定义拷贝构造函数,则系统会自动生成默认拷贝构造函数。
  • 使用场合:旧对象初始化新对象

2.4.2 特性

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

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

class Date
{
public:
 	Date(int year = 1900, int month = 1, int day = 1)
 	{
		_year = year;
 		_month = month;
 		_day = day;
 	}
 // Date(const Date d)   // 错误写法:编译报错,会引发无穷递归
    Date(const Date& d)   //正确写法
	{
		_year = d._year;
 		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

若未显式定义,编译器会生成默认的拷贝构造函数。

默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

注意:

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的

这时就有以下问题:

既然会生成默认的拷贝构造函数,那是不是意味着没有显式实现拷贝构造函数的必要了呢?

默认的拷贝构造函数的值拷贝会将内容原封不动地给构造的对象,这就使得拷贝的对象与原来的对象指向同一块内存空间

这样就会导致在程序退出时内存的多次释放,从而造成程序的崩溃

注意:

类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝

对应的解决方法就是深拷贝:

  • 深拷贝就是对于对象中的动态成员,并不只是简单的赋值,而是重新分配空间,即资源重新分配

2.4.3 使用场景

拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用

2.4.4 重载

拷贝构造函数重载与普通的函数重载基本是没有区别,就是同一个函数名因为参数不同代表不同的函数,只是这里的构造函数都没有返回值

3、析构函数

3.1 概念

我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的

而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

3.2 特性

  1. 析构函数名是在类名前加上字符 ~

  2. 无参数无返回值类型

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数

    注意:析构函数不能重载

  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

class Time
{
public:
 	~Time()
 	{
 		cout << "~Time()" << endl;
 	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
 	// 基本类型(内置类型)
 	int _year = 1970;
 	int _month = 1;
 	int _day = 1;
 	// 自定义类型
 	Time _t;
};
int main()
{
 Date d;
 return 0;
}
//在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
//因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
//但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
//main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数

3.3 要点

注意:

  1. 创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
  2. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类

猜你喜欢

转载自blog.csdn.net/qq_64893500/article/details/128883749