您现在的位置是:首页 >其他 >【C++】c++11的新特性——右值引用/移动语义/lambda表达式网站首页其他
【C++】c++11的新特性——右值引用/移动语义/lambda表达式
文章目录
- ? 个人主页 :超人不会飞)
- ? 本文收录专栏:《C++的修行之路》
- ? 如果本文对您有帮助,不妨点赞、收藏、关注支持博主,我们一起进步,共同成长!
C++11介绍
C++11是C++的第二个主要版本,也是自C++98以来最重要的更新。引入了大量的变化,旨在规范现有的实践,并改善C++程序员可用的抽象。在ISO于2011年8月12日最终批准之前,它的名称为“C++0x”,因为人们预计它将在2010年之前发布。C++03到C++11之间历时8年,因此这成为迄今为止版本之间最长的间隔。自那时以来,C++定期每3年进行更新。
1. 统一的列表初始化
1.1 {}初始化
-
在C++98中,可以用花括号
{}
对数组或结构体元素进行列表初始值设定。struct Point { int x; int y; }; int main() { int a[] = { 1,2,3,4,5 }; //构造数组 Point p = { 0,1 }; //依次构造Point中的元素 return 0; }
-
C++11扩大了
{}
列表初始化的范围。使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。struct Point { int x; int y; }; int main() { int x1 = 1; int x2{ 2 }; int array1[]{ 1, 2, 3, 4, 5 }; int array2[5]{ 0 }; Point p{ 1, 2 }; // C++11中列表初始化也可以适用于new表达式中 int* pa = new int[4] { 1, 2, 3, 4}; return 0; }
-
对于自定义类型,实例化对象时,列表应用于构造函数的初始化列表
class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; }; int main() { //传统的构造方式 Date d0(2000, 1, 1); //列表初始化构造 Date d1 = { 2023,5,5 };//传入Date的构造函数 Date d2{ 1999,9,9 }; return 0; }
1.2 std::initializer_list
?C++11中,对于STL容器,支持以下的初始化方法
vector<int> v = { 2,3,4,5,2,1 };
list<int> l = {2,3,4,5,2,1};
map<string, string> m = { {"苹果","apple"}, {"香蕉","banana"}};
?这是因为C++11引入了initializer_list
这一类型,并为STL容器类型提供了以initializer_list
类型对象为参数的构造函数。实例化STL容器对象时,将{}
括起来的列表视为一个initializer_list
类型的对象,调用对应的构造函数即可。
-
std::initializer_list是什么类型?
std::initializer_list是一个模板类型,initializer_list<T>
是一个以const T类型元素组成的序列类型。因此,可以将其视作一个常量数组,不可以修改。
std::initializer_list使用场景:
- std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。
- 也可以作为operator=的参数,这样就可以用大括号赋值。
⭕使用演示
void test1()
{
initializer_list<int> il = { 2,4,1,5,6,3 };//可以理解为一个常量数组
auto it = il.begin();
//*it = 2;// const类型,不可修改
vector<int> v = { 2,3,4,5,2,1 };
//等价于
//initializer_list<int> il = { 2,3,4,5,2,1 };
//vector<int> v(il);
//还可以构造匿名对象
vector<int>{1,2,3};
//等价于vector<int> v{1,2,3}/vector<int> v = {1,2,3};
map<string, string> m = { {"苹果","apple"}, {"香蕉","banana"},{"西瓜","watermelon"} };
//等价于
//initializer_list<pair<const string,string>> il_str = { {"苹果","apple"}, {"香蕉","banana"},{"西瓜","watermelon"} };
//map<string, string> m(il_str);
// map<string, string> m = { {"苹果","apple"}, {"香蕉","banana"},{"西瓜","watermelon"} };
// 里层花括号:构造pair<const string,string>类型对象
// 外层花括号:initializer_list<pair<const string,string>>构造map对象
for (auto& kv : m)
{
cout << kv.first << ":" << kv.second << endl;
}
}
2. 一些关键字
2.1 auto
⭕自动类型推导,要求变量必须进行显示初始化,编译器将定义对象的类型设置为初始化值的类型。
int main()
{
auto a = 1;
//a的类型为int
auto pa = &a;
//pa的类型为int*
}
2.2 decltype
⭕关键字decltype将变量的类型声明为表达式指定的类型。
//decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
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;
F(1, 'a');
return 0;
}
2.3 nullptr
⭕由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
这段代码的作用是定义一个通用的空指针常量 NULL,以便在程序中避免使用未初始化的指针或者空指针导致的错误。在 C++中,空指针常量已经被定义为 nullptr,但是在C中没有类似的内置常量,因此需要通过宏定义的方式来实现。
3. 范围for
形如以下形式,可遍历容器v。底层原理是迭代器,语法糖部分已经详细讲解,不再赘述。
vector<int> v = {1,2,3,4,5};
for(auto e:v)
{
cout << e << endl;
}
4. 右值引用和移动语义(重点)
4.1 左值引用和右值引用
?传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
-
什么是左值?什么是左值引用?
左值: 左值是一个表示数据的表达式(如变量名或指针解引用),可对其取地址或赋值,左值(非const)可以出现在赋值符号
=
的左边。左值定义为const时,不能赋值,但能取地址。左值引用: 给左值取别名。
-
什么是右值?什么是右值引用?
右值: 右值也是一个数据的表达式。如:字面常量、表达式的临时结果(如:&var、p+1、匿名对象)、函数的返回值(这个返回值不能是左值引用)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
右值引用: 给右值取别名。
void test_ref()
{
// 左值引用
int a = 10;
int& ra = a;
const int& rra = a;//权限缩小
const int& rrra = 10;//权限平移
/*int& rra = 10;*///err,10是右值
// 右值引用
int b = 1;
/*int&& rb = b;*/ //err,b是左值
int&& rb = 1;
int&& rrb = move(b);//move将左值转化为右值
cout << rb << endl;
}
左值引用:一个&
右值引用:两个&
⭕总结:
左值引用不能引用右值,但const左值引用可以引用左值和右值
右值引用不能引用左值,但右值引用可以引用move后的左值
4.2 右值引用的应用
⭕右值一般又可以分为两种
- 纯右值:内置类型表达式的临时值,例如常量、字面量、临时对象等。
- 将亡值:将亡值是指具有“资源所有权转移”的右值。它是C++11引入的新概念,用于表示即将被销毁的对象的值,但其资源可以被转移到其他对象。 即将被销毁的对象,例如:函数返回对象、匿名对象等等。
右值引用的应用是利用了将亡值的特性支持移动语义。
将亡值的引入主要是为了支持移动语义,通过将资源的所有权转移给新对象,可以避免进行昂贵的拷贝操作,提高效率。这在处理大型对象、容器和动态分配的内存时特别有用。
为了演示移动语义的实现,我们需要先自定义一个string类型(后面称之为ckf::string,因为我将其定义在ckf命名空间中),再写一个to_string用作测试函数
namespace ckf
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{ _str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
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;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
~string()
{
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];
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] = '