C++11 统一列表初始化+std::initializer_list

在 C++98 时代,花括号 {} 初始化的使用场景非常受限,只能用于数组、简单结构体,自定义对象、容器初始化写法繁琐又不统一。

C++11 推出的「统一列表初始化 」 和 std::initializer_list彻底解决了这个问题,让所有类型的初始化语法高度统一,代码更简洁、更易读。 本篇博客会从基础用法到原理,再到实战模拟实现,带你彻底掌握这个 C++11 高频特性

一、C++98 的 {} 初始化:受限的用法

在 C++98 中,{} 初始化只能用于内置数组无构造函数的简单结构体,自定义类、动态数组、STL 容器都无法使用,语法非常不统一。

示例代码:

cpp 复制代码
struct Point
{
    int _x;
    int _y;
};

int main()
{
    // 支持:数组初始化
    int array1[] = { 1, 2, 3, 4, 5 };
    int array2[5] = { 0 };

    // 支持:简单结构体初始化
    Point p = { 1, 2 };

    // 不支持:自定义类、容器、new动态数组
    // Date d{2024, 1, 1};  报错
    // vector<int> v{1,2,3}; 报错
    return 0;
}

痛点总结

  1. 初始化语法不统一,数组 / 结构体 / 类各写各的
  2. 自定义类型、STL 容器无法使用{}初始化
  3. 代码冗余,可读性差

二、C++11 统一列表初始化:{} 全能使用

C++11 直接扩大了{}的使用范围,所有类型(内置类型、自定义类型、动态数组、对象)都可以用{}初始化 ,并且可以省略=,语法完全统一。

1. 内置类型 & 数组

cpp 复制代码
int main()
{
    // 内置变量
    int x1 = 1;
    int x2{ 2 };   // C++11 写法,省略=
    int x3 = { 3 };  // 带=也支持

    // 静态数组
    int array1[]{ 1, 2, 3, 4, 5 };
    int array2[5]{ 0 };

    // 动态数组 new 表达式(C++98不支持)
    int* pa = new int[4] { 1, 2, 3, 4 };
    return 0;
}

2. 自定义结构体 / 类:调用构造函数

只要类有对应的构造函数,直接用{}传参即可,编译器会自动匹配构造函数:

cpp 复制代码
class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        , _month(month)
        , _day(day)
    {
        cout << "Date构造函数调用" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2022, 1, 1);  // C++98 老式写法
    Date d2{ 2022, 1, 2 }; // C++11 列表初始化
    Date d3 = { 2022, 1, 3 }; // 带=也支持
    return 0;
}

✅ 效果:三种写法都能正常调用构造函数,语法更统一、更直观。


三、std::initializer_list:{} 初始化的底层支撑

{1,2,3,4} 这种花括号列表,在 C++11 中会被自动推导为 std::initializer_list 类型,它是一个轻量级的容器,用来存储临时列表数据。

1. 查看类型

cpp 复制代码
int main()
{
    auto il = { 10, 20, 30 };
    cout << typeid(il).name() << endl; // 输出:class std::initializer_list<int>
    return 0;
}

2. 核心特性

  • 是一个轻量级只读容器,只存储数据的引用 / 指针,不深拷贝
  • 支持begin()end()size()接口
  • 可使用范围 for 遍历
  • 主要用途:作为构造函数 / 赋值运算符重载的参数 ,实现{}初始化和赋值

3. STL 容器的天然支持

C++11 给所有 STL 容器(vector、list、map、set 等)都增加了:

  1. initializer_list 构造函数
  2. initializer_list 赋值运算符重载

所以我们可以直接这样写:

cpp 复制代码
#include <vector>
#include <list>
#include <map>
using namespace std;

int main()
{
    // 容器列表初始化
    vector<int> v = { 1,2,3,4 };
    list<int> lt = { 1,2 };
    map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };

    // 容器列表赋值
    v = {10, 20, 30};
    return 0;
}

这就是 C++11 最常用、最便捷的容器初始化方式。


四、实战:模拟实现 vector,支持 {} 初始化和赋值

我们自己模拟实现的 vector,只要添加两个函数:

  1. vector(initializer_list<T> l):列表初始化构造

  2. operator=(initializer_list<T> l):列表赋值

就能和 STL vector 一样使用{}初始化和赋值,完整代码如下:

cpp 复制代码
#include <iostream>
#include <initializer_list> // 必须包含头文件
#include <algorithm>
using namespace std;

namespace biter
{
    template<class T>
    class vector 
    {
    public:
        typedef T* iterator;

        // 1. 无参构造
        vector()
            :_start(nullptr)
            , _finish(nullptr)
            , _endofstorage(nullptr)
        {}

        // 2. 列表初始化构造函数(核心)
        vector(initializer_list<T> l)
        {
            // 开辟空间
            _start = new T[l.size()];
            _finish = _start + l.size();
            _endofstorage = _start + l.size();

            // 遍历initializer_list,拷贝数据
            iterator vit = _start;
            // 注意:必须加typename,否则编译器无法识别iterator是类型
            typename initializer_list<T>::iterator lit = l.begin();
            while (lit != l.end())
            {
                *vit++ = *lit++;
            }

            // 简化写法:范围for
            // for (auto e : l)
            //     *vit++ = e;
        }

        // 3. 列表赋值运算符重载(核心)
        vector<T>& operator=(initializer_list<T> l) 
        {
            // 复用构造函数创建临时对象,交换指针
            vector<T> tmp(l);
            std::swap(_start, tmp._start);
            std::swap(_finish, tmp._finish);
            std::swap(_endofstorage, tmp._endofstorage);
            return *this;
        }

        // 打印函数,方便测试
        void print() const
        {
            for (auto it = _start; it != _finish; ++it)
            {
                cout << *it << " ";
            }
            cout << endl;
        }

        // 析构函数
        ~vector()
        {
            delete[] _start;
            _start = _finish = _endofstorage = nullptr;
        }

    private:
        iterator _start;       // 起始位置
        iterator _finish;      // 有效数据结尾
        iterator _endofstorage;// 容量结尾
    };
}

// 测试代码
int main()
{
    // {} 初始化
    biter::vector<int> v1 = { 1,2,3,4,5 };
    cout << "v1:";
    v1.print();

    // {} 直接初始化
    biter::vector<int> v2{ 10,20,30 };
    cout << "v2:";
    v2.print();

    // {} 赋值
    v1 = { 100,200,300 };
    cout << "v1赋值后:";
    v1.print();

    return 0;
}

关键知识点说明:

必须包含 <initializer_list> 头文件

遍历 initializer_list 时,迭代器前必须加 typename,因为它是依赖类型

赋值重载利用拷贝交换,代码简洁且异常安全

支持两种写法:vector v{...} 和 vector v={...}


五、统一列表初始化的优势

语法统一:所有类型(内置 / 自定义 / 容器)都能用{}初始化

代码简洁:一行完成容器初始化,告别循环 push_back

安全性更高:{}初始化会严格检查类型,禁止隐式缩窄转换

扩展性强:自定义类型只需实现initializer_list接口,即可支持统一初始化

相关推荐
ShineWinsu3 小时前
爬虫对抗:ZLibrary反爬机制实战分析技术文章大纲
c++
telllong3 小时前
BeeWare:Python原生移动应用开发
开发语言·python
海海不瞌睡(捏捏王子)3 小时前
C#知识点概要
java·开发语言·1024程序员节
aini_lovee3 小时前
C# 实现邮件发送源码(支持附件)
开发语言·javascript·c#
_MyFavorite_4 小时前
JAVA重点基础、进阶知识及易错点总结(10)Map 接口(HashMap、LinkedHashMap、TreeMap)
java·开发语言
charlie1145141914 小时前
通用GUI编程技术——Win32 原生编程实战(十六)——Visual Studio 资源编辑器使用指南
开发语言·c++·ide·学习·gui·visual studio·win32
DpHard4 小时前
现代 C++ 中 push 接口为何提供 const T& 与 T&& 两个重载
c++
wheelmouse77885 小时前
网络排查基础与实战指南:Ping 与 Telnet
开发语言·网络·php
敲代码的嘎仔5 小时前
Java后端开发——真实面试汇总(持续更新)
java·开发语言·程序人生·面试·职场和发展·八股