1.5 C++基础知识_构造函数

之前都是先创建一个对象,然后调用类的成员函数对对象赋值。

本节尝试使用析构函数,在创建变量的时候直接对变量赋值。

构造函数

所谓构造函数,就是跟类名相同和函数,需要注意的是,构造函数不可以指定返回值

如下图所示,在 Person 类中创建了三个构造函数,函数名与类相同,都是Person,区别在于函数的传参不同。

然后创建类时,就可以调用构造函数。

如下图所示,根据传入的参数,编译器会调用上图中第三个构造函数,这个函数中会给类的name成员和age成员赋值。

增加一个对象per1,只传入name,那么per1应该调用第二个构造函数。

编译测试,可以看到,结果符合上面的描述。

需要注意一点,如果想要使用第一个构造函数,也就是无传参的那个构造函数,那么创建对象时不能加括号否则编译器会认为这是一个函数声明

如下图所示,创建了一个对象per2,因为没有传入参数,所以会调用第一个构造函数。

编译测试如下图,箭头所指的打印信息说明第一个构造函数被调用了,而 Person per3();由于编译器认为这是一个函数声明,所以不会调用任何构造函数。

设置默认值

调用per2的printfInfo函数,在没有设置name成员和age成员的值的情况下,看看打印出来的结果是什么。

 

对于没有设置的成员,打印出来的值可能是随机的,也可能是0,这显然是不好的。

问:如果没有传入设置值,是否可以使用一个默认值

答:可以。如下图所示,如果没有传入age的设置值,则设置为默认值 20。

修改构造函数,将Person(const char *name)删除,将Person(const char *name, int age)改为Person(const char *name, int age = 20);

编译后测试结果如下图所示,在没有设置age时,会将age设置为默认值20。

申请动态内存创建对象

还可以使用 new 来动态的申请空间创建变量,其中 per4 per5 是一样的,他们都会调用无传参的那个构造函数。

编译后测试结果如下。

使用 new 动态申请的空间,在程序执行完毕退出时,会由操作系统统一释放掉,也可以使用 delete 手动释放。

注意per6申请的是一个数组,delete时要加 []。

在释放完per7之后,调用per7的printfInfo,可以看到依旧有打印,这是因为释放的空间如果没有被其他程序使用,则可能会保持原来的数据,所以还能调用成功。

但是,正常写程序时,我们认为释放掉之后的空间就是废弃的了,不会再去使用它,因为它的数据已经不可靠了。

在之前的代码中,对name的赋值是直接将字符串的地址赋值过去,现在也尝试使用动态申请的空间。

编译测试,打印和之前一样,但是 name 这次使用的是动态申请的空间。

释放动态申请的内存

做一个修改,将原来的 main 函数改为一个普通的 test_fun 函数,然后创建一个新的 main 函数,在新的 main 函数中调用 test_fun 函数。

为了使内存占用多一些,循环调用 1000000 次。

然后编译程序,在执行前使用 free -m 查看内存使用情况。

可以看到,在执行程序之前,空闲内存有1480M。

 执行程序后,空闲内存变为1389M。

将进程杀死后,空闲内存又变为1480M了。

总结一下,就是main函数退出也就是进程执行完毕后,系统会将进程申请的资源释放。但是,如果是在调用的函数(test_fun函数)中申请内存,即使函数执行完毕退出,申请的内存也不会自动释放。

析构函数

析构函数(destructor)是成员函数的一种,它的名字与类名相同,但前面要加~,没有参数和返回值。如 ~Person();

一个类有且仅有一个析构函数。如果定义类时没写析构函数,则编译器生成默认析构函数(函数体为空)。如果定义了析构函数,则编译器不生成默认析构函数。

析构函数在对象消亡时自动被调用,所以可以是用析构函数来释放对象申请的内存空间

如下图所示,添加析构函数 ~Person,在析构函数中释放申请的内存空间。

编写完成后,执行编译,在启动进程前查看一下空闲内存如下,为1478M。

启动进程,运行过程中的空闲内存如下,与之前相同,为1478M。

也就是说,之前对象申请的内存,通过析构函数,在对象消亡时自动的释放掉了。

可以修改一下程序,看看析构函数是不是在test_fun函数结束前被调用的。

在析构函数中增加打印一行log,并且取消 main 函数中的循环。

方便起见,修改一下test_fun函数。

 

 测试结果如下,先delete per7,然后在函数退出的瞬间,再delete per。

 如果注释掉 delete per7,则不会调用per7的析构函数,但是在进程退出瞬间,系统也会释放per7申请的空间,只是这时候不会调用per7的析构函数。

 没有调用per7的析构函数。

最后,使用new申请的内存空间,建议手动加上delete来释放。

传参为类本身的构造函数

在main函数中,创建一个对象per2,这个对象的传参是另一个对象per。

但是,我们并没有创建一个传参为Person的构造函数,这时候,就会把per的数值拷贝到per2里面去。

测试结果如下,可以看到,per2的数值与per相同,但是程序执行有报错,错误原因为同一个地址空间被释放了两次。

问:为什么会报错呢?

答:创建per变量时,会调用构造函数,new一个空间给per使用,用来存放name的值,这时候,this->name = addr1,也就是per.name = addr1。

当执行Person per2(per)时,会进行值拷贝,per2.name其实是等于per.name,即per2.name = addr1。

当程序执行完毕退出时,系统会自动释放申请的内存空间,这时候,由于per.name和per2.name相同,所以addr1会被释放两次,也就是同一块内存空间被释放了两次,这样会引起错误。

要解决这个问题,可以在构造函数中,添加传参为Person类本身的构造函数

编译测试如下,可以看到,调用了传参为Person类的构造函数,这时候就不会出现同一块地址被释放两次的问题了。

对象的构造过程/顺序

修改代码,增加全局变量,局部变量,静态变量,看看它们的创建顺序是怎么样的。

对应调用的构造函数和析构函数如下:

执行结果如下,可以看到首先是全局变量 g_per 的构建函数被调用,然后按顺序程序执行到哪里就创建哪个变量,然后调用对应的构造函数

func函数中创建的per_func,每次退出func函数时会调用对应的析构函数,再次进入时又会重新调用一次构造函数,而静态变量s_per_func在func函数退出时不会调用析构函数,再次进入时也不会重新调用构造函数。

消除变量则是先局部变量,然后静态变量,最后全局变量。

总结一下,构造顺序如下:

按运行中定义对象的顺序调用构造函数,静态对象只要调用一次构造函数;全局对象在main函数执行前被构造。

在对象内创建对象

首先定义一个Student类,然后在Student类中,包含两个Person类成员,father和mother,然后还有一个int类型的成员student_id。

然后,在main函数中创建一个Student类的对象student,由于没有设置构造函数,所以猜测会调用默认的构造函数,

 Person类的无传参的构造函数如下。

 

 结果如下,应该是father和mother的无传参构造函数被调用。

添加一个Student的构造函数,猜测程序执行时会先调用Person的构造函数,之后再调用Student的构造函数。

测试结果如下,符合猜测。

修改代码,在创建student对象的时候,直接设置Student类里面Person类成员的值:

	Student(char *father, char *mother, int father_age = 40, int mother_age = 39) : father(father, father_age), mother(mother, mother_age){
		cout << "Student(char *father, char *mother, int father_age = 40, int mother_age = 39)" << endl;
	}

在函数的后面,加上 “: father(father, father_age), mother(mother, mother_age)”,就表示调用对应的构造函数。

增加一个Student的析构函数,测试一下调用的顺序。

测试发现,Student类的析构函数先被调用,然后Person类的析构函数才被调用,与构造函数的调用顺序刚好相反。

总结一下:

要调用对象成员的其他构造函数,可以这样写:Student(int sId) : id(sID) {},构造函数的“{}”前加“:”,加上成员的初始化代码。

对象成员:按定义时的顺序构造,与初始化代码顺序无关;

析构函数被调用的顺序:与构造函数的顺序相反。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值