在这里插入图片描述

草莓熊Lotso:

❄️个人专栏:

✨生活是默默的坚持,毅力是永久的享受!

博主简介:

在这里插入图片描述



前言:

C++11 作为 C++ 历史上最具里程碑意义的版本,引入了众多革命性特性,彻底改变了 C++ 的编程范式。其中,列表初始化 实现了初始化方式的大一统,右值引用与移动语义 则解决了长期存在的拷贝效率问题。这两大特性不仅简化了代码编写,更在性能优化上带来质的飞跃,成为现代 C++ 开发的必备技能。本文结合核心知识点、示例代码和实操案例,从列表初始化的用法与原理,到右值引用、移动语义的落地实践,层层拆解 C++11 这两大核心特性,帮你从 “会用” 到 “吃透”,真正融入日常开发。


一. C++11的发展历史

C++11是C++的第二个主要版本,并且是从C++98起的最重要更新。它引入了大量更改,标准化了既有实践,并改进了对C++程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前,人们曾使用名称“C++0X”,因为它曾被期待在2010年之前发布。C++03与C++11期间花了8年时间,故而是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新一次。
在这里插入图片描述


二. 列表初始化:万物皆可 {} 的统一初始化方案

C++98 中初始化方式杂乱(数组用 {}、类用构造函数、内置类型直接赋值),C++11 引入列表初始化(统一初始化) ,通过{}实现 “一切对象皆可初始化”,兼顾简洁性与安全性。

2.1 核心用法:支持所有对象类型

C++11中的{}:

  • 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化变成直接构造
  • {}初始化的过程中,可以省略掉=
  • C++11列表初始化的本意是实现一个大统一的初始化方法,其次他在有些场景下带来的不少便利,如容器push多参数构造的对象时,{}初始化会很方便。
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
void Insert(const Date& d)
{
}
Date func()
{
//Date d(2025, 11, 15);
//return d;
//return { 2025,11,15 };
//Date d;
//return d;
return {};
}
int main()
{
// C++98
int array1[] = { 1,2,3,4,5 };
int array2[5] = { 0 };
Point p = { 1,2 };
// C++11
// ⼀切皆可用列表初始化,且可以不加=
int x1 = { 2 };
// 自定义类型支持
// 这里本质是用{2025, 1, 1}构造一个Date临时对象
// 临时对象再去拷⻉构造d1,编译器优化后合二为一变成{2025, 1, 1}直接构造初始化d1
// 运行一下,我们可以验证上面的理论,发现是没调用拷⻉构造的
Date d1 = { 2025, 1, 1 };
// 这里d2引用的是{ 2024, 7, 25 }构造的临时对象
const Date& d2 = { 2024, 7, 25 };
// 需要注意的是C++98支持单参数时类型转换,也可以不用{}
Date d3 = { 2025 };
Date d4 = 2025;
// 可以省略掉=
Point p1{ 1, 2 };
int x2{ 2 };
Date d6{ 2024, 7, 25 };
const Date& d7{ 2024, 7, 25 };
// 不支持,只有{}初始化,才能省略=
// Date d8 2025;
vector<Date> v;
  v.push_back(d1);
  v.push_back(Date(2025, 1, 1));
  // 比起有名对象和匿名对象传参,这里{}更有性价比
  v.push_back({ 2025, 1, 1 });
  Insert({ 2025,11,15 });
  return 0;
  }

2.2 C++11中的std::initializer_list

std::initializer_list是 C++11 新增的轻量级容器,本质存储两个指针(指向数组首尾),数组存储初始化列表中的数据(位于栈上)。其核心接口包括begin()end()size(),支持迭代器遍历。

  • 上面的初始化已经很方便,但是对象是容器初始化还是不太方便,比如一个vector对象,我想用N个值去构造初始化,那么我们得实现多个构造函数才能支持,vector<int> v1= {1,2,3}; vector<int> v2 = {1,2,3,4,5};
  • C++库中提出std::initializer_list这个类,auto il = { 10, 20, 30 }; // the type of il is an initializer_list ,这个类的本质是底层开⼀个数组,将数据拷⻉过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。
  • std::initializer_list支持迭代器遍历
  • 容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的{x1,x2,x3……}进行初始化。STL中的容器支持任意多个值构成的{x1,x2,x3……}进行初始化,就是通过std::initializer_list的构造函数支持的。
int main()
{
vector<int> v1 = { 1,2,3,4 };
  vector<int> v2 = { 1,2,3,4,5,6,7,7 };
    // 里面的括号是pair列表初始化,外面的是initialize_list
    map<string, string> dict = { {"sort","排序"},{"string","字符串"} };
      v1 = { 10,20,30 };
      auto il = { 10,20,30 };
      cout << typeid(il).name() << endl;
      std::initializer_list<int> mylist;
        mylist = { 10,20,30 };
        cout << sizeof(mylist) << endl;
        // 这里begin和end返回的值initializer_list对象中存的两个指针
        // 这两个指针的值跟i的地址跟接近,说明数组存在栈上
        int i = 0;
        cout << mylist.begin() << endl;
        cout << mylist.end() << endl;
        cout << &i << endl;
        return 0;
        }

三. 右值引用与移动语义:彻底解决拷贝效率问题

C++98 中左值引用(Type&)无法绑定右值,大量临时对象的拷贝导致性能浪费。C++11 新增右值引用(Type&&移动语义,通过 “窃取” 右值对象的资源,替代拷贝操作,大幅提升效率。

3.1 基础概念:左值与右值

  • 左值:是⼀个表示数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
  • 右值:右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
  • 核心区别:能否取地址是左值和右值的核心区别。
int main()
{
// 左值:可以取地址
// 以下的p,b,c,*p,s,s[0]就是常见的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("11111111111111");
s[0] = 'x';
cout << &p << endl;
cout << &b << endl;
cout << &c << endl;
cout << &(*p) << endl;
cout << &s << endl;
cout << (void*)&s[0] << endl;
// 右值:不能取地址
double x = 1.1, y = 2.2;
// 以下几个10、x + y、fmin(x, y)、string("11111")都是常见的右值
10;
x + y;
fmin(x, y);
string("11111");
// 编译报错
//cout << &10 << endl;
//cout << &(x+y) << endl;
//cout << &(fmin(x, y)) << endl;
//cout << &string("11111") << endl;
}

3.2 右值引用语法

  • 右值引用专门绑定右值,延长其生命周期;
  • 左值引用(非 const)不能绑定右值,但const左值引用可绑定右值(但无法修改);
  • 右值引用不能直接绑定左值,但可通过std::move()强制转换左值为右值(move本质是类型转换,不移动数据)。

在这里插入图片描述

int main()
{
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("11111111111111");
s[0] = 'x';
double x = 1.1, y = 2.2;
// 左值引用给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
string& r4 = s;
char& r5 = s[0];
// 右值引用给右值取别名
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
string&& rr4 = string("11111");
// 左值引用不能直接引用右值,但是const左值引用可以引用右值
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");
// 右值引用不能直接引用左值,但是右值引用可以引用move(左值)
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s;
// b、r1、rr1都是变量表达式,都是左值
cout << &b << endl;
cout << &r1 << endl;
cout << &rr1 << endl;
// 这里要注意的是,右值引用后rr1的属性是左值,所以不能再被右值引用绑定,除非move一下
// 后面还会再讲到的
int& r6 = r1;
// int&& rrx6 = rr1;
int&& rrx6 = move(rr1);
return 0;
}

3.3 引用延长生命周期

我们知道引用可以延长对象生命周期,那么临时对象和匿名对象的生命周期可以通过右值引用来延长,const的左值引用也行,但是不能修改。

class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A aa1;
// 延长匿名对象的生命周期
const A& ref1 = A();// const左值引用,但是这样就不能修改了
A&& ref2 = A();// 右值引用
cout << "main end()" << endl;
return 0;
}

在这里插入图片描述

3.4 右值和左值的参数匹配

  • C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配
  • C++11之后,分别重载左值引用,const左值引用,右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配(const左值引用),实参是右值会匹配f(右值引用)。
  • 右值引用变量在用于表示式时属性是左值,这个我们后面还会讲到的,到时候大家就会对它有更直观的认知了。
void f(int& x)
{
std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
int i = 1;
const int ci = 2;
f(i); // 调用 f(int&)
f(ci); // 调用 f(const int&)
f(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)
f(std::move(i)); // 调用 f(int&&)
// 右值引用变量在用于表达式时是左值
int&& x = 1;
f(x); // 调用 f(int& x)
f(std::move(x)); // 调用 f(int&& x)
return 0;
}

在这里插入图片描述

3.5 移动语义核心:移动构造与移动赋值

先回顾一下左值引用的一些场景
左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题,但是有些场景不能使⽤传左值引⽤返回,如addStrings,generate函数,C++98中的解决⽅案只能是被迫使⽤输出型参数解决。那么C++11以后这⾥可以使⽤右值引⽤做返回值解决吗?显然是不可能的,因为这⾥的本质是返回对象是⼀个局部对象,函数结束这个对象就析构销毁了,右值引⽤返回也⽆法改变对象已经析构销毁的事实。
在这里插入图片描述
移动构造和移动赋值

  • 移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
  • 移动赋值是一个赋值运算符重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
  • 对于像string/vector这样的深拷贝的类或者深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,他的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造函数和拷贝赋值那样去拷贝资源,提高效率。下面的Lotso::string样例实现了移动构造和移动赋值,我们需要结合场景去理解。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
  #include<assert.h>
    #include<string.h>
      #include<algorithm>
        using namespace std;
        namespace Lotso
        {
        class string
        {
        public:
        typedef char* iterator;
        typedef const char* const_iterator;
        iterator begin()
        {
        return _str;
        }
        iterator end()
        {
        return _str + _size;
        }
        const_iterator begin() const
        {
        return _str;
        }
        const_iterator end() const
        {
        return _str + _size;
        }
        string(const char* str = "")
        :_size(strlen(str))
        , _capacity(_size)
        {
        cout << "string(char* str)-构造" << endl;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
        }
        void swap(string& s)
        {
        ::swap(_str, s._str);
        ::swap(_size, s._size);
        ::swap(_capacity, s._capacity);
        }
        // 拷贝构造
        string(const string& s)
        {
        cout << "string(const string& s) -- 拷贝构造" << endl;
        reserve(s._capacity);
        for (auto ch : s)
        {
        push_back(ch);
        }
        }
        // 移动构造
        string(string&& s)
        {
        cout << "string(string&& s) -- 移动构造" << endl;
        swap(s);
        }
        string& operator=(const string& s)
        {
        cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
        if (this != &s)
        {
        _str[0] = '\0';
        _size = 0;
        reserve(s._capacity);
        for (auto ch : s)
        {
        push_back(ch);
        }
        }
        return *this;
        }
        // 移动赋值
        string& operator=(string&& s)
        {
        cout << "string& operator=(string&& s) -- 移动赋值" << endl;
        swap(s);
        return *this;
        }
        ~string()
        {
        //cout << "~string() -- 析构" << endl;
        delete[] _str;
        _str = nullptr;
        }
        char& operator[](size_t pos)
        {
        assert(pos < _size);
        return _str[pos];
        }
        void reserve(size_t n)
        {
        if (n > _capacity)
        {
        char* tmp = new char[n + 1];
        if (_str)
        {
        strcpy(tmp, _str);
        delete[] _str;
        }
        _str = tmp;
        _capacity = n;
        }
        }
        void push_back(char ch)
        {
        if (_size >= _capacity)
        {
        size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newcapacity);
        }
        _str[_size] = ch;
        ++_size;
        _str[_size] = '\0';
        }
        string& operator+=(char ch)
        {
        push_back(ch);
        return *this;
        }
        const char* c_str() const
        {
        return _str;
        }
        size_t size() const
        {
        return _size;
        }
        private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
        };
        // 传值返回需要拷贝
        string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        // 进位
        int next = 0;
        while (end1 >= 0 || end2 >= 0)
        {
        int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
        int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
        int ret = val1 + val2 + next;
        next = ret / 10;
        ret = ret % 10;
        str += ('0' + ret);
        }
        if (next == 1)
        str += '1';
        reverse(str.begin(), str.end());
        cout << &str << endl;
        return str;
        }
        }
        int main
        {
        bit::string s1("xxxxx");
        // 拷⻉构造
        bit::string s2 = s1;
        // 构造+移动构造,优化后直接构造
        bit::string s3 = bit::string("yyyyy");
        // 移动构造
        bit::string s4 = move(s1);
        cout << "******************************" << endl;
        return 0;
        }

再补充两个场景

// 场景一
int main()
{
Lotso::string ret = Lotso::addStrings("11111", "2222");
cout << ret.c_str() << endl;
cout << &ret << endl;
}
// 场景二:
int main()
{
Lotso::string ret;
// ……
ret = Lotso::addStrings("11111", "2222");
cout << ret.c_str() << endl;
cout << &ret << endl;
}

3.5.1 右值对象构造,只有拷贝构造,没有移动构造的场景

  • 下图1中展现了 vs2019 debug 环境下编译器对拷贝的优化,左边为不优化的情况下,两次拷贝构造,右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造。
  • 需要注意的是在 vs2019的release和 vs2022的debug和release,下面代码优化十分恐怖,会直接将strd对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为一,变为直接构造,要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图三。
  • linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次拷⻉。

图一
在这里插入图片描述

3.5.2 右值对象构造,有拷⻉构造,也有移动构造的场景

  • 图2展示了vs2019 debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次移动构造,右边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次移动构造。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常恐怖,会直接将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图3所示。
    • linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次移动。

图二
在这里插入图片描述

图三
在这里插入图片描述

3.5.3 右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景

  • 图4左边展示了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境
    下编译器的处理,⼀次拷⻉构造,⼀次拷⻉赋值。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以看到str的析构是在赋值以后,说明str就是临时对象的别名。

图四
在这里插入图片描述

3.5.4 右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景

  • 图5左边展示了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境
    下编译器的处理,⼀次移动构造,⼀次移动赋值。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以看到str的析构是在赋值以后,说明str就是临时对象的别名。

图五
在这里插入图片描述

小思考
在这里插入图片描述

  • 综上:移动语义的使用肯定是对提高效率具有一定作用的,大家要好好理解体会一下。

3.6 右值引用和移动语义在传参中的提效

  • 查看STL⽂档我们发现C++11以后容器的push和insert系列的接⼝否增加的右值引⽤版本
  • 当实参是⼀个左值时,容器内部继续调⽤拷⻉构造进⾏拷⻉,将对象拷⻉到容器空间中的对象
  • 当实参是⼀个右值,容器内部则调⽤移动构造,右值对象的资源到容器空间的对象上
  • 把我们之前模拟实现的bit::list拷⻉过来,⽀持右值引⽤参数版本的push_back和insert
  • 其实这⾥还有⼀个emplace系列的接⼝,但是这个涉及可变参数模板,我们需要把可变参数模板学习以后后续再来了解。
int main()
{
std::list<std::string> lt;
  cout << "*************************" << endl;
  std::string s1("111111111111111111111");
  lt.push_back(s1);
  cout << "*************************" << endl;
  lt.push_back(std::string("22222222222222222222222222222"));
  cout << "*************************" << endl;
  lt.push_back("3333333333333333333333333333");
  cout << "*************************" << endl;
  // 左值move本质授予别人转移你数据资源权限,所以要谨慎
  lt.push_back(move(s1));
  cout << "*************************" << endl;
  return 0;
  }

注意:有些编译器的优化比较极致可能和这里的结果有出入。

运行结果:
string(char* str)
string(const string& s) -- 拷⻉构造
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(string&& s) -- 移动构造
*************************
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构

结合之前自己实现的list.h使用

// List.h
// 以下代码把跟这里无关的接口都删除了
namespace Lotso
{
template<class T>
  struct ListNode
  {
  ListNode<T>* _next;
    ListNode<T>* _prev;
      T _data;
      ListNode(const T& data = T())
      :_next(nullptr)
      , _prev(nullptr)
      , _data(data)
      {
      }
      ListNode(T&& data)
      :_next(nullptr)
      , _prev(nullptr)
      , _data(move(data))
      {
      }
      };
      template<class T, class Ref, class Ptr>
        struct ListIterator
        {
        typedef ListNode<T> Node;
          typedef ListIterator<T, Ref, Ptr> Self;
            Node* _node;
            ListIterator(Node* node)
            :_node(node)
            {
            }
            Self& operator++()
            {
            _node = _node->_next;
            return *this;
            }
            Ref operator*()
            {
            return _node->_data;
            }
            bool operator!=(const Self& it)
            {
            return _node != it._node;
            }
            };
            template<class T>
              class list
              {
              typedef ListNode<T> Node;
                public:
                typedef ListIterator<T, T&, T*> iterator;
                typedef ListIterator<T, const T&, const T*> const_iterator;
                iterator begin()
                {
                return iterator(_head->_next);
                }
                iterator end()
                {
                return iterator(_head);
                }
                void empty_init()
                {
                _head = new Node();
                _head->_next = _head;
                _head->_prev = _head;
                }
                list()
                {
                empty_init();
                }
                void push_back(const T& x)
                {
                insert(end(), x);
                }
                void push_back(T&& x)
                {
                insert(end(), move(x));
                }
                iterator insert(iterator pos, const T& x)
                {
                Node* cur = pos._node;
                Node* newnode = new Node(x);
                Node* prev = cur->_prev;
                // prev newnode cur
                prev->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
                }
                iterator insert(iterator pos, T && x)
                {
                Node* cur = pos._node;
                Node* newnode = new Node(move(x));
                Node* prev = cur->_prev;
                // prev newnode cur
                prev->_next = newnode;
                newnode->_prev = prev;
                newnode->_next = cur;
                cur->_prev = newnode;
                return iterator(newnode);
                }
                private:
                Node* _head;
                };
                }
                // Test.cpp
                #include"List.h"
                int main()
                {
                Lotso::list<Lotso::string> lt;
                  cout << "*************************" << endl;
                  Lotso::string s1("111111111111111111111");
                  lt.push_back(s1);
                  cout << "*************************" << endl;
                  lt.push_back(Lotso::string("22222222222222222222222222222"));
                  cout << "*************************" << endl;
                  lt.push_back("3333333333333333333333333333");
                  cout << "*************************" << endl;
                  lt.push_back(move(s1));
                  cout << "*************************" << endl;
                  return 0;
                  }

注意push_back和insert的实现,以及为啥还要加个move,这个就涉及到了前面讲到右值引用变量的属性是左值(变量表达式均为左值)。

那又为什么是左值呢?
在这里插入图片描述


结尾:

 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:C++11 的列表初始化和右值引用 + 移动语义,分别从 “语法简洁性” 和 “性能优化” 两个维度革新了 C++ 编程。列表初始化统一了初始化方式,减少错误;移动语义则解决了临时对象拷贝的性能痛点,尤其适用于深拷贝类和容器操作。掌握这两大特性,不仅能写出更简洁、高效的代码,更能理解现代 C++ 的设计思想 ——“零成本抽象”,在不牺牲性能的前提下提升开发效率。实际开发中,应优先使用列表初始化简化代码,对深拷贝类主动实现移动构造 / 赋值,充分利用移动语义优化性能。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

在这里插入图片描述