C++11列表初始化 {}

发布于:2025-08-19 ⋅ 阅读:(13) ⋅ 点赞:(0)

C++11列表初始化 {}

C++11 引入的列表初始化(List Initialization) 是一种统一的初始化方式,支持使用 {} 对变量、对象、容器等进行初始化,解决了 C++98/03 中初始化语法混乱的问题。其核心特点是兼容多种场景、禁止窄化转换(Narrowing Conversion),并简化了初始化代码。

一、列表初始化({})的核心特性

  1. 统一语法:可用于几乎所有类型的初始化(基本类型、数组、结构体、类、容器等)。
  2. 禁止窄化转换:不允许可能丢失精度的类型转换(如 intfloat、大整数转小整数),编译时会报错。
  3. 支持初始化列表构造:类可通过 std::initializer_list 接收 {} 中的元素,实现批量初始化(如 STL 容器)。

二、C++11 列表初始化核心内容(对比 C++98/03 初始化方法)

1. 基本类型与数组初始化

C++98/03 方式

// 基本类型
int a = 10;
double b(3.14);

// 数组
int arr1[3] = {1, 2, 3}; // 只能在定义时初始化,且不能省略大小(部分场景除外)
int arr2[] = {4, 5, 6};  // 需依赖初始化元素数量推导大小

C++11 列表初始化

// 基本类型:更简洁,无需等号或括号
int a{10};         // 等价于 int a = 10;
double b{3.14};    // 等价于 double b(3.14);

// 数组:支持省略等号,且语法更统一
int arr1[3]{1, 2, 3}; 
int arr2[]{4, 5, 6};  // 同样支持推导大小

2. 结构体与类初始化

C++98/03 方式

C++98 中,有自定义构造函数的类无法用{}初始化,必须显式调用构造函数;

需依赖默认构造函数或带参构造函数,结构体可直接用 {} 但类需显式调用构 造函数。

struct Point {
    int x;
    int y;
};

class Student {
private:
    std::string name;
    int age;
public:
    Student(std::string n, int a) : name(n), age(a) {}
};

// 初始化
Point p1 = {1, 2};       // 结构体可直接用{}(聚合类型)
Student s1("Tom", 18);   // 类必须调用构造函数

C++11 列表初始化

C++11 允许用{}直接匹配构造函数。

结构体和类均可直接用 {} 初始化,类会自动匹配对应构造函数。

Point p1{1, 2};          // 无需等号,更简洁
Student s1{"Tom", 18};   // 类可直接用{}匹配构造函数,等价于 Student s1("Tom", 18);

3. 容器初始化(STL)

3.1 序列式容器(vectorlistdeque等)

序列式容器存储单一类型的元素,初始化时直接在{}中列出元素即可。

C++98方式(繁琐)

#include <vector>
#include <list>

// 初始化vector
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);

// 初始化list
std::list<std::string> lst;
lst.push_back("a");
lst.push_back("b");

C++11列表初始化(简洁)

#include <vector>
#include <list>

// 直接用{}传入元素,自动匹配initializer_list构造函数
std::vector<int> vec{1, 2, 3, 4}; // 初始化包含4个int的vector
std::list<std::string> lst{"a", "b", "c"}; // 初始化包含3个string的list
std::deque<double> dq{3.14, 2.718, 1.618}; // 双端队列
3.2 关联式容器(mapsetunordered_map等)

关联式容器存储键值对(map)或单一键(set),初始化时需按容器元素类型传入{}中的元素。

mapunordered_map(键值对)
键值对用{key, value}表示,多个键值对用逗号分隔。

#include <map>
#include <unordered_map>

// 初始化map(有序键值对)
std::map<std::string, int> score{
    {"Alice", 90}, 
    {"Bob", 85}, 
    {"Charlie", 95}
};

// 初始化unordered_map(无序键值对)
std::unordered_map<int, std::string> dict{
    {1, "one"}, 
    {2, "two"}, 
    {3, "three"}
};

setunordered_set(单一键)
直接传入键值即可。

#include <set>
#include <unordered_set>

// 初始化set(有序集合)
std::set<int> s{3, 1, 4, 1, 5}; // 自动去重并排序,结果为{1,3,4,5}

// 初始化unordered_set(无序集合)
std::unordered_set<std::string> us{"apple", "banana", "cherry"};
3.3 嵌套容器初始化

列表初始化支持嵌套,可直接初始化多维容器(如vector<vector<int>>)。

#include <vector>

// 初始化二维vector(矩阵)
std::vector<std::vector<int>> matrix{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 初始化vector of map
std::vector<std::map<int, std::string>> vec_of_map{
    {{1, "a"}, {2, "b"}},
    {{3, "c"}, {4, "d"}}
};
3.4 容器赋值操作

除了初始化,std::initializer_list还支持对已存在的容器进行赋值。

#include <vector>
#include <set>

std::vector<int> vec{1, 2, 3};
vec = {4, 5, 6}; // 赋值新的元素列表,覆盖原有内容

std::set<std::string> s;
s = {"x", "y", "z"}; // 赋值后s包含{"x", "y", "z"}

4. 禁止窄化转换

在 C++11 引入的列表初始化({})中,禁止窄化转换(Narrowing Conversion) 是一项重要的安全性特性。它的核心作用是:防止可能导致数据精度丢失或溢出的隐式类型转换,在编译阶段就拦截这类风险,避免运行时错误。

4.1 什么是“窄化转换”?

窄化转换指的是将一种类型的值转换为另一种类型时,可能导致数据信息丢失或溢出的转换。常见场景包括:

  1. 浮点数转整数:如 doubleint(丢失小数部分)。
  2. 大类型转小类型:如 long longint(可能超出范围)。
  3. 高精度转低精度:如 long doublefloat(丢失精度)。

例如:

int a = 3.14;      // 3.14 → 3(丢失小数部分,属于窄化转换)
short b = 100000;  // 100000 超出 short 最大范围(32767),溢出(窄化转换)
float c = 123456789.123;  // 超出 float 精度范围(丢失部分小数,窄化转换)
4.2 列表初始化列表初始化如何禁止窄化转换?

C++11 规定:使用 {} 进行列表初始化时,编译器必须检查并禁止所有窄化转换,若存在则直接编译报错。

与之对比:C++98 中使用 = 或括号的传统初始化允许窄化转换(仅可能触发警告,不会报错)。

4.3 具体示例:禁止窄化转换的场景
(1) 浮点数 → 整数(丢失小数)
// 传统初始化:允许(仅警告)
int a = 3.14;       // 编译通过(a=3)
int b(2.718);       // 编译通过(b=2)

// 列表初始化:禁止(编译报错)
int c{3.14};        // 错误:double → int 丢失小数(窄化转换)
int d{2.718};       // 错误:窄化转换
(2)大整数 → 小整数(可能溢出)
// 传统初始化:允许(可能溢出但不报错)
short s1 = 100000;  // 100000 超出 short 范围(32767),编译通过(结果不确定)
char c1 = 300;      // 300 超出 char 范围(-128~127),编译通过(溢出)

// 列表初始化:禁止(编译报错)
short s2{100000};   // 错误:整数超出 short 范围(窄化转换)
char c2{300};       // 错误:整数超出 char 范围(窄化转换)
(3)高精度浮点数 → 低精度浮点数(丢失精度)
// 传统初始化:允许(丢失精度但不报错)
float f1 = 123456789.123456789;  // float 精度有限,编译通过(值被截断)

// 列表初始化:禁止(编译报错)
float f2{123456789.123456789};   // 错误:long double → float 丢失精度(窄化转换)
(4)例外:安全的转换(非窄化)允许通过

列表初始化并非禁止所有转换,仅禁止可能丢失信息的转换。以下安全转换允许通过:

  • 整数 → 更大的整数(如 intlong long)。
  • 整数 → 浮点数(如 intdouble,浮点数精度足够容纳整数)。
  • 低精度浮点数 → 高精度浮点数(如 floatdouble)。

示例:

// 安全转换:允许通过
int a{100};
long long b{a};          // int → long long(范围更大,非窄化)
double c{100};           // int → double(精度足够,非窄化)
double d{3.14f};         // float → double(精度更高,非窄化)

5. std::initializer_list – 初始化列表构造函数

std::initializer_list 是 C++11 引入的一个轻量级模板类(定义在 <initializer_list> 头文件中),专门用于处理列表初始化(即用 {} 包裹的元素列表)。它为编译器提供了一种统一的方式来处理初始化列表,是 C++11 中列表初始化语法(如容器初始化、变量初始化)的底层实现基础。

5.1 核心功能与设计目的

std::initializer_list 的主要作用是:

  1. 封装初始化列表:将 {a, b, c} 这样的列表元素转换为一个临时的容器对象,供其他代码(如构造函数、函数参数)使用。
  2. 支持统一初始化:让自定义类型(如类、容器)能够像原生数组一样使用 {} 语法初始化。
  3. 简化代码:避免为不同数量的初始化参数编写多个重载函数/构造函数。
5.2 基本特性
  1. 模板类型std::initializer_list<T> 是模板类,其中 T 是列表中元素的类型(如 intstd::string)。
  2. 轻量级:内部仅存储两个指针(或一个指针+长度),指向列表元素的起始和结束位置,不负责内存管理(元素存储在栈上的临时区域)。
  3. 只读访问:元素是常量,不允许修改(std::initializer_list<T> 本质是 const T* 的封装)。
  4. 临时对象std::initializer_list 对象的生命周期与初始化列表相同,通常是表达式结束时销毁。
5.3 使用场景
(1) 作为函数参数

函数可以接收 std::initializer_list<T> 类型的参数,从而支持用 {} 列表传参。

#include <initializer_list>
#include <iostream>

// 接收initializer_list的函数
void print(const std::initializer_list<int>& list) {
    for (auto val : list) { // 遍历列表元素(只读)
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

int main() {
    print({1, 2, 3, 4}); // 直接用{}传参,自动转换为initializer_list
    print({10, 20});
    return 0;
}

输出:

1 2 3 4 
10 20 
(2) 作为类的构造函数参数

类可以定义接收 std::initializer_list<T> 的构造函数,从而支持用 {} 初始化对象(这也是 STL 容器支持列表初始化的原理)。

#include <initializer_list>
#include <vector>

class MyContainer {
private:
    std::vector<int> data;
public:
    // 接收initializer_list的构造函数
    MyContainer(std::initializer_list<int> list) {
        // 将列表元素添加到容器中
        for (auto val : list) {
            data.push_back(val);
        }
    }

    void print() {
        for (auto val : data) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyContainer c{1, 2, 3, 4}; // 用{}初始化,自动匹配initializer_list构造函数
    c.print(); // 输出:1 2 3 4 
    return 0;
}
(3) STL容器中的应用

所有 STL 容器(如 vectormapset 等)在 C++11 中都新增了接收 std::initializer_list 的构造函数,因此可以直接用 {} 初始化:

#include <vector>
#include <map>

int main() {
    // vector用initializer_list初始化
    std::vector<int> vec{1, 2, 3};

    // map用initializer_list初始化(元素是键值对)
    std::map<std::string, int> score{{"Alice", 90}, {"Bob", 85}};
    return 0;
}
(4)作为返回值

函数可以返回 std::initializer_list<T> 类型,允许用 {} 直接返回元素列表。

#include <initializer_list>
#include <iostream>

std::initializer_list<int> get_numbers() {
    return {1, 2, 3, 4}; // 返回initializer_list
}

int main() {
    for (auto num : get_numbers()) {
        std::cout << num << " "; // 输出:1 2 3 4 
    }
    return 0;
}
5.4关键注意事项
  1. 元素的生命周期
    std::initializer_list 中的元素存储在栈上的临时内存中,其生命周期与 std::initializer_list 对象一致。不能保存指向这些元素的指针或引用,否则会导致悬垂指针:

    #include <initializer_list>
    
    int main() {
        std::initializer_list<int> list = {1, 2, 3};
        const int* p = list.begin(); // 指向临时元素
        list = {4, 5, 6}; // 原临时元素已销毁,p变为悬垂指针
        return 0;
    }
    
  2. 元素是常量
    std::initializer_list<T> 中的元素是 const T 类型,不允许修改:

    #include <initializer_list>
    
    int main() {
        std::initializer_list<int> list = {1, 2, 3};
        // list[0] = 10; // 错误:元素是常量,不允许修改
        return 0;
    }
    
  3. 与其他构造函数的优先级
    当类同时有接收 std::initializer_list 的构造函数和其他构造函数时,列表初始化({})会优先匹配 initializer_list 构造函数

    #include <initializer_list>
    #include <iostream>
    
    class MyClass {
    public:
        MyClass(int a, int b) {
            std::cout << "调用(int, int)构造函数" << std::endl;
        }
    
        MyClass(std::initializer_list<int> list) {
            std::cout << "调用initializer_list构造函数" << std::endl;
        }
    };
    
    int main() {
        MyClass obj1(1, 2); // 调用(int, int)构造函数
        MyClass obj2{1, 2}; // 优先调用initializer_list构造函数
        return 0;
    }
    
    

网站公告

今日签到

点亮在社区的每一天
去签到