1.4 统一的类成员初始化语法与 std::initializer_list<T>
假设类A有一个成员变量是一个int数组,在C++98/03标准中,如果我们要在构造函数中对其进行初始化,我们需要这样写:
//C++98/03类成员变量是数组时的初始化语法
class A
{
public:
A()
{
arr[0] = 2;
arr[1] = 0;
arr[2] = 1;
arr[3] = 9;
}
public:
int arr[4];
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对于字符数组,我们可能就要在构造函数中使用strcpy、memcpy这一类函数了;再者如果数组元素足够多,初始值又没什么规律,这种赋值代码会有很多行。但是,如果arr是一个局部变量,我们在定义arr时其实是可以使用如下的语法初始化的:
int arr[4] = {2, 0, 1, 9};
既然C++98/03标准中,局部变量数组支持这种语法,为什么在类成员变量语法中就不支持呢?这是旧语法不合理的一个地方,因此在C++11语法中类成员变量也可以使用这种语法进行初始化了:
//C++11类成员变量是数组时的初始化语法
class A
{
public:
A() : arr{2, 0, 1, 9}
{
}
public:
int arr[4];
};
2
3
4
5
6
7
8
9
10
11
新语法相比较旧语法,更加简洁。
在像Java这类语言中,定义一个类时,即可给其成员变量设置一个初始值,语法如下:
class A
{
public int a = 1;
public String string = "helloworld";
};
2
3
4
5
但在C++89/03标准中要使用这种语法,必须是针对类的static const成员,且必须是整型(包括bool、char、int、long等)。
//C++89/03在类定义处初始化成员变量
class A
{
public:
//T的类型必须整型,且必须是static const成员
static const T t = 某个整型值;
};
2
3
4
5
6
7
在C++11标准中,就没有这种限制了,你可以使用花括号(即{})对任意类型的变量进行初始化,且不用是static类型。
//C++11在类定义处初始化成员变量
class A
{
public:
bool ma{true};
int mb{2019};
std::string mc{"helloworld"};
};
2
3
4
5
6
7
8
当然,在实际开发的时候,建议还是将这些成员变量的初始化统一写到构造函数的初始化列表中去,方便代码阅读和维护。
综上所述,在C++11标准中,无论是局部变量还是类变量,使用花括号({})初始化的语法被统一起来,写法也变得简洁起来。
那么这种语法是如何实现的呢?如何在自定义类中也支持这种花括号呢?这就需要用到C++11引入的新对象std::initializer_list<T>,这是一个模板对象,接收一个自定义参数类型T,T既可以是基础数据类型(如编译器内置的bool、char、int 等)也可以是自定义复杂数据类型。为了使用std::initializer_list<T>,需要包含**<initializer_list>**头文件。下面是一个例子:
#include <iostream>
#include <initializer_list>
#include <vector>
class A
{
public:
A(std::initializer_list<int> integers)
{
m_vecIntegers.insert(m_vecIntegers.end(), integers.begin(), integers.end());
}
~A()
{
}
void append(std::initializer_list<int> integers)
{
m_vecIntegers.insert(m_vecIntegers.end(), integers.begin(), integers.end());
}
void print()
{
size_t size = m_vecIntegers.size();
for (size_t i = 0; i < size; ++i)
{
std::cout << m_vecIntegers[i] << std::endl;
}
}
private:
std::vector<int> m_vecIntegers;
};
int main()
{
A a{ 1, 2, 3 };
a.print();
std::cout << "After appending..." << std::endl;
a.append({ 4, 5, 6 });
a.print();
return 0;
}
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
37
38
39
40
41
42
43
44
45
46
47
上述代码,我们自定义了一个类A,为了让A的构造函数和append方法同时支持花括号语法,给这两个方法同时设置了一个参数integers,参数类型均为std::initializer_list<int>,程序执行结果如下:
[root@myaliyun testxx]# ./test_initializer_list
1
2
3
After appending...
1
2
3
4
5
6
2
3
4
5
6
7
8
9
10
11
12
再来看一个例子,网上某C++ json库支持如下语法创建一个json对象:
// a way to express an _array_ of key/value pairs
// [["currency", "USD"], ["value", 42.99]]
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
2
3
那么这个json::array()方法是如何实现的呢?这利用std::initializer_list<T>也很容易实现,首先花括号中有两个元素{"currency", "USD"}和{"value", 42.99},且这两个元素的值不一样,前者是两个字符串类型,后者是一个字符串和一个浮点型,因此我们可以创建两个构造函数分别支持这两种类型的构造函数,构造的对象类型为jsonNode,然后创建一个类型为json的对象,实现其array()方法,该方法接收一个参数,参数类型为std::initializer_list<jsonNode>,完整的代码如下所示:
#include <iostream>
#include <string>
#include <initializer_list>
#include <vector>
//简单地模拟json支持的几种数据类型
enum class jsonType
{
jsonTypeNull,
jsonTypeInt,
jsonTypeLong,
jsonTypeDouble,
jsonTypeBool,
jsonTypeString,
jsonTypeArray,
jsonTypeObject
};
struct jsonNode
{
jsonNode(const char* key, const char* value) :
m_type(jsonType::jsonTypeString),
m_key(key),
m_value(value)
{
std::cout << "jsonNode contructor1 called." << std::endl;
}
jsonNode(const char* key, double value) :
m_type(jsonType::jsonTypeDouble),
m_key(key),
m_value(std::to_string(value))
{
std::cout << "jsonNode contructor2 called." << std::endl;
}
//...省略其他类型的构造函数...
jsonType m_type;
std::string m_key;
//始终使用string类型保存值是避免浮点类型因为精度问题而显示不同的结果
std::string m_value;
};
class json
{
public:
static json& array(std::initializer_list<jsonNode> nodes)
{
m_json.m_nodes.clear();
m_json.m_nodes.insert(m_json.m_nodes.end(), nodes.begin(), nodes.end());
std::cout << "json::array() called." << std::endl;
return m_json;
}
json()
{
}
~json()
{
}
std::string toString()
{
size_t size = m_nodes.size();
for (size_t i = 0; i < size; ++i)
{
switch (m_nodes[i].m_type)
{
//根据类型,组装成一个json字符串,代码省略...
case jsonType::jsonTypeDouble:
break;
}
}
}
private:
std::vector<jsonNode> m_nodes;
static json m_json;
};
json json::m_json;
int main()
{
json array_not_object = json::array({ {"currency", "USD"}, {"value", 42.99} });
return 0;
}
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
程序执行结果如下:
[root@myaliyun testxx]# ./construct_complex_objects
jsonNode contructor1 called.
jsonNode contructor2 called.
json::array() called.
2
3
4
通过上面两个例子希望读者可以理解**std::initializer_list<T>**的使用场景,**std::initializer_list<T>**除了构造函数还提供了三个成员函数,这和 stl 的其他容器的同名方法用法一样:
//返回列表中元素的个数
size_type size() const;
//返回第一个元素的指针
const T* begin() const;
//返回最后一个元素的下一个位置,代表结束
const T* end() const;
2
3
4
5
6