C语言中初始化列表和列表初始化如何改成?

摘要:深入理解 C++ 中初始化列表与列表初始化的区别:前者解决对象生命周期与内存模型问题,后者统一初始化语法并提供类型安全保障。
目录初始化列表(Member Initializer List)初始化 vs. 赋值为什么要用初始化列表?必须使用的场景(物理限制)初始化的顺序(内存布局决定)列表初始化(List Initialization)统一初始化语义底层机制:std::initializer_list安全特性:防止窄化转换(Narrowing Conversion)解决 “最令人头秃的解析”(Most Vexing Parse)两者的 “撞车”如何选择 {} 与 ()?{} 的写法直接列表初始化(Direct List Initialization)拷贝列表初始化(Copy List Initialization)直接列表初始化和拷贝列表初始化的选择explicit 关键字的影响真的应该所有情况都用 {} 吗?陷阱 1:std::vector 的构造陷阱 2:auto 类型推导的歧义{} 的特性:默认值初始化(Value Initialization) 本文首发于我的个人博客:Better Mistakes 版权声明:本文为原创文章,转载请附上原文出处链接及本声明。 由于技术迭代较快,文章内容可能随时更新(含勘误及补充)。为了确保您看到的是最新版本,并获得更好的代码阅读体验,请访问: 🍭 原文链接:https://bfmhno3.github.io/note/initialization-in-cpp/ 很多初学者容易混淆初始化列表(Member Initializer List)和列表初始化(List Initialization),因为它们的名字很像,但它们实际上解决的是完全不同的问题: 初始化列表:解决的是对象生命周期与内存模型的问题(“什么时候赋初值”) 列表初始化:解决的是类型系统的统一性与安全性的问题(“用什么语法赋初值”) 初始化列表(Member Initializer List) 它的形式出现在构造函数参数列表之后,函数体大括号之前,以 : 开头。 class A { int x; public: A(int val) : x(val) {} // 初始化列表 }; 初始化 vs. 赋值 在 C++ 的对象模型中,“初始化”(Initialization)和 “赋值”(Assignment)是两个截然不同的物理过程。 内存分配(Allocation):在栈上或堆上划出一块内存 初始化(Initialization):在这块内存上构建对象,使其成为一个合法的实例 赋值(Assignment):对象已经存在了,擦出旧值,填入新值。 构造函数的执行时间线: 进入构造函数之前:编译器必须确保所有成员变量都已经 “出生”(初始化完成) 进入构造函数体({...}):这已经是 “出生后” 的世界了,这里面写的代码都是 “赋值” 操作。 为什么要用初始化列表? 如果你不用初始化列表,而是写在函数体内: class Person { std::string name; public: // 写法 1:在函数体内赋值 Person(const std::string& n) { name = n; } }; 底层发生了什么? 隐式初始化:在进入 { 之前,编译器发现 name 还没有初始化,于是强行用 std::string 的默认构造函数。name 变成了一个空字符串 "" 赋值操作:进入 { 后,执行 name = n;。调用 std::string 的赋值运算符,把刚才那个空字符串的内容丢弃,拷贝 n 的内容 这就类似于,你先建了一个空房子,然后立即把它拆了重建成你想要的样子,这就是双倍的开销。 正确的写法(初始化列表): // 写法 2:使用初始化列表 Person(const std::string& n) : name(n) {} 底层发生了什么? 直接构造:直接调用 std::string 的拷贝构造函数,用 n 来 “生出” name 函数体为空,无操作。收益:省去了一次默认构造和一次赋值操作。 必须使用的场景(物理限制) 有些东西必须 “出生时” 就确定,生出来后再改就晚了。
阅读全文