构造函数、初始化列表和析构函数
大约 6 分钟
构造函数、初始化列表和析构函数

构造函数
构造函数的特点:
- 函数的名字与类名相同
- 没有返回值
- 没有返回类型,即使是void也不能有
- 构造函数在对象创建时自动调用,用以完成对象及其他操作(如为指针成员等);
- 如果程序员没有显式定义它,系统会提供一个默认构造函数。
示例
下面我们用一个点Point来举例:
class Point
{
public:
//即使不写,编译器也会自动提供一个
Point()
{
cout << "Point()" << endl;
_ix = 0;
_iy = 0;
}
void print()
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
}
private:
int _ix;
int _iy;
};
int main(void){
Point pt;
pt.print();
return 0;
}
编译器自动生成的缺省(默认)构造函数是,实际上,构造函数可以接收参数
Point(int ix, int iy)
{
cout << "Point(int,int)" << endl;
_ix = ix;
_iy = iy;
}
- 这说明了构造函数是可以****的。
现在假设Point类中只显式,如果还希望通过默认构造函数创建对象,则需要。
初始化列表
class Point
{
public:
//...
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int = 0,int = 0)" << endl;
}
//...
};
如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。如在“对象的创建”部分的两个构造函数中的_ix
和_iy
都是先执行默认初始化后,再在函数体中执行赋值操作。
注意:
- 每个成员在初始化列表之中,
- 其初始化的顺序不是由成员变量在初始化列表中的顺序决定的,而是由成员变量在类中被决定的。(举例说明)
class Base {
public:
int m_a;
public:
Base(int a) : m_a(a)
{
std::cout << "Base obj constructed !" << std::endl;
}
};
class Derived: public Base {
public:
int m_b;
int m_c;
int m_d;
public:
Derived (int a, int c) : m_c(c), Base(a), m_b(m_a + m_c)
{
std::cout << "Derived obj constructed !" << std::endl;
}
};
int main()
{
Derived d(1, 10);
std::cout << d.m_a << std::endl; // 1
std::cout << d.m_b << std::endl; // 未初始化
std::cout << d.m_c << std::endl; // 10
std::cout << d.m_d << std::endl; // 1
std::cin.get();
}
$./a.out
Base obj constructed !
Derived obj constructed !
1
1
10
0
class Test
{
public:
Test(int data = 10):mb(data), ma(mb){}
void show()
{
cout << "ma:" << ma << " mb:" << mb << endl;
}
private:
int ma;
int mb;
};
int main()
{
Test t;
t.show();
return 0;
}
结果:ma先初始化,此时mb还没有值,即0xcccccccc;mb再初始化即10。
必须在初始化列表中进行初始化的情况有哪些
**成员变量是
const
**成员变量引用类型:
如果派生类需要调用基类的构造函数
- 如果派生类需要调用基类的有参构造函数,必须初始化列表初始化
- 如果派生类需要调用基类的无参构造函数
- 基类有一个无参构造函数,那么编译器会自动调用基类的无参构造函数来初始化基类部分
- 如果基类没有提供无参构造函数,编译器会产生编译错误
- (因为编译器在自动生成构造函数的过程中会尝试调用基类的构造函数,但是如果基类没有提供无参构造函数,而且编译器不会自动创建默认的基类构造函数调用,编译器就无法生成默认构造函数,这样就会导致编译错误。)
初始化其他自定义类型的成员对象
- 如果类中有其他类类型的成员对象,必须在构造函数初始化列表中对其进行初始化。
class OtherClass { public: OtherClass(int val) {} }; class MyClass { public: MyClass(int val) : _other(val) {} private: OtherClass _other; };
析构函数
析构函数(Destructor)在对象被撤销时被自动调用,相比构造函数,析构函数要简单的多。
析构函数的特点:
与类同名,之前冠以波浪号,以区别于构造函数。
析构函数没有返回类型,也没有参数, 因此,析构函数只能有,。
对象超出其作用域被销毁时,析构函数会被自动调用
- 析构函数在对象撤销时自动调用,用以执行一些**清理任务,如**等。
如果程序员没有显式的定义它,系统也会提供一个默认的析构函数。
示例
class Point
{
public:
//...
~Point()
{
}
//...
};
class Computer
{
public:
Computer(const char *brand, double price)
: _brand(new char[strlen(brand) + 1]())
, _price(price)
{
cout << "Computer(const char *, double)" << endl;
}
~Computer()
{
cout << "~Computer()" << endl;
delete [] _brand;
_brand = nullptr;
}
double getPrice() {
return _price;
}
private:
char *_brand;
double _price;
};
如果某个数据成员是指针,而该指针在构造函数中初始化时已经申请了堆空间的资源,则当对象被销毁时,必须回收其资源。此时,编译器提供的默认析构函数是没有做回收操作的,因此就不再满足我们的需求,我们必须显式定义一个析构函数,在函数体内回收资源。 析构函数除了在对象被销毁时自动调用外,还可以。
int main() {
Computer c;
c.~Computer(); //不建议手动析构
c.getPrice(); //error
}
析构函数在哪些时候会被调用呢?
- 对于全局定义的对象,每当程序开始运行,在主函数main接受程序控制权之前,就调用构造函数创建全局对象,整个程序结束时,自动调用全局对象的析构函数。
- 对于局部定义的对象,每当程序流程到达该对象的定义处就调用构造函数,在程序离开局部对象的作用域时调用对象的析构函数。
- 对于关键字static定义的静态局部变量,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。
- 对于用new运算符创建的对象,每当创建该对象时调用构造函数,当用delete删除该对象时,调用析构函数。
注意
- 先构造的后析构,后构造的先析构。(栈结构)