std::initialzer_list 与花括号{}数据列表


author: hjjdebug

date: 2025年 05月 22日 星期四 15:50:23 CST

descrip: std::initialzer_list 与花括号{}数据列表


文章目录

  • 1.{数值列表}是什么?
    • [1.1 数组初始化 时 , 称为数组初始化列表](#1.1 数组初始化 时 , 称为数组初始化列表)
    • [1.2. 当用于容器时, 称为容器初始化列表](#1.2. 当用于容器时, 称为容器初始化列表)
    • [1.3. 对于结构体或类,{1,2,3,4,5} 可用于初始化成员变量](#1.3. 对于结构体或类,{1,2,3,4,5} 可用于初始化成员变量)
  • [2. std::initializer_list 是什么?](#2. std::initializer_list 是什么?)
  • [3. 实现自己的initializer_list](#3. 实现自己的initializer_list)
    • [3.1 可变参数包](#3.1 可变参数包)
    • [3.2 实现自己的initializer](#3.2 实现自己的initializer)
  • [4.std::intializer_list 使用举例](#4.std::intializer_list 使用举例)
  • [5. 小结](#5. 小结)

我对如下语句发生了疑惑
std::initializer_list<int> data={1,2,3,4,5};
这到底是什么意思? 为什么它能用这样的语法?

1.{数值列表}是什么?

{1,2,3,4,5} 在c++中叫什么? 为什么用{}

1.1 数组初始化 时 , 称为数组初始化列表

例:

int arr[] = {1,2,3,4,5};

1.2. 当用于容器时, 称为容器初始化列表

std::vector<int> vec = {1,2,3,4,5}; // 初始化 vector

std::set<int> s = {1,2,3,4,5}; // 初始化 set

我也曾经常使用vector, 我把这种写法当成一种很自然的写法,

认为它天生就等价于把数值直接付给vector对象中的元素. 其实应该是想得太简单了.

1.3. 对于结构体或类,{1,2,3,4,5} 可用于初始化成员变量

struct Point { int x; int y; int z; };

Point p = {1, 2, 3}; // c语言方式结构体成员初始化, 顺序填充成员.

所以{1,2,3,4,5}没有统一标准, 与上下文有关, 还是叫花括号列表吧, 编译器没有对它规范约束.

2. std::initializer_list 是什么?

std::initializer_list是C++11标准库中的一个类模板

std::initializer_list本质上是一个轻量级的容器,用于存储相同类型的一组值,

并且这些值在其生命周期内是只读的。它通过存储一个指向常量数组的指针和元素个数来实现,

允许用户遍历其中的元素, 由于内部数据是const的,因此它仅能读取数据,不能修改其中的值

看了定义我还是不懂,它为什么能支持初始化列表?

std::initializer_list<int> data={1,2,3,4,5}; 为什么能够把数据列表放到对象中.

要想搞清楚它的工作原理, 莫若自己实现一个 initializer_list!

3. 实现自己的initializer_list

先认识一下可变参数包

3.1 可变参数包

可变参数包, 从函数调用的角度看, 就是有不定个数的参数,0个或者多个.

c++给我们想好了, 这可以用funcname(type... args) 来表达. type...就是任意个参数

由于类型也可变, 没关系, c++有类模板,支持各种类型, 类型可变,那也加个...表示可变.

即型如 template<typename... T_ARGS>

把2者连起来, 如下形式, 表示可变类型,可变参数的统一形式

template<typename... T_Args> void func(T_Args... args)

这就是可变参数模板. c++11 开始支持

其中, typename... 声明模板支持0个或多个类型参数

其中,T_Args... 表示函数可接受任意数量的参数,这些参数的类型由typename 指定

sizeof...(T_Args)可获取参数数量

args, 可变参数包名称,可以被展开.

举例:

cpp 复制代码
#include <iostream>

template<typename... Args>
void printAll(Args... args) {
//    (std::cout << ... << args ) << std::endl; //左折叠表达式也可以
    ((std::cout << args ), ...) << std::endl; //逗号加右折叠表达式也可以
}

//,表达式用于连接多个操作
//右折叠表达式 ... 在参数包的右边, 右折叠从右向左结合,不过本例没有结合性
template<typename T, typename... Args>
void printWithComma(T first, Args... args) {
    std::cout << first;
    ((std::cout << ", " << args), ...); //逗号加右折叠表达式
    std::cout << std::endl;
}
int main() {
    printWithComma(1, 2.5, "hello"); // 输出:1,2.5,hello
    printAll("A", "B", "C");   // 输出:ABC
    return 0;
}

执行:

$ ./temp

1,2.5,hello

ABC

说明:

(std::cout << ... << args) 是C++17引入的折叠表达式(Fold Expression)语法

... ,参数包展开符, 必需在折叠表达式中使用才有效

args,可变参数包, 在折叠式中代表一个参数.

3.2 实现自己的initializer

实现代码:

cpp 复制代码
#include <stdio.h>
#include <vector>

template<typename T>
class MyInitializer 
{
	std::vector<T> data_;
	public:
	template<typename... T_Args>
		MyInitializer(T_Args&&... args) 
		{
			data_.reserve(sizeof...(T_Args)); //调整vector 容量
			//emplace安置 emplace_back尾部安置比push_back效率更高,免copy
			(data_.emplace_back(std::forward<T_Args>(args)), ...); //折叠表达式,...表示展开参数包
		}

	size_t size() const { return data_.size(); }
	auto begin() const { return data_.begin(); }
	auto end() const { return data_.end(); }
};

int main()
{
	MyInitializer<int> initList{1, 2, 3};
	for(auto it=initList.begin(); it!=initList.end();it++)
	{
		printf("%d\n",*it);
	}
	return 0;
}

执行结果:

$ ./temp2

1

2

3

为什么可以接受 {1,2,3} 初始化列表?

因为c++11 始引入了可变参数包语法支持 template<typename... T_Args>,

可支持任意类型,可变参数0个或多个的语法. 能够逐个取到每个参数.

类对象初始化中的{1,2,3} 认为是3个整形数

函数调用中的(1,2.5,"hello")认为是1个整数,1个浮点数,1个字符串的列表数据.

看起来{},()所起的作用还是有点混乱, 不如逗号,是数据的分割意义很明确.

初始化器为什么要求初始化列表是**"一种"数据类型? 而不能是 "多种"**数据类型.

因为它的实现使用了数据容器,类似于vector, 数据容器要求数据是一种类型.

std::initializer_list<int> data={1,2,3,4,5}; 语句解释.

{1,2,3,4,5}是一个花括号列表数据,它会被隐士转换为一个initializer_list 的无名对象,

对象的构建方式当然是它的构造函数, 其构造函数支持列表数据初始化.

然后通过赋值构造函数把无名对象赋值给对象 data

有了这些基础,就可以使用std::initializer_list 标准库模板函数了.

下面给个测试例,体会一下c语言的做法传递数组,与c++的做法传递初始化列表对象(initializer_list obj)的差别.

4.std::intializer_list 使用举例

cpp 复制代码
#include <initializer_list>
#include <vector>
#include <iostream>

//定义一个int容器, 用初始化列表接受初始化数据
class IntContainer 
{
public:
	//构造函数中使用initializer_list对象做参数
	//vector 支持 list对象初始化
	IntContainer(std::initializer_list<int> list_obj) : m_data(list_obj){}
	void print() const 
	{
		for (auto elem : m_data) 
		{
			std::cout << elem << ' ';
		}
		std::cout << std::endl;
	}
private:
	std::vector<int> m_data;
};

//函数中使用 
int sum_obj(std::initializer_list<int> list_obj)  //对象自带大小.
{
	int total = 0;
	for (auto& num : list_obj)   // 传递对象很安全,对象甚至不用大小,直接枚举.
	{
		total += num;
	}
	return total;
}

void printArr(int *arr, int size) //传递数组不安全,size出错就出错.
{
	for(int i=0;i<size;i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");
}

int sum_c(int *arr, int size) 
{
	int total = 0;
	for (int i=0;i<size;i++)  //size 很危险,万一传错参数,arr[i]要出错!
	{
		total += arr[i];
	}
	return total;
}
int main() {
	std::initializer_list list_obj={1,2,3,4,5}; //会发生隐含转换,从数值列表到无名list对象,然后再调用list的赋值构造
	IntContainer container_obj = list_obj;  //先创建无名container对象,再调用默认的赋值构造函数(浅copy构造函数)
//	IntContainer container_obj = {1, 2, 3, 4, 5}; //一步也可以,过程是数值列表->无名list对象->无名container对象->container_obj对象.
	container_obj.print(); // 输出:1 2 3 4 5
	int n = sum_obj({1,2,3,4,5}); //隐含转换,从数值列表到无名list对象,传递给函数参数,传递的是对象,安全!
	printf("sum_obj:%d\n",n);
	int arr[] = { 1,2,3,4,5};   // c语言的做法, 用数组. 但元素的个数就是一个风险因素,传错就有麻烦!
#define SIZE sizeof(arr)/sizeof(int)
	printArr(arr,SIZE);  //c函数传递SIZE 不安全,万一传错就有风险!
	n=sum_c(arr, SIZE);  //c函数传递SIZE 不安全,万一传错就有风险!
	printf("sum_c:%d\n",n);
	return 0;
}

执行结果:

$ ./tt

1 2 3 4 5

sum_obj:15

1 2 3 4 5

sum_c:15

5. 小结

总之:

用对象传递安全,因为对象自带大小. 用数组传递不安全.用数组还要传递大小,有出错风险.

相关推荐
C++chaofan11 分钟前
P2089 烤鸡
数据结构·c++·算法
ergevv16 分钟前
std::thread的说明与示例
c++·thread
龙湾开发1 小时前
C++ vscode配置c++开发环境
开发语言·c++·笔记·vscode·学习
爱学习的小邓同学1 小时前
C++ --- string
c++·笔记
大坏波1 小时前
C/C++内存管理
java·c语言·c++
小学生的信奥之路2 小时前
力扣509题:斐波那契数列的解法与代码注释
c++·算法·leetcode·动态规划·斐波那契数列
无影无踪的青蛙2 小时前
[C++]洛谷B3626 跳跃机器人(题干 + 详细讲解, BFS练习题)
开发语言·c++·算法·bfs·广度优先
kyle~2 小时前
C/C++---隐式显式转换
c语言·开发语言·c++
四谷夕雨3 小时前
C++八股 —— 手撕定时器
开发语言·c++
四谷夕雨4 小时前
C++八股 —— 手撕shared_ptr
开发语言·c++