构造函数与析构函数

目录

前言

一、构造函数

1. 构造函数的引入

2. 构造函数的特性

1). 函数名与类名相同。2). 无返回值。

3). 对象实例化时编译器自动调用对应的构造函数。

经典面试题:

4). 构造函数可以重载。

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

6).默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理,其中c++把变量分成两种,内置类型/基本类型:int/char/double/指针 ,自定义类型:class/struct去定义类型对象。

思考一个问题?

注意:构造函数只有三种:

二、析构函数

1. 概念

2. 特性

3. 笔试题-析构构造的顺序问题

4. 最后,我们再详细理解一下析构函数。

总结

前言

c++类与对象(二)(上)


一、构造函数

1. 构造函数的引入

class Data
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1;
	//d1.Init(2022, 9, 27);
	d1.Print();//忘记初始化的话输出随机值
	return 0;
}

        对于Data类,可以通过 Init 公有方法给对象设置日期,如果不进行初始化,将输出随机值,写代码的人很容易忘记进行初始化,且如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
        构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

在函数F中,本地变量a和b的构造函数(constructor)和析构函数(destructor)的调用顺序是: ( )

Class A;

Class B;

void F() {

A a;

  B b;

  }


A.b构造 a构造 a析构 b析构
B.a构造 a析构 b构造 b析构
C.b构造 a构造 b析构 a析构
D.a构造 b构造 b析构 a析构

2. 构造函数的特性

        构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:

1). 函数名与类名相同。
2). 无返回值。

class Data
{
public:
	Data()//函数名和类名相同,无返回值,自动调用的构造函数,保证初始化
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}	
	void Print()
	{

		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{	
	Data d1;
	d1.Print();
	return 0;
}

3). 对象实例化时编译器自动调用对应的构造函数。

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

Data d1();会报错

注意以下代码,存在歧义,Data d1,会不知道调用谁

经典面试题:

class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;
}

 4). 构造函数可以重载。

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

注意:推荐构造函数自己写,推荐全缺省或者半缺省

 

6).默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理,其中c++把变量分成两种,内置类型/基本类型:int/char/double/指针 ,自定义类型:class/struct去定义类型对象。

 下面将分几个情形进行对第六点特征进行理解:

情形一:MyQueue使用默认构造函数时:

对于q进行了初始化!

class Stack
{
public:
	Stack()//构造函数,函数名为类名Stack,无返回值
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
class MyQueue 
{
public:
	void push(int x)
	{

	}
	int pop() 
	{

	}//注意:函数都在公共区
private:
	Stack _st1;
	Stack _st2;
};
int main()
{
	MyQueue q;
	return 0;
}

情形二:

当Stack类也使用默认的构造函数的时候:

class Stack
{
public:
	/*Stack()//构造函数,函数名为类名Stack,无返回值
	{
		_a = nullptr;
		_top = _capacity = 0;
	}*/
private:
	int* _a;
	int _top;
	int _capacity;
};
class MyQueue 
{
public:
	void push(int x)
	{

	}
	int pop() 
	{

	}//注意:函数都在公共区
private:
	Stack _st1;
	Stack _st2;
};
int main()
{
	MyQueue q;
	return 0;
}

 为什么Stack使用默认构造函数会使得q是随机值???

        因为当MyQueue中成员变量只有自定义成员变量,其不写构造函数很合理,(注意函数在公共代码区) 当其调用其默认的构造函数,其再去调用Stack的构造函数,而Stack也没写构造函数的时候,默认的构造函数不会对内置变量进行初始化,故会导致_st1/_st2为随机值

思考一个问题?

        情形一的情况,是因为MyQueue中成员变量只有自定义变量,故其在调用其默认的构造函数的时候,默认构造函数会对自定义类型进行初始化,会再去调用Stack的构造函数,进行初始化,但是如果MyQueue的成员变量中有内置类型的时候怎么办???见下图。

 所以注意:

1).如果一个类的成员全是自定义类型,我们就使用默认生成的函数
2).如果有内置成员,最好还是自己去写构造函数,或者需要参数的构造函数,也自己写

注意:构造函数只有三种:

1).默认生成的

 其中MyQueue默认生成构造函数

2).自己写的无参的

        见之前所写的日期的构造函数

3).自己写的全缺省的

 注意:以下这种情形不构成构造函数

二、析构函数

1. 概念

        通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
        析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

2. 特性

析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。

class Stack
{
public:
	Stack()//构造函数,函数名为类名Stack,无返回值
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;//可写可不写
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack st;
	return 0;
}

 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数,如下图所示。

        但是下图代码没必要写析构函数,因为不需要资源清理,因为像这些day、month、year出了作用域,栈帧就自动销毁了。

         如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,
比如上图的Data类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

class Stack
{
public:
	Stack()//构造函数,函数名为类名Stack,无返回值
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;//可写可不写
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack st;
	return 0;
}

 3. 笔试题-析构构造的顺序问题

设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?( )

C c;

int main()

{

A a;

B b;

static D d;

  return 0;

}



A.D B A C
B.B A D C
C.C D B A
D.A B D C

分析:1、类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在, 因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象

   2、全局对象先于局部对象进行构造

   3、局部对象按照出现的顺序进行构造,无论是否为static

   4、所以构造的顺序为 c a b d

   5、析构的顺序按照构造的相反顺序析构,只需注意static改变对象的生存作用域之后,会放在局部 对象之后进行析构

   6、因此析构顺序为B A D C

设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为? ( )

C c;

void main()

{

  A*pa=new A();

  B b;

  static D d;

  delete pa;

}


A.A B C D
B.A B D C
C.A C D B
D.A C B D

分析:首先手动释放pa, 所以会先调用A的析构函数,其次C B D的构造顺序为 C D B,因为先构造全局对象,在构造局部静态对象,最后才构造普通对象,然而析构对象的顺序是完全按照构造的相反顺序进行的,所以答案为 B

4. 最后,我们再详细理解一下析构函数。

        关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

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函数中创建了Data对象d,而d中包含4个成员变量,
其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
而_t是Time类对象,是自定义类型,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以需要调用Time类的析构函数进行销毁。
但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Data类对象,所以编译器会调用Data类的析构函数,而Data类中没有显式的析构函数,则编译器会给Data类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,再进行销毁。
而Time类的析构函数是自己写的,则调用cout << "~Time()" << endl;该语句,输出~Time(),
即当Data对象销毁时,要保证其内部每个自定义对象都可以正确销毁。
注意:创建哪个类的对象则调用该类的析构函数,销毁哪个类的对象则调用哪个类的析构函数。

总结

        构造函数与析构函数的正确使用。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值