❀C++11
前言:在C++的悠久历史中,每一次标准的更新都如同为这门强大的编程语言注入了新的活力。C++11,作为这一进程中的一个重要里程碑,不仅带来了众多新特性,还深刻改变了C++编程的范式,其中右值引用(Rvalue References)无疑是最为引人注目的特性之一
本篇将带您深入探索C++11中的右值引用及其相关特性,包括移动语义(MoveSemantics)、完美转发(Perfect Forwarding)等。我们将从基础概念讲起,逐步深入到实际应用和最佳实践,旨在帮助您全面理解并掌握这一强大的编程工具
让我们一起踏上学习的旅程,探索它带来的无尽可能!
📒1. C++11简介
相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习
总之,C++11是C++编程语言发展历程中的一个重要里程碑,它带来了众多新特性和改进,为C++程序员提供了更加强大和灵活的工具来编写高效、可维护的代码
C++11介绍
📜2. 统一的列表初始化
🌸{ }初始化
代码示例 (C++):
// C++98
struct Pxt
{
int _x;
int _y;
};
int main()
{
Pxt p = { 1, 2 };
return 0;
}
代码示例 (C++):
// C++11
struct Pxt
{
int _x;
int _y;
};
int main()
{
int a = 1;
int b{ 2 };
Pxt p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
return 0;
}
我们的自定义创建的对象也可以使用列表初始化方式调用构造函数初始化
代码示例 (C++):
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 7, 28); // old style
// C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{ 2024, 7, 29 };
Date d3 = { 2024, 7, 30 };
// 这里的 vector 和上面的 Date 不太一样
vector<int> v = { 1,2,3,4,5 };
return 0;
}
vector<Data> vd = { {2024, 7, 28},{2024, 7, 29},{2024, 7, 30} };
🌺initializer_list
initializer_list介绍文档
代码示例 (C++):
int main()
{
auto it = { 1 ,2 };
// initializer_list
cout << typeid(it).name() << endl;
return 0;
}
代码示例 (C++):
vector(initializer_list<T> lt)
{
reverse(lt.size());
for(auto& e : lt)
{
push_back(e);
}
}
📚3. decltype与新容器 array
🎩decltype
代码示例 (C++):
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
return 0;
}
关键字decltype
将变量的类型声明为表达式指定的类型
🎈新容器 array
array<int, 10> a; // a[10]
vector<int> v(10, 0)
// 因为有vector的存在,让array的出现有点“小丑”,而且vector 似乎比array好用
array介绍文档
📝4. 右值引用和移动语义
右值引用
移动语义
⛰️左值引用和右值引用
左值引用
代码示例 (C++):
int main()
{
// p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
右值引用
代码示例 (C++):
int main()
{
double x = 1.1, y = 2.2;
// 常见的右值
10;
x + y;
fmin(x, y);
// 对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
return 0;
}
int main()
{
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
}
注意:
🌄左值引用与右值引用比较
左值引用:
代码示例 (C++):
int main()
{
// 左值引用只能引用左值,不能引用右值。
int a = 10;
int& ra1 = a; // ra为a的别名
//int& ra2 = 10; // 编译失败,因为10是右值
// const左值引用既可引用左值,也可引用右值。
const int& ra3 = 10;
const int& ra4 = a;
return 0;
}
右值引用:
代码示例 (C++):
int main()
{
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
return 0;
}
move
代码示例 (C++):
int main()
{
string s1("hello world");
string s2 = s1;
string s3 = move(s1);
return 0;
}
直接将s1中的资源“移动”到了s3中
🌞右值引用使用场景和意义
移动构造,移动赋值代码示例 (string为例):
// 移动构造
string(string&& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
左值引用做参数可以减少拷贝,提高效率的使用场景和价值,但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回
代码示例 :
string to_string(int x)
{
string ret;
while (x)
{
int val = x % 10;
x /= 10;
ret += ('0' + val);
}
reverse(ret.begin(), ret.end());
return ret;
}
int main()
{
string ret = to_string(1234);
return 0;
}
代码示例 :
void push_back (value_type&& val);
int main()
{
list<pxt::string> lt;
pxt::string s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);
// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
return 0;
}
⭐完美转发
模板中的&& 万能引用
代码示例 :
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
神奇的一幕发生了,我们运行发现结果全是左值引用
那我们如何能够在传递过程中保持它的左值或者右值的属性, 就需要用我们用到完美转发
完美转发
template<typename T>
void PerfectForward(T&& t)
{
Fun(forward<T>(t));
}
📙5. 新的类功能
C++11在原来的基础上新增了两个默认成员函数:移动构造函数和移动赋值运算符重载
禁止生成默认函数的关键字delete
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p) = delete;
private:
string _name;
int _age;
};
📖6. 总结
在探索C++11的广阔特性时,右值引用无疑是一个令人兴奋且意义深远的新特性。它不仅为C++带来了移动语义和完美转发的能力,还极大地增强了C++代码的性能和灵活性。通过深入学习和实践右值引用,我们学会了如何更有效地管理资源,减少了不必要的拷贝操作,从而提高了程序的运行效率
随着C++标准的不断演进,我们期待看到更多基于右值引用的新特性和优化,C++11的内容我们还没有完全了解,愿我们都能保持好奇心和求知欲,不断探索C++的无限可能,我们下期见!
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!