第10课 - 析构函数
一.对象组合
1.1 初始化问题
Source Example 1.1:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class M {
private:
int i;
public:
M(int a)
{
i = a;
}
void print()
{
printf ("i = %d\n", i);
}
};
class Test{
private:
/* 编译错误1: const int c没有进行初始化,但是类里面不允许直接初始化 */
const int c;
/* 编译错误2:由于在M类里面提供了构造函数,因此不会提供默认的无参构造函数,初始化时需要根据自己写的构造函数提供一个整数 */
M m1;
M m2;
public:
Test()
{
printf ("Test()!\n");
}
};
void run()
{
Test t1;
Test t2;
}
int main(int argc, char** argv) {
run ();
return 0;
}
1.2 解决方案->初始化列表
C++提供了初始化列表对成员变量进行初始化
语法规则:
Constructor::Constructor():m1(v1),m2(v1,v2),m3(v3)
修改上面代码类Test的定义为
class Test{
private:
/* 编译错误1: const int c没有进行初始化,但是类里面不允许直接初始化 */
const int c;
/* 编译错误2:由于在M类里面提供了构造函数,因此不会提供默认的无参构造函数,初始化时需要根据自己写的构造函数提供一个整数 */
M m1;
M m2;
public:
Test() : c(1),m1(1),m2(1)
{
printf ("Test()!\n");
}
};
即可编译通过,执行时先执行初始化列表,再执行Test构造函数的函数体。
注意:
a.成员变量的初始化顺序与成员变量定义的顺序相关(即先初始化m1,再初始化m2),与在初始化列表中的顺序无关无论是 Test() : c(1),m1(1),m2(1)还是 Test() : c(1),m2(1),m1(1),都先初始化m1,因为m1先定义。
b.初始化列表先于构造函数的函数体执行
1.3 关于const成员变量的一些说明
1.3.1 类中的const成员是肯定会被分配空间的
1.3.2 类中的const成员变量只是一个只读变量
(编译器无法直接得到const成员变量的初始值,因此无法进入符号表成为真正的常量)
1.4 初始化和赋值的说明(初始化列表里面属于初始化)
1.4.1 初始化时用已经存在的对象或者值对正在创建的对象进行初始化设置
1.4.2 初始化时用已经存在的对象或者值对已经存在的对象进行值设置
二.对象的销毁
2.1 一般的解决方案
为每个类提供一个destroy函数,对象不再使用时立即调用destroy函数进行清理
(存在问题,每次都要手动调用,容易忘记出错)
2.2 析构函数(在对象将要被销毁之前被调用的函数)
2.2.1 C++中的类可以定义一个特殊的成员函数清理对象
2.2.2 这个特殊的成员函数叫析构函数
定义: ~ClassName();2.2.3 析构函数没有参数也没有任何返回类型的声明
2.2.4 析构函数在对象被销毁时自动调用
Source Example 2.2:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class Test{
private:
const int c;
public:
Test():c(1)
{
printf ("Test()!, c = %d\n", c);
}
~Test()
{
printf ("~Test()!, c = %d\n",c);
}
};
/* 由于t1,t2在函数里面,函数执行完成就要被销毁,因此会调用析构函数 */
void run()
{
Test t1;
Test t2;
}
int main(int argc, char** argv) {
run ();
return 0;
}
三.构造与析构
3.1 构造函数和析构函数的调用秩序
3.1.1 当类中有其他成员变量是其他类的对象时
a.首先调用成员变量的构造函数(调用顺序与声明顺序相同)
b.之后调用自身类的构造函数
3.2.2 析构类的调用秩序与对应构造函数调用秩序相反
Source Example3.1:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class Test{
private:
int c;
public:
Test(int i):c(i)
{
printf ("Test(int i)!, c = %d\n", c);
}
Test (const Test& t)
{
printf ("Test (const Test& t)!\n");
c = t.c;
}
~Test()
{
printf ("~Test()!, c = %d\n",c);
}
};
void func(Test t)
{
/* 调用构造函数初始化
输出Test(int i)!, c = 1*/
Test t2(1);
}
void run()
{
/* 调用构造函数初始化
输出Test(int i)!, c = 0*/
Test t1(0);
/* 调用该函数时传递t1,相当于Test t = t1;因此会调用拷贝构造函数
输出Test (const Test& t)! */
func(t1);
}
输出结果如下:
输出结果同时也表明了析构顺序和构造顺序相反。
四.小结
4.1 析构函数时C++中对象销毁时做清理工作的特殊函数
4.2 析构函数在对象销毁是被自动调用
4.3 析构函数时对象所使用的资源及时释放的保障
4.4 析构函数的调用顺序和构造顺序相反
问题: 构造函数是否可以手动来调用
Source Example:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class Test{
private:
int i;
int j;
char* k;
public:
Test()
{
printf ("Test!\n");
i = 0;
j = 0;
k = 0;
}
Test (const char *s)
{
printf ("Test (char *s)\n");
/* 该构造函数不能够在此被调用,如果调用,会创建一个新的临时对象,调用完成之后就会销毁,调用~Test函数 */
Test();
k = s;
}
~Test()
{
printf ("~Test()\n");
}
void print()
{
printf ("i = %d, j = %d, k = %s\n", i, j , k);
}
};
void run()
{
/* 调用char *的构造函数 */
Test t = Test("shuai guo !");
t.print();
}
int main(int argc, char** argv) {
run ();
return 0;
}
输出结果如下:
如果自己掉用就会产生一个临时对象,然后对象会被析构。