C++类与对象 (new与delete / 初始化列表 / 析构函数 / 拷贝构造函数 )

类的构造与析构

上篇博文我简单地介绍了一下什么是类的构造函数,什么时候构造函数被调用,并通过代码例子介绍了类的实例化。

那既然有构造,那肯定是有销毁的啦,那就是析构函数干的事情。析构函数在对象被销毁的时候调用,被销毁有两种情况:

  • 离开作用域
  • 调用delete

这两种情况我都会具体说一下,先说说最简单的“离开作用域”。学过C的同学都知道,局部变量在离开作用域的时候会被内存回收,除非用malloc函数为其分配内存。这里涉及到栈区和堆区的知识,简单说明一下栈区是由编译器管理的,主要是为函数的调用分配空间,函数结束以后被回收;堆区是由程序员管理的,主要通过new / delete关键字分配和回收,他在程序运行过程中一直存在,程序结束后由OS进行回收,想要知道细节的同学可以百度一下。

这里用代码简单地感受一下析构函数,还是用上次的角色类:

#include <iostream>

using namespace std;

class Character {
   
private:
	int hp;
	int mp;
	
public:
	Character() {
   
		cout << "新建英雄成功!" << endl;
	}
	~Character() {
   
	//在构造函数前加个~就是析构函数,析构函数不能带任何参数
		cout << "角色被销毁!" << endl;
	}
};

void fun(){
   
	Character mario;
}

int main(){
   
	fun();
	cout << "程序结束" << endl;
	return 0;
}
[dyamo@~/code 17:08]$ g++ -o character.exe character.cpp 
[dyamo@~/code 17:08]$ ./character.exe 
新建英雄成功!
角色被销毁!
程序结束

可见mario的析构函数在“程序结束”之前被调用了,也就是说函数内实例化的对象在离开定义域之后会自动被销毁。

new / delete

那有没有什么方法能让对象长时间存在内?那就是使用C++的关键字new,为对象分配堆上的内存。在对象的实例化上,关键字new做了以下两件事:

  • 为对象分配堆上的内存空间,并返回对象地址;
  • 调用该类的构造函数;

学过C的同学可能比较属性malloc这个函数,这个函数也是给变量或者结构体分配堆上内存的。当然malloc也可以给类的对象分配堆上内存,但是有一个问题,那就是malloc并不会调用类的构造函数,他只是单纯的划一片内存区域给你,划多少由你说了算;而且他返回的是void *指针,想要使用的话还得进行强制类型转化才能使用。所以要为对象分配堆上内存,还是用得用new。还是用上面的角色类例子:

void fun(){
   
	//实例化对象并返回对象地址
	Character *mario_ptr= new Character();
}

int main(){
   
	fun();
	cout << "程序结束" << endl;
	return 0;
}
[dyamo@~/code 17:08]$ g++ -o character.exe character.cpp 
[dyamo@~/code 17:08]$ ./character.exe 
新建英雄成功!
程序结束

可见mario的析构函数并没有被调用程序就已经结束了,所以我们也可以知道OS回收程序内存就是单纯的回收,并不会做什么收尾工作。

那么如果我们想销毁用new实例化的对象,就得用C++的另一个关键字delete。上面也说了对象被销毁只有两种情况,第二种就是调用delete来销毁。我们来销毁mario:

void fun(){
   
	Character *mario_ptr = new Character();
	delete mario_ptr;
}

要注意两点问题:

  • delete后跟着的一定是该对象的指针
  • new和delete一定是成双成对使用的。用了new就一定是用delete来回收内存,不是用new分配的内存就千万不要用delete回收。
Character mario;
delete mario;	//error!不能这样使用
delete &mario;	//error!mario不是用new实例化的

再额外说一下批量分配内存。如果我们要一次实例化多个对象,就必须回收同样的内存,不然会出大问题。看以下代码:

void fun(){
   
	Character *heros = new Character[5]();
	delete mario;
}
[dyamo@~/code 17:08]$ g++ -o character.exe character.cpp 
[dyamo@~/code 17:08]$ ./character.exe 
新建英雄成功!
新建英雄成功!
新建英雄成功!
新建英雄成功!
新建英雄成功!
角色被销毁!
Segmentation fault

我们会发现,只有一个hero被销毁了,然后就报段错误了。为什么会这样子呢?有同学就想:解决这个问题还不简单,写个循环一个个delete掉呗。

void fun(){
   
	Character *heros = new Character[5]();
	for(int i = 0; i < 5; ++i){
   
		delete heros + i;
	}
}

运行之后还是一样的结果。原因是这样的:在第一个delete之后,其实后面的都已经被内存回收了。注意这里我说的是“内存回收”,并不是“销毁”,也就是说对象构造函数还没调用就已经被内存回收掉了。这时候你再用delete去销毁已经被内存回收的对象,就会报段错误。而且这会造成另外一个严重的问题——内存泄漏(Memory Leak),这个在后面析构函数和拷贝构造函数会具体讲。

那么怎么正确回收内存呢?就和上面说的用了new就一定要用delete回收一样,用了new[]就一定要用delete[]来回收。

void fun(){
   
	Character *heros = new Character[5]();
	delete[] heros;
}
[dyamo@~/code 17:08]$ g++ -o character.exe character.cpp 
[dyamo@~/code 17:08]$ ./character.exe 
新建英雄成功!
新建英雄成功!
新建英雄成功!
新建英雄成功!
新建英雄成功!
角色被销毁!
角色被销毁!
角色被销毁!
角色被销毁!
角色被销毁!

构造函数的初始化列表

这里讲一下构造函数的初始化列表。之前说过声明了一定要定义才可以使用,初始化列表就是用于做定义的,也可以理解为给对象的变量进行初始化。

class Character {
   
private:
	int hp
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值