Loading... # 1 整型提升 ```cpp #include <iostream> void f(unsigned int) { std::cout << "u"; } void f(int) { std::cout << "i"; } void f(char) { std::cout << "c"; } int main() { char x = 1; char y = 2; f(x + y); } ``` 答案: Implement Defined。 ## 解析 首先: > “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides **at least as much** storage as those preceding it in the list. 也就是说`int`不一定比`char`更大,只有不小于的关系是得到保证的。我们知道加法会首先对操作数进行整型提升,而又因为: > It is implementation-defined whether a char object can hold negative values. 所以当我们需要将它提升到可容纳它的(至少是`int`的)类型时,如果当前环境`char`可以包含负数,`x+y`就是`signed char+signed char`得到`int`;反之,`x+y`就是`unsigned char+unsigned char`得到`unsigned int`。 而产生分歧的原因是ID不是UB,所以最终的结果是ID的。 # 2 重载决议中的隐式转换 ```cpp #include <iostream> void f(float &&) { std::cout << "f"; } void f(int &&) { std::cout << "i"; } template <typename... T> void g(T &&... v) { (f(v), ...); } int main() { g(1.0f, 2); } ``` 答案: ```plaintext if ``` ## 解析 首先, 在进入`g()`以后, 我们实际产生的调用是`g(float&& v1 = 1.0f, int&& v2 = 2)`, 然而**这两个右值引用的值类别是左值!** (关于这一点, [我的文章](https://zclll.com/index.php/cpp/value_category.html)的#0302节有详细的解释, 不再赘述). 确认了这一点, 那么9行实际上就会产生`f(float)`和`f(int)`先后两个调用. 那么结果是fi吗? 此时就要进行一下重载决议了, 而这时就有一件容易被遗忘的事情: **右值引用不能绑定到左值上**! ![image.png](https://zclll.com/usr/uploads/2023/02/3439347207.png) 从而`f(1.0f)`不能调用`f(float)`重载, `int`那一支同理. 然而, 经由**标准转换序列中的数值转换**, `int`左值可以转换为一个`float`亡值, `float`左值也可以转换为一个`int`亡值, 从而`f(1.0f)`可以调用`f(int&&)`, 而`f(2)`可以调用`f(float&&)`! 于是展现给我们的结果就是一个float调用了int重载, int调用了float重载, 似乎"颠倒"了过来. --- 这件事情看起来诡异, 但其实质是合理的: 左值和右值之间不能随意转换, 否则区分左右值就没有意义了. 看起来相比于`float&&`, `int`转化成`int&&`更为自然, 但这种转换是`std::move`的任务. 而隐式转换序列在重载决议时又是应当允许的, 这二者组合就产生了这一看似奇怪的结果. # 3 初始化列表 ```cpp #include <iostream> #include <vector> int f() { std::cout << "f"; return 0;} int g() { std::cout << "g"; return 0;} void h(std::vector<int> v) {} int main() { h({f(), g()}); } ``` 答案: ```plaintext fg ``` ## 解析 函数实参求值是不定序的, 然而这里有一个初始化列表, **初始化列表是by order求值的**. 当然, 前提也是因为`std::vector`有接收init_list的构造函数重载. 如果一个类型既没有这样的重载, 又不是聚合类(不能聚合初始化), 那么这里也有隐含的一个坑. # 4 静态成员初始化 ```cpp #include<iostream> int foo() { return 10; } struct foobar { static int x; static int foo() { return 11; } }; int foobar::x = foo(); int main() { std::cout << foobar::x; } ``` 答案: ```plaintext 11 ``` ## 解析 ![image.png](https://zclll.com/usr/uploads/2023/02/3836884971.png) 静态数据成员是可以延迟定义的, 此时类内的声明不是默认定义. 从而17行是`static`变量的有效定义, 而它本质上还在类内, 所以根据**static成员初始化的调用如同成员函数中的调用**, 此时调用的`foo()`是类内的重载. ![image.png](https://zclll.com/usr/uploads/2023/02/2344608691.png) # 5 综合编译期类型判断 ```cpp #include <iostream> #include <type_traits> using namespace std; int main() { int i, &j = i; [=] { cout << is_same<decltype ((j)), int >::value << is_same<decltype (((j))), int & >::value << is_same<decltype ((((j)))), int const& >::value << is_same<decltype (((((j))))), int && >::value << is_same<decltype((((((j)))))), int const&& >::value; }(); } ``` 答案: ```plaintext 00100 ``` ## 解析 非常综合的一道题, 依次列举考点: 1. 首先对于`decltype((x))`, 这个双层小括号是有实际意义的, 它用来区分左值引用和非引用. **如果内部为左值, 那么`decltype(())`将产生左值引用**. 括号更多没有其他意义, 与此等价. 2. 捕获符`[=]`说明以复制作为默认的捕获方式, 但`decltype`**并非ODR使用, 因此`j`未被捕获, 而是直接使用原对象**! `decltype`推导的是`j`原本的`int&`类型, 这又是一大坑点. 3. 由于lambda未加说明符`mutable`, 因此`j`带有`const`限定. 4. `std::is_same`完全以模板方式(即, 使用**重载决议规则**)判断两类型是否一致, 因此是否带`const`指示两不同类型. © 允许规范转载 打赏 赞赏作者 赞 2 如果觉得我的文章对你有用,请随意赞赏
2 条评论
叔叔,这些quiz是哪找来的额
cppquiz.org 哦