Loading... # 1 带有初始化列表的构造函数重载决议 ```cpp #include <initializer_list> #include <iostream> struct A { A() { std::cout << "1"; } A(int) { std::cout << "2"; } A(std::initializer_list<int>) { std::cout << "3"; } }; int main(int argc, char *argv[]) { A a1; A a2{}; A a3{ 1 }; A a4{ 1, 2 }; } ``` 答案: ```cpp 1133 ``` ## 解析 `a2`到`a4`均为列表初始化,具体调用重载的方式如下: ![image.png](https://zclll.com/usr/uploads/2022/06/2541249625.png) 由于阶段1被跳过,所以`a2`调用了默认构造,之后两个均在阶段1找到了初始化列表构造。 **重载决议**这东西整体还是很复杂的…… # 2 聚合初始化 ```cpp #include <iostream> struct S { S() = delete; int x; }; int main() { auto s = S{}; std::cout << s.x; } ``` 答案: ```cpp 0 ``` ## 解析 这看起来很像是CE,因为`S`的构造函数已经显式删除了。然而注意到`S`是一个聚合体,所以列表初始化实际上进行了聚合初始化。由于列表是空的,所以这里进行了**从空列表进行的复制初始化**。(注意,在**C++14之前**是**空的值初始化从而得到的零初始化**,而非空复制初始化。) 聚合体是指这样一种东西: ![image.png](https://zclll.com/usr/uploads/2022/06/1366462317.png) 而[聚合初始化](https://zh.cppreference.com/w/cpp/language/aggregate_initialization)可以从链接中详细查看。 POD类型和聚合类的关系: POD=平凡+标准布局 聚合类≈标准布局 # 3 成员函数的类型 ```cpp #include <type_traits> #include <iostream> using namespace std; struct X { int f() const&&{ return 0; } }; int main() { auto ptr = &X::f; cout << is_same_v<decltype(ptr), int()> << is_same_v<decltype(ptr), int(X::*)()>; } ``` 答案: ```cpp 00 ``` ## 解析 函数类型包括**默认实参以外的函数声明的一切**: ![image.png](https://zclll.com/usr/uploads/2022/06/3222809940.png) 成员函数可以有引用限定,但需要注意,**引用限定不改变`this`指针的类型**,它只是指明了调用要求。 (甚至,成员函数也可以为`this`[指明形参](https://zh.cppreference.com/w/cpp/language/member_functions#:~:text=%E6%8A%BD%E8%B1%A1%E7%B1%BB%E3%80%82-,%E6%98%BE%E5%BC%8F%E5%AF%B9%E8%B1%A1%E5%BD%A2%E5%8F%82,-%E9%9D%9E%E9%9D%99%E6%80%81%E6%88%90%E5%91%98),此时指向它的指针是普通的**非成员函数指针**) 因为指向非静态成员函数的函数指针总是通过对象实例去调用,这意味着它们不包含对象信息(这很显然), # 4 虚表与虚函数调用 事实上,这道题是我自己出的: ```cpp #include <bits/stdc++.h> struct X { virtual void f() { std::cout << "x"; } }; struct Y : X { void f() override { std::cout << "y"; } }; int main() { auto p = &X::f; X *x = new X, *y = new Y; (x->*p)(); (y->*p)(); return 0; } ``` 答案: ```cpp xy ``` ## 解析 虚函数的名始终对应的是虚表指针,而并不直接对应代码段中的函数,因此函数指针`p`并不能指向具体的函数(它其实也是指向的虚表),所有虚函数的调用都是必须实时产生的。 可以看到: ![image.png](https://zclll.com/usr/uploads/2022/06/1913697588.png) 实际上虚函数指针`p`并不实时指向实际的函数,而是通过存储偏移量的方式指向虚表(vtable)中的槽位(slot),实际上我们需要从虚表头加上对应偏移,从而得到实际的函数调用地址。 例如我们这样修改: ![image.png](https://zclll.com/usr/uploads/2022/06/3493850672.png) ![image.png](https://zclll.com/usr/uploads/2022/06/4127831487.png) 从而得到: ![image.png](https://zclll.com/usr/uploads/2022/06/3283582463.png) 我们通过`X`的虚表找到三个虚函数: ![image.png](https://zclll.com/usr/uploads/2022/06/2110336745.png) © 允许规范转载 打赏 赞赏作者 赞 如果觉得我的文章对你有用,请随意赞赏