构造函数、析构函数与赋值函数是每个类最基本的函数。他们太普通以致让人容易麻痹大意,其实这些貌似简单的函数在使用时要特别注意以免造成不必要资源浪费和产生意想不到的错误。
每个类只有一个析构函数和一个赋值函数,但是可以有多个构造函数(包含一个拷贝构造函数,其他的成为普通构造函数)。
一 . 构造函数
所谓构造函数,就是在对象构造的时候调用的函数。构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。
构造函数在定义类对象时自动调用,不需用户调用,也不能被用户调用。在对象使用前调用。如果类中没有定义构造函数,系统则会自动给出一个无参构造函数。
构造函数没有返回值,函数名必须与类名一致,一个类可以有多个构造函数,但是参数必须有差别(也就是所谓的重载)。
例如:
class Test
{
public:
Test(); // 无参数的构造函数
Test(int a); // 有一个 int 参数的构造函数
private:
Test(int a,int b); // 私有的两个参数的构造函数
};
Test::Test()
{ /* 此处省略一些初始化的工作 */}
Test::Test(int a)
{ /* …… */}
Test::Test(int a,int b)
{ /* …… */}
构造函数也会受访问性影响,在不同的作用范围,能调用的构造函数也会不同。
初始化成员
构造函数的一个重要任务就是给成员初始化,初始化成员有两种办法,一种是手动给成员赋值,另一种是使用初始化列表。这里介绍第二种,格式为:
类名::构造函数名(参数表): (成员初始化表){ 构造函数体 }
构造函数中的初始化列表只需要在参数列表的后面加一个冒号(:
),然后将要初始化的成员按照成员名(参数)
的格式排列在后面,个成员之间用逗号隔开。
例如:
class Test
{
public:
int A;
int B;
Test(int a);
};
Test::Test(int a)
:A(a),B(10) //给成员变量 A、B 初始化,不一定要和参数列表写在一行
{ /* …… */ }
其中成员的初始化顺序不是按照初始化列表中的顺序来的,而是按照成员声明的顺序来的,例如:
/* Test类的声明同上 */
Test::Test(int a)
:B(10),A(a) // 虽然 B 在前面,但还是 A 先初始化
{/* …… */}
Test::Test(int a)
:B(a),A(B)
//此处A的初始化依赖了B,然而是A先初始化,这就导致A得到了B中还没初始化的错误内容
{/* …… */}
二 . 析构函数:
析构函数是一种特殊的成员函数,它会在每次删除所创建的对象时执行。它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。格式如下:
类名::~析构函数名(){}
class Test
{
public:
~Test(); // 析构函数
};
Test::~Test()
{/* 一些收尾的工作 */}
三 . 构造函数与析构函数的调用:
构造函数不能直接调用,只能通过声明一个对象或者使用new 运算符动态创建对象时由系统自动调用。
例如:
class Test
{
public:
int A;
Test();
Test(int a);
};
/* 此处省略定义构造函数部分 */
int main()
{
Test t; // 调用无参构造函数
Test t2(10); // 调用带参构造函数
Test t3 = Test(10); // 同上
Test *t = new Test; // 动态创建对象,调用无参构造函数
Test *t2 = new Test(10); // 动态创建对象,调用带参构造函数
}
而析构函数则不同,它能够通过对象主动调用,并在以下两种情况下它会自动调用:
若一个对象被定义在一个函数体内,当这个函数结束时(声明的变量的生命周期结束)会自动调用。
若一个对象是使用 new 运算符动态创建,在使用 delete 释放时会自动调用。
例如:
/* Test类的声明接上 */
Test::~Test() // 修改一下析构函数,让它打印一条消息
{
cout << "Test的析构函数被调用" << endl;
}
int main()
{
cout << "p1" << endl;
{
Test t1(1); // t1 的生命周期就只在这个大括号内
} //因此在这个位置 t1 的析构函数就会被调用
cout<<"p2"<
//输出结果为:
p1
Test的析构函数被调用
p2
Test的析构函数被调用
p3
上述代码中 t1 对象的析构函数调用的位置有点微妙,它是在代码离开大括号 }
的那瞬间的位置被调用的,因为一个变量只在直接包含它的那层大括号的范围内存活。