|
@@ -235,3 +235,77 @@ int main()
|
|
|
不得不使用远大于实际比赛题目限制的内存),因此随便怎么初始化都行
|
|
|
(当然不能写时间复杂度与输入规模呈二次关系的初始化,
|
|
|
例如多组数据输入时每组都 `memset` 整个数组就可能超时)。
|
|
|
+
|
|
|
+## 关于构造函数的补充说明
|
|
|
+
|
|
|
+对于一些评测系统和题目,
|
|
|
+我们发现在代码中添加构造函数会导致运行时间数据变得非常难看。
|
|
|
+例如:
|
|
|
+
|
|
|
+```c++
|
|
|
+struct node
|
|
|
+{
|
|
|
+ int u, v;
|
|
|
+#if defined(HAVE_CONSTRUCTOR) && HAVE_CONSTRUCTOR
|
|
|
+ node(): u(0), v(0) {}
|
|
|
+#endif
|
|
|
+};
|
|
|
+
|
|
|
+node arr[500000000];
|
|
|
+
|
|
|
+int main() {}
|
|
|
+```
|
|
|
+
|
|
|
+我们不加 `-DHAVE_CONSTRUCTOR=1` 编译,得到的程序几乎不消耗时间。
|
|
|
+而加了 `-DHAVE_CONSTRUCTOR=1` 后,得到的程序会消耗内核态时间 $0.24$ 秒,
|
|
|
+用户态时间 $0.13$ 秒[^1]。这说明,在前一种情况下,初始化仍然是按照 BSS
|
|
|
+段的机制进行的,只要我们不使用数组,就不会真的进行初始化。而在后一种情况下,
|
|
|
+编译器直接生成了初始化整个数组的代码。
|
|
|
+
|
|
|
+[^1]: 在编写补充说明之前,测试环境的内存升级为了 DDR4 3200 MHz,
|
|
|
+故结果与之前的测量结果没有可比性。
|
|
|
+
|
|
|
+对于一些 OI 题目来说,这种行为会导致总的运行时间显著增加。例如,
|
|
|
+如果某题目规定对于 $100%$ 的数据有 $1 \leq n \leq 10^6$,
|
|
|
+而对于 $50%$ 的数据有 $1 \leq n \leq 1000$,那么加了构造函数后,
|
|
|
+即使对于 $n$ 较小的数据,程序在启动时也会初始化 $10^6$ 个元素。
|
|
|
+而不加构造函数的程序实际上只会初始化 $1000$ 个元素。
|
|
|
+洛谷等评测系统会将多组数据的运行时间叠加起来,作为总运行时间,
|
|
|
+这就会产生可观的差距。
|
|
|
+
|
|
|
+对此一种解决方法是使用 `= default()` 显式指定默认构造函数,例如:
|
|
|
+
|
|
|
+```c++
|
|
|
+struct node
|
|
|
+{
|
|
|
+ int u, v;
|
|
|
+ node() = default; // default constructor
|
|
|
+ node(int a, int b = 0) : u(a), v(b) {} // nontrivial constructor
|
|
|
+};
|
|
|
+
|
|
|
+node arr[500000000];
|
|
|
+
|
|
|
+int main()
|
|
|
+{
|
|
|
+ node n(1, 2);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+这里我们使用构造函数的目的是方便构造非 $0$ 的元素。
|
|
|
+然而如果我们只定义上面的 `nontrivial constructor`,
|
|
|
+则编译器会拒绝自动生成默认构造函数,导致无法声明数组。
|
|
|
+如果我们手写一个构造函数,就会像前面讨论的一样,引入额外的初始化。
|
|
|
+而使用 `node() = default;` 这样的默认构造函数就能解决这一问题。
|
|
|
+但是需要注意的是,默认构造函数的行为与构造一个传统的 C 结构体是完全一致的,
|
|
|
+它甚至不会初始化该类型局部变量的各字段 (除非该字段的类型本身有构造函数)。
|
|
|
+例如:
|
|
|
+
|
|
|
+```c++
|
|
|
+int main()
|
|
|
+{
|
|
|
+ node n;
|
|
|
+ return n.u;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+如果 `n` 使用了 `default` 默认构造函数,该程序的行为是未定义的。
|