C++ 构造函数 & 析构函数
构造函数
- C++规定,每个类必须有构造函数,没有构造函数就不能创建对象。
- 若没有提供任何构造函数,那么c++提供自动提供一个默认的构造函数,该默认构造函数是一个没有参数的构造函数,它仅仅负责创建对象而不做任何赋值操作。
- 只要类中提供了任意一个构造函数,那么c++就不在自动提供默认构造函数。
- 构造函数在类中的定义和类名相同,并且没有任何返回类型。
- C++规定如果一个类对象是另外一个类的数据成员,那么在创建对象的时候系统将自动调用哪个类的构造函数。
- 一个类的成员如果是另外一个类的对象的话,不能在类中使用带参数的构造函数进行初始化
- 构造函数定义为私有,可以防止该类被直接实例化。一般用于Singleton实现
拷贝构造函数
和 赋值运算符
- 在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个
拷贝构造函数
和赋值运算符
,它们只会按位拷贝对象所在的内存(浅拷贝)
。 - 还有一点需要注意的是,
拷贝构造函数必须以引用的方式传递参数 (const reference)
。 - 这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。
- 拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同:
- 拷贝构造函数使用传入对象的值生成一个新的对象的实例,
- 而赋值运算符是将对象的值复制给一个已经存在的实例。
- 这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。
- 调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
- 调用拷贝构造函数主要有以下场景:
- 对象作为函数的参数,以值传递的方式传给函数。
- 对象作为函数的返回值,以值的方式从函数返回
- 使用一个对象给另一个对象初始化
- 看下面一个例子,程序会crash,正确的做法就是自己写
复制构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class String
{
private:
char* m_Buffer;
unsigned int m_Size;
public:
String(const char* s)
{
m_Size = strlen(s);
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, s, m_Size);
m_Buffer[m_Size] = 0;
}
~String()
{
delete[] m_Buffer;
}
};
int main()
{
String str = "QinHan"; // 隐式转换
// str2 & str3 他们在析构函的时候都会出现重复 delete 的问题
// `m_Buffer` 指向 heap 上的同一个地址,会被释放两次,crash
// 另外,下面两种写法其实都是调用了拷贝构造函数,因为都有新的对象产生
String str2 = str;
String str3(str);
return 0;
}
explicit关键字
- 如果类A有一个构造函数
A(int n)
,则以下代码是正确的1
A a = 3; // “单参数构造函数”被自动型别转换(这是一个隐式转换)
- 可以通过explicit关键字,阻止“以赋值语法进行带有转型操作的初始化”。
- 还是上述的例子,构造函数改成
explicit A(int n)
,则上述写法无法编译通过,需要改成:A a(3);
一种特殊的构造函数的写法
- 如果按照一般的写法,成员变量会被赋值两次,第一次是初始值,然后再进行一次赋值
1
2
3
4
5class Entity
{
Entity()
:name("Unknown"), score(0) {}
}
析构函数
析构函数 的定义:析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载,只有在类对象的生命期结束的时候,由系统自动调用。
析构函数与构造函数的不同:析构函数与构造函数最主要大不同就是在于调用期不同,构造函数可以有参数可以重载!
成员的构造是按照在类中定义的顺序进行的,而不是按照构造函数说明后的冒号顺序进行构造的。
Q&A
delete 某个类的指针会调用该类的析构函数么?
会的,如果不调用的话怎么析构一个类.
不过指针所指向的对象必须是在堆中用new关键词开创的
如果指针指向的是一个栈中的对象,会引起调用两种析构函数而导致程序错误
1
2
3
4class ob
ob a;
ob *p=&a;
delete p; // 这样会导致调用两次析构函数.是会引起程序错误的只有
1
2
3class ob
ob * p= new ob;
delete p; //这样是正确的