【c++进阶】C++11新特性:一切皆可{}初始化

关注我,学习c++不迷路:

个人主页:爱装代码的小瓶子

专栏如下:

  1. c++学习
  2. Linux学习

后续会更新更多有趣的小知识,关注我带你遨游知识世界

期待你的关注。


文章目录

  • [1. c++11的介绍:](#1. c++11的介绍:)
  • [2. ``{}``初始化:](#2. {}初始化:)
    • [2-1: 避免歧义和窄化转换问题:](#2-1: 避免歧义和窄化转换问题:)
    • [2-2 统一的样式进行初始化:](#2-2 统一的样式进行初始化:)
    • [2-3 对比两种初始化:](#2-3 对比两种初始化:)
    • [2-4 陷阱:](#2-4 陷阱:)
  • [3. 为什么要做到统一初始化?](#3. 为什么要做到统一初始化?)
    • [1. **降低学习成本**](#1. 降低学习成本)
    • [2. **提高代码可读性和一致性**](#2. 提高代码可读性和一致性)
    • [3. **与现代C++理念一致**](#3. 与现代C++理念一致)
    • 总结

1. c++11的介绍:

C++11是C++历史上一次里程碑式的更新,它为这门古老而强大的语言注入了新的活力。在C++11之前,我们习惯了繁琐的构造函数调用、复杂的初始化语法,以及在传递临时对象时的性能损耗。但C++11的到来,彻底改变了这一切。

想象一下,你可以用统一的{}语法初始化任何类型------从内置类型到自定义类,从数组到容器,代码变得更加简洁优雅。更令人兴奋的是,C++11引入了右值引用和移动语义,让编译器能够"偷走"临时对象的资源,避免不必要的拷贝,从而大幅提升程序性能。

本文将带你深入探索C++11的这两个核心特性:首先是列表初始化(统一初始化),它让C++的初始化语法"大一统";然后是右值引用与移动语义,这是C++11性能优化的利器。无论你是C++新手还是资深开发者,这些特性都将让你的代码更现代、更高效。


2. {}初始化:

在c++11没有出来之前,我们初始化无法做到统一,而前复杂,还很麻烦:

我们还发现,我们还可以这样初始化:

最后一个 a3 这个变量明显是错误的,这又他的精度会明显变小,于此同时在初始化STL容器的时候很麻烦,我们需要一个一个的进行插入。或者在自定义变量中,初始化也是很麻烦的事情。总之初始化变量,这个事情可谓是八仙过海各显神通。

cpp 复制代码
// C++11之前,容器初始化很繁琐
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);

std::vector<int> v2(5, 1);  // 5个元素,都是1
std::vector<int> v3(5);     // 5个默认初始化的元素

// 对于map,更麻烦
std::map<int, std::string> m;
m[1] = "one";
m[2] = "two";

在引入c++11后:

2-1: 避免歧义和窄化转换问题:

原来的精度问题得到有效解决:

原本的警告直接变成error,这一点在编译器中我们也可以看到,{}初始化会禁止窄化转换,让代码更安全。

2-2 统一的样式进行初始化:

无论你是STL的vector还是map或者set,还是自定义类型Data或者student,我们都可以尝试使用{}来进行初始化:

cpp 复制代码
	int x = { 1 };
	double y = { 1.2 };
	vector<int> v({ 1,2,3,4,5,6 });
	vector<int> v2 = { 1,2,3,4,5,6 };
	map<string, int> mp = { {"1111",1},{"2222",2} };

这样看起来很是美观,这里是怎么做到的呢?这里其实引入了initializer_list。这是一个轻量级的容器:

cpp 复制代码
// initializer_list是C++11引入的轻量级代理对象
namespace std {
    template<typename T>
    class initializer_list {
    private:
        const T* array;  // 指向数组的指针
        size_t len;      // 元素个数
    public:
        // 构造函数等
    };
}

我们拿vector中拿来作比较:我们在{}里面的数字先构成了initializer_list,随后调用vector的构造函数完成来构造:

cpp 复制代码
template<typename T>
class vector {
public:
    // 1. 默认构造
    vector();
    
    // 2. 指定大小和值
    vector(size_t n, const T& value);
    
    // 3. 迭代器范围
    template<typename InputIt>
    vector(InputIt first, InputIt last);
    
    // 4. initializer_list构造(C++11新增)
    vector(std::initializer_list<T> init);
    
    // 5. 拷贝/移动构造
    vector(const vector& other);
    vector(vector&& other);
};

在这里我们可以看到很多构造函数。c++中总是调用自己最合适的函数:我们{}里面的变量已经构成了initializer_list,我们便可以调用4号来进行完成构造。

总结:

编译器背后做了什么:

  • 创建临时数组:在栈上创建 int[5] = {1, 2, 3, 4, 5}
  • 创建initializer_liststd::initializer_list<int> il(&临时数组[0], 5)。
  • 调用匹配的构造函数:vector(il) 拷贝或移动这些元素到vector内部。

再来说说,map是怎么初始化的:

cpp 复制代码
template<typename Key, typename Value>
class map {
public:
    // 接受pair的initializer_list
    map(std::initializer_list<std::pair<const Key, Value>> init);
};
cpp 复制代码
std::map<int, std::string> m{{1, "one"}, {2, "two"}};
  1. 外层{}:识别为initializer_list的参数
  2. 内层{1, "one"}:解析为std::pair<int, std::string>的初始化
  3. pair的构造:std::pair<const int, std::string>(1, "one")
  4. 最终调用:map({pair1, pair2})

2-3 对比两种初始化:

我们来尝试多组对比:

cpp 复制代码
	vector<int> v(8, 2);
	vector<int> v1{ 1,2,3,4,5,6,7,8 };
	vector<int> v2 = { 1,2,3,4,5,6,7,8 };
	vector<int> v3({ 1,2,3,4,5,6,7,8});

这几个初始化有什么区别呢:

  1. 第一个是传统的c++初始化,全部初始化成:2。只有在特定的情况下使用。
  2. 第二个则是c++11初始化的标准,我们会调用vector(std::initializer_list<int> init)创建 8个元素,值分别为1到8。最推荐的现代C++写法。
  3. 第三个很像是拷贝列表初始化,在现代编译器中几乎和第二个相同,更像是赋值拷贝初始化。适用场景:v2更像"赋值"语义,v1更像"构造"语义。
  4. 第四个过于冗余了,显式创建一个 initializer_list<int> 临时对象调用构造函数:vector(std::initializer_list<int> init)多此一举,但语法合法。
初始化方式 调用构造函数 创建元素数量 元素值 推荐度
v(8, 2) vector(size_t, const T&) 8个 全是2 ⭐⭐⭐(特定场景)
v1{1,2,3,4,5,6,7,8} vector(initializer_list<T>) 8个 1-8 ⭐⭐⭐⭐⭐
v2 = {1,2,3,4,5,6,7,8} vector(initializer_list<T>) 8个 1-8 ⭐⭐⭐⭐
v3({1,2,3,4,5,6,7,8}) vector(initializer_list<T>) 8个 1-8 ⭐⭐

2-4 陷阱:

cpp 复制代码
class Widget {
public:
    Widget(int a, int b) {}
    Widget(std::initializer_list<int> list) {}
};

Widget w1(1, 2);      // 调用第一个构造函数
Widget w2{1, 2};      // 调用第二个构造函数(initializer_list)
Widget w3(1, 2, 3);   // 错误!
Widget w4{1, 2, 3};   // 调用第二个构造函数

在这段函数中,总是调用最合适自己的,那么第一个就会调用第一个构造函数,第二个由于大括号会调用第二个构造函数。第三个由于无法匹配导致错误。

cpp 复制代码
class MyClass {
    int a;
    std::string b;
public:
    // 1. initializer_list构造(不常用)
    MyClass(std::initializer_list<int> list) {
        // 不推荐:类型不匹配
    }
    
    // 2. 多参数构造(推荐)
    MyClass(int a, std::string b) : a(a), b(b) {}
    
    // 3. 聚合类型(最简单)
    // 去掉构造函数,让struct成为聚合类型
};

// 使用
MyClass obj{1, "hello"};  // 调用构造函数 #2

3. 为什么要做到统一初始化?

1. 降低学习成本

  • 旧的C++:需要掌握多种初始化方式

    • int x = 0;(赋值)
    • int x(0);(直接初始化)
    • int arr[] = {1,2,3};(聚合初始化)
    • std::vector<int> v(10, 1);(括号初始化)
  • 新的C++:一种方式搞定所有

    • int x{0};
    • int arr[]{1,2,3};
    • std::vector<int> v{10, 1};

2. 提高代码可读性和一致性

cpp 复制代码
// 以前,不同容器、不同类型的初始化语法不一致
int a = 1;
int b(2);
int c[] = {3, 4};
std::vector<int> v(5, 1);  // 括号
std::map<int, int> m{{1,2}, {3,4}};  // 双括号

// 现在,统一用{},一眼就能看懂
int a{1};
int b{2};
int c[]{3, 4};
std::vector<int> v{5, 1};  // 注意:这里含义不同了!
std::map<int, int> m{{1,2}, {3,4}};

3. 与现代C++理念一致

C++11开始,C++致力于:

  • 更安全(类型安全、转换安全)
  • 更简洁(语法统一)
  • 更现代 (符合现代编程语言趋势)
    C++98/03的初始化语法是"拼凑"出来的,不同场景用不同语法。统一初始化让C++从"方言"走向"标准语"。
    统一初始化为后续C++11/14/17/20的很多新特性提供了基础,比如:
  • 结构化绑定
  • 模板参数推导
  • 聚合初始化扩展

总结

统一初始化({})的核心价值

  1. 安全:防止意外的类型转换
  2. 统一:一种语法,多种场景
  3. 简洁:减少记忆负担
  4. 现代:让C++更符合现代语言标准

正如C++之父Bjarne Stroustrup所说:"我希望C++能有一种统一的初始化方式,让初始化不再令人困惑。" {}就是这个愿景的实现。

相关推荐
yaoxin5211232 小时前
273. Java Stream API - Stream 中的中间操作:Mapping 操作详解
java·开发语言·python
技术小甜甜2 小时前
[Python实战] 告别浏览器驱动烦恼:用 Playwright 优雅实现网页自动化
开发语言·python·自动化
vortex52 小时前
Bash 替换机制(一):命令替换与进程替换
开发语言·chrome·bash
xiaoye-duck2 小时前
吃透C++类和对象(中):构造函数与析构函数深度解析
c++
AA陈超2 小时前
Lyra Starter Game 中 GameFeature 类(如 ShooterCore)的加载流程
c++·笔记·学习·ue5·虚幻引擎
小徐Chao努力2 小时前
【GO】Gin 框架从入门到精通完整教程
开发语言·golang·gin
她说..2 小时前
手机验证码功能实现(附带源码)
java·开发语言·spring boot·spring·java-ee·springboot
加成BUFF2 小时前
C++入门讲解3:数组与指针全面详解
开发语言·c++·算法·指针·数组
GoWjw2 小时前
C语言高级特性
c语言·开发语言·算法