CppQuiz一些有趣的题和分析

CppQuiz is a simple online quiz that you can use to test your knowledge of the C++ programming language.
很有意思,今天刷了几道随手写点东西出来,以后有空再刷刷都放到这里来好了。其实CppQuiz有很多题都可以从《深度探索C++对象模型》中找到原因…如果有很多题不会做我建议还是买一本《深度探索C++对象模型》认真看一遍吧!
另外,我尽量在解答题的同时会在C++标准(ISO/IEC 14882:2014)中找到相关的描述。

According to the C++11 standard, what is the output of this program?

Q1

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

template <class T> void f(T &i) { std::cout << 1; }
template <> void f(const int &i) { std::cout << 2; }

int main() {
int i = 42;
const int j=42;

f(i); // match to void f(int&)
f(j); // match to void f(const int&)
}

Answer: 12

Q2

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <string>

void f(const std::string &) { std::cout << 1; }

void f(const void *) { std::cout << 2; }

int main() {
f("foo");
const char *bar = "bar";
f(bar);
}

Answer:22
因为一个字面值不是一个std::string对象,但却是一个const char[],如果想要通过const char[]匹配到std::string,必须要精要user-define conversion,这不是一个最优的选择。

Ordinary string literals and UTF-8 string literals are also referred to as narrow string literals. A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration

Q3

1
2
3
4
5
6
7
8
#include <iostream>

void f(int) { std::cout << 1; }
void f(unsigned) { std::cout << 2; }

int main() {
f(-2.5);
}

**Answer: **compilation error

调用f有歧义,因为-2.5是一个signed double

The type of a floating literal is double unless explicitly specified by a suffx. The suffxes f and F specify float, the suffxes l and L specify long double.

double转换到internal需要按照Integer conversion rank,具体请看**[ISO/IEC 14882:2014 4.13]**
其中规定了带符号整型的转换等级等于无符号的转换等级:

The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.

在当前题目而言,两个f的匹配程度是一样(就是可以转换为任何一个,没有优劣),所以就造成了歧义。

Q5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

struct A {
A() { std::cout << "A"; }
};
struct B {
B() { std::cout << "B"; }
};

class C {
public:
C() : a(), b() {}

private:
B b;
A a;
};

int main()
{
C();
}

Answer:BA

因为在C构造时对成员a和b的初始化顺序不是按构造函数中的顺序来构造的,而是按数据成员在类中定义的次序来构造的。

Q6

1
2
3
4
5
6
7
8
#include <iostream>

int main() {
for (int i = 0; i < 3; i++)
std::cout << i;
for (int i = 0; i < 3; ++i)
std::cout << i;
}

**Answare: **012012

Q9

1
2
3
4
5
6
#include <iostream>
struct X {
X() { std::cout << "X"; }
};

int main() { X x(); }

结果:不输出任何东西。
因为X x();是一个函数原型,不是实例化一个对象。
改成X x{};就会输出X了。

Q11

According to the C++11 standard, what is the output of this program?

1
2
3
4
5
6
7
#include <iostream>

int a;

int main () {
std::cout << a;
}

结果:0
因为静态存储区的对象没有指定初始化会进行零初始化(zero-initialized)。

[ISO/IEC 14882:2014 §8.5/10]Every object of static storage duration is zero-initialized at program startup before any other initialization takes place.

Q13

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
#include <iostream>

class A {
public:
A() { std::cout << "a"; }
~A() { std::cout << "A"; }
};

class B {
public:
B() { std::cout << "b"; }
~B() { std::cout << "B"; }
};

class C {
public:
C() { std::cout << "c"; }
~C() { std::cout << "C"; }
};

A a;
int main() {
C c;
B b;
}

Answer: acbBCA

首先,因为static对象在main函数的第一条语句执行之前被初始化(实现定义):

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.

在大多数编译器下(还没碰到过不是的),全局对象a最先构造,然后cb按其定义的顺序构造,则输出就是acb.
而程序终止destory对象时则是根据定义的反序释放,则输出BCA.

Q15

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
33
34
#include <iostream>
#include <exception>

int x = 0;

class A {
public:
A() {
std::cout << 'a';
if (x++ == 0) {
throw std::exception();
}
}
~A() { std::cout << 'A'; }
};

class B {
public:
B() { std::cout << 'b'; }
~B() { std::cout << 'B'; }
A a;
};

void foo() { static B b; }

int main() {
try {
foo();
}
catch (std::exception &) {
std::cout << 'c';
foo();
}
}

Answer:acabBA

  1. 首先,调用foo()foo函数体中创建了一个static B对象,因为没有提供初始化式,所以会调用B的默认构造函数。
  2. 根据对象构造时的执行顺序,来初始化基类(如果有的话)和数据成员。因为类B没有基类且只有一个A类的数据成员a,按照上面的规则,会先执行成员a的初始化,因为也没有提供初始化式,所以也会调用A的默认构造函数。
  3. A的默认构造函数中输出a,然后检查此时x++ == 0的结果为true,所以会在A的构造函数中抛出一个异常。因为当在构造函数抛出一个异常时,会对其所有完全构造的子对象(不包括类似union的类的变体成员)执行构造函数,因此,A中并没有任何数据成员,所以会直接结束类A的构造,不会执行任何析构函数。
  4. catch中捕获到异常,并输出一个c
  5. 再次执行foo(),然后依次执行B->A的构造,先调用B中数据成员a的的构造函数。(输出a),由于上面执行过了x++,所以此时x++==0位false,不会抛出异常,A的对象构造成功。
  6. 执行B的构造函数体,输出b
  7. 程序结束时开始析构B类对象b,根据对象析构时的执行顺序来执行(和构造反序)
  8. 根据上面的规则,会先调用b的析构函数输出B
  9. 然后再调用b的A类数据成员a的析构函数输出A

Q17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class A {
public:
A() { std::cout << 'a'; }
~A() { std::cout << 'A'; }
};

class B : public A {
public:
B() { std::cout << 'b'; }
~B() { std::cout << 'B'; }
};

int main() { B b; }

Answer:abBA
这道题主要考继承层次下的构造顺序。
构造:

where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

以及析构:

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

Q25

1
2
3
4
5
6
7
#include <iostream>
#include <limits>

int main() {
int i = std::numeric_limits<int>::max();
std::cout << ++i;
}

**Answare: **The programm is undefined.

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.[ Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. — end note ]

Q26

1
2
3
4
5
6
7
#include <iostream>

int main() {
int i = 42;
int j = 1;
std::cout << i / --j;
}

Answer:The behavior is undefined.

Q27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

struct A {
virtual std::ostream &put(std::ostream &o) const {
return o << 'A';
}
};

struct B : A {
virtual std::ostream &put(std::ostream &o) const {
return o << 'B';
}
};

std::ostream &operator<<(std::ostream &o, const A &a) {
return a.put(o);
}

int main() {
B b;
std::cout << b;
}

**Answare: **B
因为可以使用指向基类的指针或者引用实现多态,所以operator<<中对A&调用put会调用B中的put。

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.

至于为什么D d2(d1);会输出ABDd,是因为当你显式定义了派生类的拷贝构造函数时,需要你手动调用其基类的拷贝构造函数。
如果你把D中的拷贝构造函数注释掉就会输出abc了。

Q28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

struct A {
A() { std::cout << "A"; }
A(const A &a) { std::cout << "B"; }
virtual void f() { std::cout << "C"; }
};

int main() {
A a[2];
for (auto x : a) {
x.f();
}
}

Answer: AABCBC

因为for(auto x:a),范围for语句这里是拷贝的,将a中的元素逐个拷贝至x,所以会产生调用拷贝构造函数。如果不使用拷贝可以使用引用传递给range for:

1
for(auto& x:a){}

Q29

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct A {
A() { foo(); }
virtual ~A() { foo(); }
virtual void foo() { std::cout << "1"; }
void bar() { foo(); }
};

struct B : public A {
virtual void foo() { std::cout << "2"; }
};

int main() {
B b;
b.bar();
}

**Answare: **121
所用到的东西都在对象的构造和析构顺序以及不要在基类构造函数中调用虚函数期待多态行为写到了。
这里比较重要的一点就是在基类中调用虚函数,不会调用派生类重写的版本(因为基类构造先于派生类),而函数调用时因为类构造已经完成,所以在调用bar只能找到派生类的版本。
在程序结束时会析构B,会隐式调用A的析构函数。构造和析构的顺序都在上面的链接文章里写到了。

Q32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct X {
X() { std::cout << "a"; }
X(const X &x) { std::cout << "b"; }
const X &operator=(const X &x) {
std::cout << "c";
return *this;
}
};

int main() {
X x;
X y(x);
X z = y;
z = x;
}

Answer:abbc

需要注意的地方只有X z=y;调用的不是operator=()而是X(const x &x)构造函数。注意区别拷贝构造赋值操作

The first line in main(), X x; is straightforward, it calls the default constructor.
The next two lines is the heart of the question: The difference between X y(x) and X z = y is not that the first calls the copy constructor, and the second calls the copy assignment operator. The difference is that the first is direct initialization (§8.5.15 in the standard) and the second is copy initialization (§8.5.14).
§8.5.16 says: “If the initialization is direct-initialization, or if it is copy-initialization where the (…) source type is the same class as (…) the class of the destination, constructors are considered.” So both our cases use the copy constructor.
Not until z = x; do we have an actual assignment that uses the assignment operator.
See http://stackoverflow.com/#1051468 for a more detailed discussion of direct vs. copy initialization.

Q35

1
2
3
4
5
6
7
8
#include <iostream>
#include <vector>

int main() {
std::vector<int> v1(1, 2);
std::vector<int> v2{ 1, 2 };
std::cout << v1.size() << v2.size();
}

Answer:12

v1是一个元素被初始化为2,可以看下vector的构造函数:

1
2
// Effects: Constructs a vector with n copies of value, using the specified allocator.
vector(size_type n, const T& value,const Allocator& = Allocator());

v2是用一个初始化列表(initializer_list)来初始化vector对象,结果是初始化列表中的元素的个数。

Q49

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class C {
public:
C(int i) : i(i) { std::cout << i; }
~C() { std::cout << i + 5; }

private:
int i;
};

int main() {
const C &c = C(1);
C(2);
}

**Answer: **1276

因为const引用会给临时对象续命到该引用的作用域范围,所以C(1)不会在离开当前表达式时调用一个析构函数,而是推迟到离开当前块时。

Q52

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class A;

class B {
public:
B() { std::cout << "B"; }
friend B A::createB();
};

class A {
public:
A() { std::cout << "A"; }

B createB() { return B(); }
};

int main() {
A a;
B b = a.createB();
}

**Answer: ** compilation error

因为虽然在B的定义之前具有A的前值声明,但是,在B的友元函数A::createB()在此时是无法确定A中是否存在的,所以会编译错误。

解决的办法之一,直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

class B;

class A {
public:
A() { std::cout << "A"; }
B createB();
};

class B {
public:
B() { std::cout << "B"; }
friend B A::createB();
};

B A::createB(){
return B();
}

int main() {
A a;
B b = a.createB();
}

将需要相互引用到的函数定义推迟到所有的类定义之后。

Q112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct A
{
A() { std::cout << "1"; }
A(const A&) { std::cout << "2"; }
A(A&&) { std::cout << "3"; }
};

struct B
{
A a;
B() { std::cout << "4"; }
B(const B& b) : a(b.a) { std::cout << "5"; }
B(B&& b) : a(b.a) { std::cout << "6"; }
};

int main()
{
B b1;
B b2 = std::move(b1);
}

Answer:1426

B b1;的构造顺序为1.扩充B的构造函数,将A的构造函数来构造成员对象a;2.调用B的默认构造函数。

当一个类具有默认构造函数,且具有一个及以上的成员类对象时,那么该类会扩张已有的默认构造函数,使得在该类原有的默认构造函数中的用户代码被调用之前,先调用必要的成员类对象的构造函数。

则上面代码中B的默认构造函数在编译时被扩充为:

1
2
3
4
5
b(){
// 伪码,调用A的构造函数初始化对象a
a.A::A();
cout<<"4"<<endl;
}

如果有多个类成员对象都要求构造初始化操作,C++语言要求以成员对象在类中的声明顺序来调用各个成员类对象的默认构造函数。

更多内容请参照《深度探索C++对象模型》P43.

First, b1 is default initialized. All members are initialized before the body of the constructor, so b1.a is default initialized first, and we get the output 14.
§12.6.2¶8 in the standard: “In a non-delegating constructor, if a given non-static data member or base class is not designated by a
mem-initializer-id (…) then if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in §8.5 (…) otherwise, the entity is default-initialized.”
Then, b2 is initialized with the move construcor (since std::move(b1)converts the reference to b1 to an xvalue, allowing it to be moved from.) In B’s move constructor, a is initialized in the initializer list. Even though a is an rvalue reference (and bound to an rvalue), a itself is an lvalue, and cannot be moved from. b2.a is then copy initialized, printing 2, and finally the body of B’s move constructor prints 6.(If the concept of rvalue references being lvalues is confusing, read The Article. Search for “In widget”.)

Q116

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <utility>

int y(int &) { return 1; }
int y(int &&) { return 2; }

template <class T> int f(T &&x) { return y(x); }
template <class T> int g(T &&x) { return y(std::move(x)); }
template <class T> int h(T &&x) { return y(std::forward<T>(x)); }

int main() {
int i = 10;
std::cout << f(i) << f(20);
std::cout << g(i) << g(20);
std::cout << h(i) << h(20);
return 0;
}

The T&& in the the templated functions do not necessarily denote an rvalue reference, it depends on the type that is used to instantiate the template. If instantiated with an lvalue, it collapses to an lvalue reference, if instantiated with an rvalue, it collapses to an rvalue reference. See note [1].
Scott Meyers has written a very good article about this, where he introduces the concept of “universal references” (note that this is not C++ standard wording) http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
In this example, all three functions are called once with an lvalue and once with an rvalue. In all cases, calling with an lvalue (i) collapses T&& x to T& x (an lvalue reference), and calling with an rvalue (20) collapses T&& x to T&& x (an rvalue reference). Inside the functions, x itself is always an lvalue, no matter if its type is an rvalue reference or an lvalue reference.

  • For the first example, y(int&) is called for both cases. Output: 11.
  • For the second example, move(x) obtains an rvalue reference, and y(int&&)is called for both cases. Output: 22.
  • For the third example, forward(x) obtains an lvalue reference when x is an lvalue reference, and an rvalue reference when x is an rvalue reference, resulting in first a call to y(int&)and then a call to y(int&&). Output: 12.

Note [1]: §8.3.2¶6 in the standard: “If a (…) type template-parameter (§14.3.1) (…) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type
“lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.” The example at the end of that paragraph is is worth a look.
Note from the contributor: This demonstrates Scott Meyers’s advice that use std::forward for universal references, and std::move for rvalue references.

Q119

1
2
3
4
5
6
#include <iostream>

int main() {
void * p = &p;
std::cout << bool(p);
}

**Answer: **1
因为将一个对象在声明时赋值给它本身是可以的(不过会具有不确定的值)。

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below.

1
2
unsigned char x = 12;
{ unsigned char x = x; }

Here the second x is initialized with its own (indeterminate) value.

在这道题目下,p被赋值为指向p的指针,可以确定的是它不是NULL.所以将其转换为bool时,它为true.

[ISO/IEC 14882:2014 §4.12]A zero value, null pointer value, or null member pointer value is converted to false;any other value is converted to true.

我们可以来看一下其中间代码,分析下编译器是怎么实现的:

1
2
3
4
5
6
7
8
9
define i32 @main() #4 {
%1 = alloca i8*, align 8
%2 = bitcast i8** %1 to i8*
store i8* %2, i8** %1, align 8
%3 = load i8*, i8** %1, align 8
%4 = icmp ne i8* %3, null
%5 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEb(%"class.std::basic_ostream"* @_ZSt4cout, i1 zeroext %4)
ret i32 0
}

可以看到:

1
%4 = icmp ne i8* %3, null

这一行的意思是判断%3与null是否相等,这里产生了一个ne,该中间代码的意思是:

ne: yields true if the operands are unequal, false otherwise. No sign interpretation is necessary or performed.

因为%3是具有实际地址的,所以不等于null,即产生的bool值是true.

Q120

1
2
3
4
5
6
7
int main() {
int a = 10;
int b = 20;
int x;
x = a, b;
std::cout << x;
}

输出:10
因为逗号运算符的优先级是最低的,这里会最先执行赋值操作,然后再执行逗号运算符。
C++运算符的优先级可以在看这篇文章:C++关键字与运算符优先级别速查表

Q124

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

struct A {};
struct B {};

template<typename T = A> struct X;

template<> struct X<A> {
static void f() { cout << 1 << endl; }
};

template<> struct X<B> {
static void f() { cout << 2 << endl; }
};

template<template<typename T = B> class C>
void g() {
C<>::f();
}

int main() {
g<X>();
}

Answer:2

注意:C<>::f()是调用模板默认参数的版本,也就是C<X<B>>::f(),所以输出结果是2

A template-parameter of a template template-parameter is permitted to have a default template-argument.When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter.

Q125

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

using namespace std;

template <class T> void f(T) {
static int i = 0;
cout << ++i;
}

int main() {
f(1);
f(1.0);
f(1);
}

**Answer: **112

1
2
3
f(1); // 特化一个f(int)
f(1.1); // 特化一个f(double)
f(1); // 使用先前特化的f(int)

则产生了两个版本的f,每个版本的f具有它自己的static对象i,所以按照f的调用来看产生的结果就是112.

Q130

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

template<typename T>
void adl(T){cout << "T";}

struct S{};

template<typename T>
void call_adl(T t)
{
adl(S());
adl(t);
}

void adl(S){cout << "S";}

int main()
{
call_adl(S());
}

Answer:TS
这是由于在call_adl的第一个adl(S())的调用是一个Non-dependent names,所以使用的是普通的名字查找规则。即,在此时adl存在的唯一版本是adl(T),而adl(S)在此刻还不存在,所以并不会纳入候选集。
而第二个adl(t)的调用因为依赖于模板参数,所以调用决议推迟到实例化处call_adl(S()),而此时adl(S),已经具有声明且定义,所以会被纳入候选集,从而被匹配。

When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1,3.4.2) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known (14.6.2).

Q131

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

class C {
public:
explicit C(int) {std::cout << "i";};
C(double) {std::cout << "d";};
};

int main() {
C c1(7);
C c2 = 7;
}

Answer:id

显式构造函数(explicit-constructor)只有在直接初始化(direct-initialization)时才会调用。

An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value- initialization (8.5).

转换构造函数(converting constructor):

A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.

C c1(7)是直接初始化(direct-initializtion),而C c2=7;是拷贝初始化(copy-initializatio)。虽然大多数情况下实现应该是等价的,但是在上面的代码中是不一样的。

as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.

而拷贝初始化:

as well as in argument passing, function return, throwing an exception (15.1), handling an exception(15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). — end note ]

即因为explicit constructor只会在直接初始化中调用,所以在C c2=7;中,构造函数C(int)不会作为候选集,所以只能匹配到C(double)

Q133

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
33
34
35
36
#include <iostream>
using namespace std;

class A
{
public:
A() { cout << "A"; }
A(const A &) { cout << "a"; }
};

class B: virtual A
{
public:
B() { cout << "B"; }
B(const B &) { cout<< "b"; }
};

class C: virtual A
{
public:
C() { cout<< "C"; }
C(const C &) { cout << "c"; }
};

class D:B,C
{
public:
D() { cout<< "D"; }
D(const D &) { cout << "d"; }
};

int main()
{
D d1;
D d2(d1);
}

**Answare: **ABCDABCd

同样用到了对象的构造和析构顺序里面写的动西,上面的代码里唯一可能有疑问的就是A究竟构造了几次,因为在B和C都是虚继承,所以只会构造一次,又因为在D中B的继承顺序在前,所以,从左到右由深度优先的策略会先执行A的构造函数然后B的然后C的,最后执行D的构造函数体。

Q135

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <map>
using namespace std;

int main()
{
map<bool,int> mb = {{1,2},{3,4},{5,0}};
cout << mb.size();
map<int,int> mi = {{1,2},{3,4},{5,0}};
cout << mi.size();
}

Answer:13

首先,你要明白map只能存唯一的键,其次,数值1/3/5转型为bool型都是true…此时的mb[true]是2

std::map stores values based on a unique key. The keys for mb are boolean, and 13and 5 all evaluate to the same key, true.
§23.4.4.1¶1 in the standard:
“A map is an associative container that supports unique keys (contains at most one of each key value).”
The type of mb is map. The key is bool, so the integers 13 and 5 used for initialization are first converted to bool, and they all evaluate to true.
§4.12¶1 in the standard:
“A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.”

Q140

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
#include <iostream>
using namespace std;

size_t get_size_1(int* arr)
{
return sizeof arr;
}

size_t get_size_2(int arr[])
{
return sizeof arr;
}

size_t get_size_3(int (&arr)[10])
{
return sizeof arr;
}

int main()
{
int array[10];
cout << (sizeof(array) == get_size_1(array));
cout << (sizeof(array) == get_size_2(array));
cout << (sizeof(array) == get_size_3(array));
}

*Answare: **001
因为get_size_1和get_size_2传进去的只是一个int的指针而已,对齐sizeof操作得到的只是一个int
的大小。
而get_size_3传进来的是一个十个元素的int型数组,所以对其sizeof操作得到的就是十个int型元素数组的大小。

Q144

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <limits>

int main()
{
int N[] = {0,0,0};

if ( std::numeric_limits<long int>::digits==63 and
std::numeric_limits<int>::digits==31 and
std::numeric_limits<unsigned int>::digits==32 )
{
for (long int i = -0xffffffff; i ; --i)
{
N[i] = 1;
}
}
else
{
N[1]=1;
}

std::cout << N[0] <<N [1] << N[2];
}

Answer:010

std::numeric_limits<T>::digits的结果为T的数据位大小(sizeof(T)*CHAR_BIT,不包含符号位)。

另外,C++标准只规定了内置数据类型能表示的最小范围,如下图:

类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位
int 整型 16位
long 长整型 32位
long long 长整型 64位
float 单精度浮点型 6位有效数字
double 双精度浮点型 10位有效数字
long double 扩展精度浮点型 10位有效数字

else的部分就不多说了,主要是看if范围内的部分。

最重要的概念是,无符号数的负数通过$2^n$减去其值得到,即:

-0xffffffff=$2^n-\sum_{x=0}^{31}2^i$=1

As the else part of the branch is obvious, we concentrate on the if part and make the assumptions present in the condition.
§2.14.2 in the standard: “The type of an integer literal is the first of the corresponding list in Table 6.” [Table 6: int, unsigned int, long int, unsigned long int … for hexadecimal literals –end Table] in which its value can be represented.”
Since the literal 0xffffffff needs 32 digits, it can be represented as an unsigned int,but not as a signed int, and is of type unsigned int. But what happens with the negative of an unsigned integer?
§5.3.1 in the standard: “The negative of an unsigned quantity is computed by subtracting its value from 2^n , where n is the number of bits in the promoted operand.” Here n is 32, and we get:
2^32 - 0xffffffff = 4294967296 - 4294967295 = 1
So i is initialised to 1, and N[1] is the only element accessed in the loop. (The second time around the loop, i is 0, which evaluates to false, and the loop terminates.)

Q147

1
2
3
4
5
int main(){
int x=0; //What is wrong here??/
x=1;
std::cout<<x;
}

**Answare: **0

因为??/是一个Trigraph sequences,代表的是\.而\的作用也是把下一物理行变为逻辑行。

Each instance of a backslash character (\) immediately followed by a new-line character is deleted,
splicing physical source lines to form logical source lines.

那么,上面的代码实际上为:

1
2
3
4
5
int x=0; //What is wrong here\
x=1;
// 等价于

int x=0; //What is wrong herex=1;

Q151

1
2
3
4
5
6
7
#include <iostream>
#include <type_traits>

int main()
{
std::cout << std::is_signed<char>::value;
}

C++标准中规定,char是unsigned还是signed是由实现定义的,所以这个问题是implementation-define behavior.
. It is implementation-defined whether a char object can hold negative values.

Q153

1
2
3
4
5
6
#include <iostream>

int main() {
char* str = "X";
std::cout << str;
}

A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration.

在C++11中const char转换到char是不合法的转换。

ISO C++11 does not allow conversion from string literal to ‘char *’.

解决办法就是:

1
const char* str="x";

Q158

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

struct Foo
{
Foo() { std::cout<<"a"; }
Foo(const Foo&) { std::cout<<"b"; }
};

int main()
{
std::vector<Foo> bar(5);
}

Answer:aaaaa

vector<Foo> bar(5)其中bar(5)不是将5传递给Foo的构造函数,是指定vector容器中元素的数目

创建顺序容器时,可显式指定容器大小和一个(可选的)元素初始化式。
容器大小可以是常量非常量表达式,元素初始化式则必须是可用于初始化其元素类型的对象的值。

如果想要在指定容器数目时提供初始化式可以使用:

1
std::vector<Foo> initTenFooObj(10,Foo());

Since C++11 (§23.3.6.2¶3 in the standard), std::vector has a one parameter constructor
explicit vector( size_type n )
which constructs a vector with n value-initialized elements. Each value-initialization calls the default Foo constructor, resulting in the output aaaaa .
The “trick” is, that before C++11, std::vector had a 2 parameter constructor ( + allocator ), which constructed the container with n copies of the second parameter, which is defaulted to T().So this code before C++11 would output abbbbb, because the call would be equivalent to std::vector bar(5,T()).

Q157

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <typeinfo>

struct A {};

int main()
{
std::cout<< (&typeid(A) == &typeid(A));
}

Answer:This question The program is unspecified / implementation defined.

The result of a typeid expression is an lvalue of static type const std::type_info (18.7.1) and dynamic type const std::type_info or const name where name is an implementation-defined class publicly derived from std::type_info which preserves the behavior described in 18.7.1

而unary operator &则是:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id.

两次typeid操作产生了两个指针,而两个指针相比较,比较的是他们中存储的值:

Comparing pointers is defined as follows: Two pointers compare equal if they are both null, both point to the same function, or both represent the same address (3.9.2), otherwise they compare unequal.

没有保证相同的std::type_info实例将被相同类型的typeid表达式的所有求值引用。
so,this is a implementation-define.

Q159

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int i;

void f(int x) {
std::cout << x << i;
}

int main() {
i = 3;
f(i++);
}

**Answer: **34

C++标准规定,函数实参的副作用求值在进入函数之前被排序。

[ISO/IEC 14882:2014 §5.2.3.8] All side effects of argument evaluations are sequenced before the function is entered.

也可以从IR代码的角度来看一下编译器对这个特性的实现:

1
2
3
4
5
6
7
8
define i32 @main() #4 {
store i32 3, i32* @i, align 4
%1 = load i32, i32* @i, align 4
%2 = add nsw i32 %1, 1
store i32 %2, i32* @i, align 4
call void @_Z1fi(i32 %1)
ret i32 0
}

可以看到,当我们在执行f(i++)的时候,编译器先将i自增,然后将未自增之前的值作为实参传递给f。而进入函数f后,因为static对象i的值已经在主函数中被修改,所以,在函数f中输出的值分别是3和4.

Q160

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

struct A {
virtual void foo (int a = 1) {
std::cout << "A" << a;
}
};

struct B : A {
virtual void foo (int a = 2) {
std::cout << "B" << a;
}
};

int main () {
A *b = new B;
b->foo();
}

**Answare: **B1

因为overrideing的函数不会覆盖其默认参数,所以在类B中定义的foo其默认参数仍然为1.

A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides.

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:CppQuiz一些有趣的题和分析
文章作者:查利鹏
发布时间:2016年10月24日 06时15分
本文字数:本文一共有15k字
原始链接:https://imzlp.com/posts/10205/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!