C++4--析构函数

1、析构函数函数

1)构造函数

构造函数是一个特殊的成员函数;
其作用是给类里面的数据成员初始化;
在对象构造的时候,系统自动的调用。

class A
{
public:
	A(int i = 4,int j = 8):m_i(i),m_j(j)
	{
		cout<<"A"<<" "<<m_i<<" "<<m_j<<endl;
	}
private:
	int m_i;
	int m_j;
};

void main()
{
	//局部对象,其作用域在其所在的{}内,出了其作用域,其所占的内存空间就将被释放,
	//如果其是在栈上开辟的空间,则不需要程序员自己释放,系统会自动回收
	A a;     //a所占8个字节,为m_i和m_j的大小。调用构造函数A
	cout<<sizeof(a)<<endl;
}

在这里插入图片描述

2)构造函数和析构函数

函数 — 什么时候调用 — 作用 — 未自己定义时,系统提供

构造函数 — 对象构造时自动调用 — 构造对象 — 默认的构造函数(无参,无返回值)
两者作用相反
析构函数 — 对象退出时自动调用— 释放对象 — 自动调用(程序员可以调用,一般情况下不去调用)

3)析构函数

编译器会提供一个默认的析构函数,函数名为 ~类名,析构函数无参(不能重载)
一个类有且仅有一个析构函数
(构造函数为什么可以带参数呢?因为构造函数,我们走构造对象的时候,初始值可以不一样,即传参不一样。但析构函数不带参数,因为其作用就是释放,不管带不带参的构造函数,其类里面的数据成员都是一样的。)
它的目的是释放
~ 表示为逆,

什么时候调用析构函数?

在改构造的对象其作用域即将消失时,即{}即将结束时。
在同一个作用域中,先构造的后析构,栈

class A
{
public:
	A(int i = 4,int j = 8):m_i(i),m_j(j)
	{
		cout<<"A"<<" "<<m_i<<" "<<m_j<<endl;
	}
	~A()
	{
		cout<<"~A"<<" "<<m_i<<" "<<m_j<<endl;  //这里打印是为了查看析构函数的调用,便于观察
	}
private:
	int m_i;
	int m_j;
};

void main()
{
	//局部对象,其作用域在其所在的{}内,出了其作用域,其所占的内存空间就将被释放,
	//如果其是在栈上开辟的空间,则不需要程序员自己释放,系统会自动回收
	A a;     //a所占8个字节,为m_i和m_j的大小。调用构造函数,所以会输出A 4 8
	//cout<<sizeof(a)<<endl;
	A b(3,6);//先构造的,后析构
    A c(2,7);

	cout<<"main"<<endl;//用于查看析构函数的调用时间
}

在这里插入图片描述

4)什么情况下,必须需要写析构函数?? --new, delete

当有指针作为数据成员时,就需要程序员写构造函数,将构造函数中new的空间释放

new , delete
malloc,free,realloc

new , delete和malloc,free…的区别

1、new 和 delete 是运算符,malloc 和 free 是库函数;
2、new 和 delete 可以调用构造和析构;
-----malloc 和 free 只能分配空间和释放空间;

new完之后可以随时delete;

为什么会出现new

class A
{
public:
	A(int i = 0):m_i(i){cout<<"A"<<endl;};
	void print()
	{
		cout<<m_i<<endl;
	}
	~A(){cout<<"A"<<endl;};
private:
	int m_i;
};

#if 0
void main()
{
#if 0
	A *p1 = NULL;  //这句话只是声明了一个A类型的指针变量,指向NULL,占四个字节
	p1 = (A*)malloc(sizeof(A)); //这句话没有构造一个对象(开辟空间+赋合法值),只是开辟了一个A类类型的空间,让p指针指向它,并没有构造对象出来
	p1->print();//为随机值,只是开辟空间,并没有构造一个对象出来
	free(p1);//只是释放空间
	p1 = NULL;
#endif

	//A *p = new A;
	//A *p = new A(6);
	//A *p = new A[5];//new 了五个A类型的对象
	//delete p;//error,delete必须是数组

	//char *p = new char;//开辟了一个字节的空间
	char *p = new char[20];//new了一个20个连续的内存空间,让p指针指向
	strcpy(p,"abcdefg");
	cout<<p<<endl;
	delete[]p; //new 的是一个数组,就要delete一个数组
	//delete p;//可以成功释放,对于基本数据类型,可以直接p,以为其为基本数据类型,且连续,但不建议使用
	

}
#endif

#if 1
void main()
{
	A *p1 = new A[5]; //new了5个A类型的对象
	//delete p;//error 出错
	//delete p[];//error
	delete []p1; //delete 必须是数组

	char *p2 = new char[20];
	delete p2;  //可行
}
#endif

不能delete p;类类型数组
在这里插入图片描述
正确使用方式:
在这里插入图片描述

问题:什么时候程序员需要自己写析构函数?

既然每个类都会提供一个默认的析构函数,而且析构函数只能有一个,那么为什么程序员需要自己写?什么时候程序员需要自己写析构函数?
答:
当有指针作为数据成员时,就需要程序员写析构函数,将构造函数中new的空间释放

为什么这里的m_name(name),可以完成赋值,并成功运行??

class Student
{
public:
	Student(int num = 1,char *name = "lol",char sex='f',int age =0);
	void Print();
private:
	int m_num;   //编号
	char *m_name; //姓名
	char m_sex;   //性别
	int m_age;   //年龄
};
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
//注意这里的m_name(name),可以完成赋值,并成功运行
}
void Student::Print()
{
	cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
	Student s1;
	Student s(1001,"pangpang",'m',20);
	s.Print();
	cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}

在这里插入图片描述

s1对象的内存布局:

在这里插入图片描述
其中指针name指向的是“pangpang”–字符串常量,存放在常量区存储区,即name指向"pangpang"的首地址,
m_name(name) 就让m_name指向"pangpang"的首地址,并没有将字符串存储其中,

运行完 Student s(1001,“pangpang”,‘m’,20); 后:
在这里插入图片描述

但出现一个问题,不能修改m_name的值

//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
	/*
	m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
	*/
	//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
	//*m_name = 'k';//error,
}

正确代码,可实现修改,使指针正确的合法的存储(不是指向)正确合法的内容–需要给指针开辟合法的内存单元

class Student
{
public:
	Student(int num = 1,char *name = "lol",char sex='f',int age =0);
	void Print();
private:
	int m_num;   //编号
	char *m_name; //姓名
	char m_sex;   //性别
	int m_age;   //年龄
};
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
	//需要给m_name指针开辟内存大小,存储name所指向的字符串
	//正确的合法的去存储合法的内容,需要给当前的指针开辟合法的内存单元


	 m_name = new char[strlen(name) + 1];//+1为'\0'
	 strcpy(m_name,name);
	 m_name[0] ='a';
}

#if 0
//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
	/*
	m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
	*/
	//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
	//*m_name = 'k';//error,
}
#endif
void Student::Print()
{
	cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
	Student s1;
	Student s(1001,"pangpang",'m',20);
	s.Print();
	//cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}

在这里插入图片描述
核心代码:

	m_name = new char[strlen(name) + 1];//+1为'\0'
	 strcpy(m_name,name);

这两句代码就开辟了一块 “strlen(name) + 1” 大小的内存单元,并且将name所指向的字符串的内容拷贝到这段内存单元里,使m_name指向这一块内存单元,
这样m_name不仅指向所需指向的内容,还可以修改。

在这里插入图片描述

需要在对象作用域消失之前调用析构函数,才能成功释放

析构函数(一个类有且只有一个析构函数,并且没参数):

~Student()
	{
		if(m_name != NULL)
		{
			delete []m_name;
			m_name = NULL;
		}
		//cout<<"~Student"<<endl;
	}

使用示例:

#if 1
class Student
{
public:
	Student(int num = 1,char *name = "lol",char sex='f',int age =0);
	void Print();
	~Student()
	{
		if(m_name != NULL)
		{
			delete []m_name;
			m_name = NULL;
		}
		cout<<"~Student"<<endl;
	}
private:
	int m_num;   //编号
	char *m_name; //姓名
	char m_sex;   //性别
	int m_age;   //年龄
};
#if 1
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
	//需要给m_name指针开辟内存大小,存储name所指向的字符串
	//正确的合法的去存储合法的内容,需要给当前的指针开辟合法的内存单元


	 m_name = new char[strlen(name) + 1];//+1为'\0'
	 strcpy(m_name,name);
	 m_name[0] ='a';
}
#endif

#if 0
//所以一般不这么写,不能对m_name的内容进行修改,如果不进行修改可以这么写
Student::Student(int num,char *name,char sex,int age):m_num(num),m_name(name),m_sex(sex),m_age(age)//在声明的时候写默认值,定义的时候就不用写啦
{
	/*
	m_name(name)将m_name指针指向的那个字符串常量的首地址,并没有开辟空间,不能修改
	*/
	//strcpy(m_name,"aaaaaaa");//error,m_name没有所指向的空间,不能实现,m_name(name)这句话只是让m_name这个指针指向了所传过来的字符串的首地址
	//*m_name = 'k';//error,
}
#endif
void Student::Print()
{
	cout<<m_num<<" "<<m_name<<" "<<m_sex<<" "<<m_age<<endl;
}
void main()
{
	Student s1;

	Student s(1001,"pangpang",'m',20);
	s.Print();
	cout<<sizeof(s)<<endl; //内存对齐,s的大小为16个字节
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值