乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

列表初始化与类内初始化器

定义与作用

列表初始化(C++11)统一使用花括号 {} 进行对象初始化,语法简洁且天然防止窄化转换。它替代了 C++98 中混乱的初始化方式:

// C++98 三种初始化方式,规则各异
int a = 10;       // 拷贝初始化
int b(20);        // 直接初始化
int c[] = {1,2};  // 聚合初始化(仅对数组/聚合类)

// C++11 统一使用 {}——一种写法适用所有场景
int a{10};
std::vector<int> v{1, 2, 3, 4};
MyClass obj{1, "hello"};

类内初始化器(NSDMI,C++11)允许在类定义中直接为成员变量提供默认值:

class Student {
    std::string name_ = "未命名";  // 类内初始化器
    int age_{18};                  // {} 形式也可
    double gpa_ = 0.0;
};

核心原理

统一初始化的优势

std::initializer_list 与重载决议

当同时存在 initializer_list 构造函数和其他构造函数时,{} 语法优先匹配 initializer_list 版本:

std::vector<int> v1(10, 20);  // 调用 vector(size_t, int):10 个 20
std::vector<int> v2{10, 20};  // 调用 vector(initializer_list):{10, 20}

完整示例

示例一:统一初始化全面演示

场景说明:展示 {} 语法在各种类型(基本类型、数组、STL 容器、自定义类)中的一致用法。

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <array>

class Point {
public:
    Point(double x, double y) : x_(x), y_(y) {}
    void print() const {
        std::cout << "Point(" << x_ << ", " << y_ << ")" << std::endl;
    }
private:
    double x_, y_;
};

class Config {
public:
    Config(std::initializer_list<std::pair<const std::string, std::string>> items) {
        for (const auto& item : items) {
            settings_.insert(item);
        }
    }
    void print() const {
        for (const auto& [k, v] : settings_) {
            std::cout << "  " << k << " = " << v << std::endl;
        }
    }
private:
    std::map<std::string, std::string> settings_;
};

int main() {
    std::cout << "===== 基本类型初始化 =====" << std::endl;
    int a{42};
    double b{3.14};
    char c{'A'};
    bool flag{true};

    std::cout << "a = " << a << ", b = " << b
              << ", c = " << c << ", flag = " << flag << std::endl;

    // 窄化转换被禁止(以下编译错误)
    // int x{3.14};       // double → int 窄化
    // char ch{9999};     // 溢出
    // unsigned u{-1};    // 负数→无符号

    std::cout << "\n===== 数组与容器 =====" << std::endl;
    int arr[]{1, 2, 3, 4, 5};
    std::vector<int> vec{10, 20, 30};
    std::array<int, 3> fixed_arr{100, 200, 300};

    std::cout << "数组: ";
    for (auto v : arr) std::cout << v << " ";
    std::cout << std::endl;

    std::cout << "vector: ";
    for (auto v : vec) std::cout << v << " ";
    std::cout << std::endl;

    std::cout << "\n===== 自定义类型 =====" << std::endl;
    Point p1{3.0, 4.0};
    p1.print();

    Point p2 = {1.0, 2.0};
    p2.print();

    Config config{
        {"host", "localhost"},
        {"port", "3306"},
        {"user", "admin"}
    };
    std::cout << "配置项:" << std::endl;
    config.print();

    std::cout << "\n===== initializer_list 优先 =====" << std::endl;
    std::vector<int> v1(5, 42);   // 5 个 42
    std::vector<int> v2{5, 42};   // {5, 42}

    std::cout << "v1(5,42): ";
    for (auto v : v1) std::cout << v << " ";
    std::cout << " (5 个元素)" << std::endl;

    std::cout << "v2{5,42}: ";
    for (auto v : v2) std::cout << v << " ";
    std::cout << " (2 个元素)" << std::endl;

    return 0;
}

预期输出:

===== 基本类型初始化 =====
a = 42, b = 3.14, c = A, flag = true

===== 数组与容器 =====
数组: 1 2 3 4 5 
vector: 10 20 30 

===== 自定义类型 =====
Point(3, 4)
Point(1, 2)
配置项:
  host = localhost
  port = 3306
  user = admin

===== initializer_list 优先 =====
v1(5,42): 42 42 42 42 42  (5 个元素)
v2{5,42}: 5 42  (2 个元素)

逐段分析:

  • {} 适用于所有类型——基本类型、数组、STL 容器、自定义类——写法一致
  • int x{3.14} 编译错误——{} 强制禁止窄化转换,= 和 () 不检查
  • Point{3.0, 4.0} 匹配双参数构造函数——{} 不仅限于 initializer_list
  • Config{...} 通过 std::initializer_list 接收键值对列表——简洁的集合初始化语法
  • vector<int>(5, 42) 和 vector<int>{5, 42} 的区别是面试高频考点——{} 优先匹配 initializer_list 版本

示例二:类内初始化器与委托构造的配合

场景说明:展示 NSDMI 如何消除构造函数中的初始化样板代码。

#include <iostream>
#include <string>

// 无 NSDMI:所有构造函数都要写初始化(C++98 风格)
class Student98 {
public:
    Student98() : name_(""), id_(0), gpa_(0.0) {}
    Student98(std::string name) : name_(name), id_(0), gpa_(0.0) {}
    Student98(std::string name, int id) : name_(name), id_(id), gpa_(0.0) {}
    // ... 每个构造函数都重复初始化

    void print() const {
        std::cout << name_ << " | " << id_ << " | " << gpa_ << std::endl;
    }

private:
    std::string name_;
    int id_;
    double gpa_;
};

// 有 NSDMI:构造函数只关心特殊值(C++11 风格)
class Student11 {
public:
    Student11() = default;  // 使用所有默认值
    explicit Student11(std::string name) : name_(std::move(name)) {}
    Student11(std::string name, int id, double gpa)
        : name_(std::move(name)), id_(id), gpa_(gpa) {}
    Student11(std::string name, int id) : name_(std::move(name)), id_(id) {}

    void print() const {
        std::cout << name_ << " | " << id_ << " | " << gpa_ << std::endl;
    }

private:
    std::string name_ = "未命名";  // NSDMI
    int id_ = 0;                   // NSDMI
    double gpa_ = 0.0;             // NSDMI
};

int main() {
    std::cout << "===== C++98 风格(每个构造函数重复初始化) =====" << std::endl;
    Student98 s1;
    Student98 s2("张三");
    s1.print();
    s2.print();

    std::cout << "\n===== C++11 风格(NSDMI 消除重复) =====" << std::endl;
    Student11 s3;                      // 全部默认值
    Student11 s4("李四");              // name 覆盖,其余默认
    Student11 s5("王五", 2024001);      // name, id 覆盖,gpa 默认
    Student11 s6("赵六", 2024002, 3.9); // 全部指定

    s3.print();
    s4.print();
    s5.print();
    s6.print();

    return 0;
}

预期输出:

===== C++98 风格(每个构造函数重复初始化) =====
 | 0 | 0
张三 | 0 | 0

===== C++11 风格(NSDMI 消除重复) =====
未命名 | 0 | 0
李四 | 0 | 0
王五 | 2024001 | 0
赵六 | 2024002 | 3.9

逐段分析:

  • C++98 风格中每个构造函数都要在初始化列表里列出所有成员——重复且易遗漏
  • C++11 NSDMI 在成员声明处直接写默认值 int id_ = 0;——只写一次
  • 如果构造函数显式初始化了某成员,NSDMI 的默认值被覆盖;否则保留
  • Student11() = default; 配合 NSDMI 即可获得正确的默认构造——无需手写

易错场景与面试考点

易错场景

场景错误表现正确做法
std::vector<int>{5, 42} vs std::vector<int>(5, 42)调用错误构造函数注意 {} 优先 initializer_list
窄化转换在 () 中静默发生数据截断用 {} 初始化(编译器报错)
"最令人烦恼的解析"对象声明被解析为函数声明用 {} 代替 ()
NSDMI 不能使用 () 语法int x(0); 编译错误NSDMI 用 = 或 {}
auto x{1} 的类型(C++17 前)initializer_list<int> vs intC++17 起 auto x{1} 推导为 int

常见面试问题

  1. () 初始化和 {} 初始化的区别?——{} 禁止窄化转换、不会与函数声明歧义(最令人烦恼的解析)、优先匹配 initializer_list 构造。推荐默认使用 {}。

  2. 什么是 NSDMI?解决了什么问题?——Non-static Data Member Initializers。在类内直接给成员变量默认值,减少构造函数中的重复初始化代码。

  3. std::initializer_list 的底层实现和使用场景?——编译器从 {a, b, c} 构造一个轻量级数组,传递给 initializer_list 对象。用于容器的列表初始化和变参构造。

  4. 为什么 auto x{1} 的类型在不同标准中不同?——C++11/14 推导为 std::initializer_list<int>;C++17 起推导为 int。这是 C++17 的重要破坏性变更之一。

  5. std::vector<int> v{5} 和 std::vector<int> v(5) 分别是什么?——{5} 是含一个元素 5 的列表;(5) 是 5 个默认初始化的元素。当 T 是 int 时两者含义完全不同。

小结

  • 默认使用 {} 统一初始化:防窄化、避免歧义、适用于所有类型
  • NSDMI 在类内直接为成员赋默认值,消除构造函数中的重复代码
  • {} 优先匹配 initializer_list 构造函数——注意与 () 语义的差异
  • 窄化转换用 {} 会编译错误,用 () 会静默截断——{} 更安全
  • C++17 起 auto x{1} 推导为 int 而非 initializer_list
上一页
移动构造函数与移动赋值
下一页
静态成员与嵌套类