CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章C++惯用法与ModernC++篇

    • 1.1 C++ RAII惯用法
    • 1.2 pimpl 惯用法
    • 1.3 拥抱C++新变化(C++11/14/17新增的实用特性)
    • 1.4 统一的类成员初始化语法与 std::initializer_list<T>
    • 1.5 C++17注解标签(attributes)
    • 1.6 final/override/=default/=delete语法
    • 1.7 auto关键字的前尘后事
    • 1.8 Range-based 循环语法
    • 1.9 C++17结构化绑定
    • 1.10 stl容器新增的实用方法介绍
    • 1.11 stl中的智能指针类详解
  • 第2章C++开发工具与调试进阶

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章网络通信协议设计

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章 服务其他模块设计

  • C++后端开发进阶
  • 第1章C++惯用法与ModernC++篇
zhangxf
2023-04-03

1.4 统一的类成员初始化语法与 std::initializer_list

假设类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];
};
1
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};
1

既然C++98/03标准中,局部变量数组支持这种语法,为什么在类成员变量语法中就不支持呢?这是旧语法不合理的一个地方,因此在C++11语法中类成员变量也可以使用这种语法进行初始化了:

//C++11类成员变量是数组时的初始化语法
class A
{
	public:
		A() : arr{2, 0, 1, 9}
		{		
		}
		
	public:
		int arr[4];
};
1
2
3
4
5
6
7
8
9
10
11

新语法相比较旧语法,更加简洁。

在像Java这类语言中,定义一个类时,即可给其成员变量设置一个初始值,语法如下:

class A
{
    public int a = 1;
    public String string = "helloworld"; 
};
1
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 = 某个整型值;
};
1
2
3
4
5
6
7

在C++11标准中,就没有这种限制了,你可以使用花括号(即{})对任意类型的变量进行初始化,且不用是static类型。

//C++11在类定义处初始化成员变量
class A
{
    public:
    	bool        ma{true};
    	int			mb{2019};
    	std::string mc{"helloworld"}; 	
};
1
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;
}
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
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

1
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} });
1
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;
}
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
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.
1
2
3
4

通过上面两个例子希望读者可以理解**std::initializer_list<T>**的使用场景,**std::initializer_list<T>**除了构造函数还提供了三个成员函数,这和 stl 的其他容器的同名方法用法一样:

//返回列表中元素的个数
size_type size() const;
//返回第一个元素的指针
const T* begin() const;
//返回最后一个元素的下一个位置,代表结束
const T* end() const;
1
2
3
4
5
6
上次更新: 2025/04/01, 20:53:14
1.3 拥抱C++新变化(C++11/14/17新增的实用特性)
1.5 C++17注解标签(attributes)

← 1.3 拥抱C++新变化(C++11/14/17新增的实用特性) 1.5 C++17注解标签(attributes)→

最近更新
01
C++语言面试问题集锦 目录与说明
03-27
02
第四章 Lambda函数
03-27
03
第二章 关键字static及其不同用法
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式