C++中的构造函数&析构函数
构造函数
构造函数在每次实例化对象时运行,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用)。
构造函数一般用来设置变量的初始值或者做任何我们想要的初始化操作。
根据参数的不同,可以写出很多不同的构造函数(函数重载,和写同名方法的时候一样),其中不带参数的构造函数一般是默认的构造函数。
比如下面的例子:
class Entity {
public:
int x, y;
Entity() //不带参数
{
}
Entity(int x, int y) : x(x), y(y) {}; //带参数,用来初始化x和y
void print()
{
std::cout << x << ',' << y << std::endl;
}
};
如果不实例化对象,构造函数将不会运行。如果只使用一个类的静态方法,构造函数是不会运行的。
在某些情况下,也可以删除构造函数,比如下面的例子:
class Logs
{
public:
static void Write()
{
// ...Some codes
}
};
这个类只有一个静态方法Write()
,如果我不希望别人创建它的实例,这里有两种不同的解决方案:
方案1:通过private隐藏默认的构造函数:
class Logs
{
private:
Logs() {};
public:
static void Write()
{
// ...Some codes
}
};
这样写以后,如果有人想要实例化Logs
,比如Logs logs;
会得到无法访问 private 成员(在“Logs”类中声明)
的错误。
方案2:直接告诉编译器我不想要这个默认的构造函数:
class Logs
{
public:
Logs() = delete;
static void Write()
{
// ...Some codes
}
};
同样,如果Logs logs;
会得到 “Logs::Logs(void)”: 已隐式删除函数
的错误
析构函数
析构函数是构造函数的孪生兄弟,他们很类似,构造函数在类实例化对象的时候运行,而析构函数则在销毁对象的时候执行。在任何时候,如果有一个对象要被销毁,析构函数将被调用。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
析构函数一般被用来卸载变量等东西,清理使用过的内存。同时适用于栈和堆分配的对象,所以当使用new创建一个对象,当调用delete方法时,析构函数会被调用;如果某对象只是一个栈对象,当作用域结束时,栈对象将被删除,此时也会调用析构函数。
析构函数的写法是一个~
加上类名,如上述的Entity
类中,默认的析构函数是~Entity()
。构造函数和析构函数在定义时唯一的区别就是析构函数前面的波浪号。
来看下面的例子:
class Entity {
public:
int x, y;
Entity() //不带参数
{
x = 0;
y = 0;
std::cout << "Created Entity!" << std::endl;
}
Entity(int x, int y) : x(x), y(y) {}; //带参数
~Entity()
{
std::cout << "Destroyed Entity!" << std::endl;
}
void print()
{
std::cout << x << ',' << y << std::endl;
}
};
void Function()
{
Entity entity;
entity.print();
}
int main()
{
Function();
std::cin.get();
}
在Entity()
和~Entity()
都加上了Log,这样在程序运行的时候就可以监视函数的执行情况。这里需要注意的是,我把创建entity
对象的代码放在了函数Function()
中,而没有直接卸载main()
方法里,这是因为如果写在main()
方法里,只有当main()
函数退出时,析构函数才会被调用,我们将看不到效果,所以将相关代码写在Function()
中,这样entity
对象就被创建在栈上面,当Function()
函数退出时,作用域结束,对象会自动销毁,Entity
的析构函数将会被调用。
可以使用调试工具来验证上面的说法:
在Function()
函数中的Entity entity;
这一行前面打一个断点。
当执行完Entity entity;
后,程序打印Created Entity!;
当执行完entity.print();
后,程序打印0,0
;
当执行完Function()
函数最后一个}
时,程序打印Destroyed Entity!
,说明在Function()
函数退出时,调用了析构函数。
为什么要写析构函数
如果我们在构造函数中调用了特定的初始化代码,初始化了很多东西,我们就要在析构函数中写在或者销毁这些东西(如果不这么做可能会造成内存泄漏),比如我们在堆上面分配对象,如果已经在堆上面手动分配了任何类型内存,那么我们需要手动清理这些内存。
析构函数也可以被手动调用(很少见人这么做)。