[CPP] 构造与析构
本文对 C++ 中对构造与析构函数做一次总结。
构造函数
- 继承:先父后子。如果继承链为
A->B->C
,那么一次执行 ABC 的构造函数。
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "A "; }
};
class B : public A
{
public:
B() { cout << "B "; }
};
class C : public B
{
public:
C() { cout << "C "; }
};
int main()
{
C c;
}
// 输出:A B C
- 对象组合:假如
A
的成员包含A1, A2
两个对象,那么先执行成员变量的。
#include <iostream>
using namespace std;
class A1
{
public:
A1() { cout << "A1 "; }
};
class A2
{
public:
A2() { cout << "A2 "; }
};
class A
{
public:
A1 a1;
A2 a2;
A() { cout << "A "; }
};
int main()
{
A a;
}
// 输出:A1 A2 A
总结:构造顺序总是按体积从小到大 。例如,在继承中,父类的「体积」一般比子类小(因为子类包含更多的信息);在对象组合中,成员变量中的对象的体积自然比整个对象要「小」。
析构函数
普通情况:先构造后析构,即 与构造顺序相反。
⚠️特殊情况:全局变量,局部 static
变量,普通局部变量同时出现。直接看下面「特殊情况」一节即可。
按照对象关系分类
- 继承关系
class A
{
public:
A() { cout << "A "; }
~A() { cout << "~A "; }
};
class B : public A
{
public:
B() { cout << "B "; }
~B() { cout << "~B "; }
};
class C : public B
{
public:
C() { cout << "C "; }
~C() { cout << "~C "; }
};
int main()
{
C c;
}
// 输出:A B C ~C ~B ~A
- 对象组合
class A1
{
public:
A1() { cout << "A1 "; }
~A1() { cout << "~A1 "; }
};
class A2
{
public:
A2() { cout << "A2 "; }
~A2() { cout << "~A2 "; }
};
class A
{
public:
A1 a1;
A2 a2;
A() { cout << "A "; }
~A() { cout << "~A "; }
};
int main()
{
A a;
}
// Output: A1 A2 A ~A ~A2 ~A1
按照对象所在存储区域分类
- 在栈上:
void func()
{
A a;
B b;
C c;
}
那么,构造顺序:a -> b -> c
,析构顺序为:c -> b -> a
.
如果使用 new
关键字,输出不变(堆其实是一个特殊的栈区域)。
- 在全局/静态存储区
C/C++ 中定义的全局变量,或者使用 static
修饰的局部变量,都会存储在一个叫「全局/静态存储区」的地方(即 DATA
数据段)。
例如:
A a;
B b;
C c;
int main(){}
// Output: A B C ~C ~B ~A
依旧遵循「先构造后析构」的规则。
特殊情况
但是如果既有普通变量,又有全局变量,还有 static
局部变量,那么就有点搞头了。
C c;
int main()
{
A *pa = new A();
B b;
static D d;
delete pa;
}
构造顺序:c -> a -> b -> d
,析构顺序:~A -> ~B -> ~D -> ~C
.
这种情况不符合上述规则。原因:栈上的变量是代码块结束的时候释放,全局/静态数据区的变量是 程序结束时 才释放。 main
函数结束时,程序还没结束。
逐行代码代码看,构造顺序是毫无疑问的。当运行至第 6 行代码时,内存状态如下:
| b | d |
| a | c |
|堆/栈|全局/静态|
执行至第 7 行:释放 A 。
执行至第 8 行:释放 B 。
返回到执行 main
函数的位置(有些汇编中使用符号 __start
或者 __init
来标记 ):依次释放 D 和 C 。
💬 一个有趣的问题:
main
函数之前发生了什么?在这里,在
main
函数执行前,全局变量中的对象应该且必须完成初始化。那么这个初始化动作由谁来完成的呢?我们猜是main
的调用者来完成的,学过 ICS/CSAPP 这门课的话,应该知道,在 IA32 当中,还有一个__start/__init
函数位于main
的更高一层,对程序猿不可见。至于程序第一行执行的代码是什么?这就是一个更值得探究的问题了(可惜我不会😅。
拷贝构造函数与赋值运算符重载
先看一段代码:
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass(int i = 0)
{
cout << i;
}
MyClass(const MyClass &x)
{
cout << 2;
}
MyClass &operator=(const MyClass &x)
{
cout << 3;
return *this;
}
~MyClass()
{
cout << 4;
}
};
int main()
{
MyClass obj1(1), obj2(2);
MyClass obj3 = obj1;
return 0;
}
// Output:122444
解析:
MyClass obj3 = obj1;
obj3 还不存在,所以调用拷贝构造函数输出2,
如果 obj3 存在,obj3=obj,则调用复制运算符重载函数,输出3
OS:TMD 这都什么破面试题,招的语言律师还是咋滴,人晕了。