1. c++11之前的初始化

int a = 5;        // 拷贝初始化
int b(5);         // 直接初始化
int arr[3] = {1, 2, 3};  // 数组初始化
Object obj1;      // 默认构造
Object obj2(obj1); // 拷贝构造
  • 语法不统一,混乱

2. 列表初始化

  • 统一类型,无论什么类型,都可以用 {} 搞定:

统一风格

int a { 5 };              // 直接初始化
int b = { 5 };            // 拷贝初始化
int arr[3] {1, 2, 3};     // 数组初始化
Object obj1 { };          // 默认初始化
Object obj2 { obj1 };     // 拷贝构造
std::vector<int> v {1, 2, 3}; // 容器初始化

禁止窄化转换

  • 传统的初始化允许隐式类型转换,可能导致数据精度丢失
int a = 3.14;   // OK,但 a = 3,数据丢失
  • 列表初始化会在编译期检查并禁止窄化转换:
int a { 3.14 };        // 错误!窄化转换被禁止
int arr[] {1, 2, 3.3}; // 错误!3.3 会导致窄化

简化容器初始化

  • 在C++11之前,初始化一个 vector 需要两步:
int arr[5] = {1, 2, 3, 4, 5};
std::vector<int> v(arr, arr + 5);
  • 有了列表初始化,一行搞定:
std::vector<int> v {1, 2, 3, 4, 5};

3. 初始化语法细节

  • 直接列表初始化:
T object { arg1, arg2, ... };    // 具名变量
T { arg1, arg2, ... };            // 无名临时量
new T { arg1, arg2, ... };        // 动态内存
return { arg1, arg2, ... };       // 函数返回值
  • 复制列表初始化
T object = { arg1, arg2, ... };   // 拷贝形式的列表初始化
function({ arg1, arg2, ... });    // 函数参数
object = { arg1, arg2, ... };     // 赋值

4.聚合体

  • 聚合体的定义(c++17及之前)
    一个类(或结构体)如果满足以下所有条件,就是聚合类型:
    (1)没有用户声明的构造函数(包括默认构造函数)
    (2)没有私有或受保护的非静态数据成员(所有非静态成员都是 public)
    (3)没有基类(不继承任何类)
    (4)没有虚函数
    (5)非静态数据成员没有类内初始化器(即不能写成 int x = 5; 或 int x{5};)

  • 聚合类型

struct Point {
    int x;
    int y;
};  // 满足所有条件,是聚合体

struct Person {
    std::string name;
    int age;
};  // 也是聚合体
  • 非聚合类型
struct NotAggregate1 {
    virtual void f() {}  // 有虚函数
};

struct NotAggregate2 {
private:
    int x;               //  有私有非静态成员
};

struct NotAggregate3 {
    NotAggregate3(int) {} //  有用户声明的构造函数
};

struct NotAggregate4 {
    int x = 10;          //  有类内初始化器
};

5. 聚合体初始化

聚合体最大的特点就是可以用 {} 按成员声明顺序进行初始化

struct Point {
    int x;
    int y;
};

Point p1 {1, 2};        // p1.x = 1, p1.y = 2
Point p2 = {3, 4};      // 同样有效

6. 非聚合体与 std::initializer_list

对于非聚合类型(比如有自定义构造函数的类),列表初始化会尝试匹配构造函数:

  • 匹配普通构造函数
class Foo {
public:
    Foo(int a, double b) : x(a), y(b) {}
private:
    int x;
    double y;
};

Foo f {42, 3.14};  // 调用 Foo(int, double)
  • 匹配 std::initializer_list 构造函数
    如果想要一个类支持任意长度的列表初始化,可以定义一个接受 std::initializer_list 的构造函数:
#include <initializer_list>
#include <iostream>

class MyVector {
public:
    MyVector(std::initializer_list<int> list) {
        for (auto it = list.begin(); it != list.end(); ++it) {
            std::cout << *it << " ";
        }
        std::cout << std::endl;
    }
};

MyVector v1 {1, 2, 3};        // 输出: 1 2 3
MyVector v2 {1, 2, 3, 4, 5};  // 输出: 1 2 3 4 5
  • std::initializer_list是一个轻量级代理对象,提供对 const T 类型数组的访问:
    它不拥有底层数据,只是指向编译器生成的临时数组
    内部通常实现为一对指针(或指针+长度)
    复制 initializer_list 不会复制底层数组
    元素是只读的(const)
    只能存储同类型的元素
// initializer_list 的典型用法
void print(std::initializer_list<int> list) {
    for (int x : list) {
        std::cout << x << " ";
    }
}
print({1, 2, 3, 4});  // 输出: 1 2 3 4
  • 当一个类同时有匹配的普通构造函数和 initializer_list 构造函数时,initializer_list 版本优先:
class Foo {
public:
    Foo(int a, int b) { std::cout << "普通构造\n"; }
    Foo(std::initializer_list<int>) { std::cout << "initializer_list构造\n"; }
};

Foo f1 {1, 2};    // 输出: initializer_list构造(而不是普通构造!)
Foo f2 (1, 2);    // 输出: 普通构造(圆括号调用普通构造)