微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C++11常见特性


C++11新特性

1. 列表初始化

内置类型初始化:

int main()
 { 
 // 内置类型变量
 int x1 = {10};
 int x2{10};
 int x3 = 1+2;
 int x4 = {1+2};
 int x5{1+2};
 // 数组
 int arr1[5] {1,2,3,4,5};
 int arr2[]{1,2,3,4,5};
 
 // 动态数组,在C++98中不支持
 int* arr3 = new int[5]{1,2,3,4,5};
 
 // 标准容器
 vector<int> v{1,2,3,4,5};
 //等号可以不用写
 map<int, int>= m{{1,1}, {2,2,},{3,3},{4,4}};
 return 0;
 }

2. 变量类型推导

2.1 auto

int x = 0;
auto * a = &x;      // a -> int*,auto被推导为int
auto   b = &x;      // b -> int*,auto被推导为int*
auto & c = x;       // c -> int&,auto被推导为int
auto   d = c;       // d -> int ,auto被推导为int
const auto e = x;   // e -> const int
auto f = e;         // f -> int
const auto& g = x;  // e -> const int&
auto& h = g;        // f -> const int&

由上面的例子可以看出:

  • a 和 c 的推导结果是很显然的,auto 在编译时被替换为 int,因此 a 和 c 分别被推导为 int* 和 int&。
  • b 的推导结果说明,其实 auto 不声明为指针,也可以推导出指针类型。
  • d 的推导结果说明当表达式是一个引用类型时,auto 会把引用类型抛弃,直接推导成原始类型 int。
  • e 的推导结果说明,const auto 会在编译时被替换为 const int。
  • f 的推导结果说明,当表达式带有 const(实际上 volatile 也会得到同样的结果)属性时,auto 会把 const 属性抛弃掉,推导成 non-const 类型 int。
  • g、h 的推导说明,当 auto 和引用(换成指针在这里也将得到同样的结果)结合时,auto 的推导将保留表达式的 const 属性

2.2 decltype

decltype可以用一个已知类型的变量去初始化另一个变量,使新初始化的变量是我们想要声明的类型,即将变量的类型声明为表达式指定的类型。

#include<vector>
#include<map> 
//场景:使用decltype定义一个auto推导对象的拷贝
int main()
{
	map<string,string> dict = {{"Left","左"},{"Right","右"}};
	auto it = dict.begin();
	//auto copyIt = it;
	//vector<auto> v;错误语法
	decltype(it) copyIt = it;
	vector<decltype(it)> v;
	v.push_back(it);
	
	return 0;
}

3. nullptr

C++中NULL被定义成字面量0,这样就可能出现一些问题,因为0既能表示指针常量,又能表示整形常量,所以出于清晰和安全角度的考虑,C++11新增了nullptr,用于表示空指针


4. 范围for

底层为迭代器,凡是支持迭代器的都支持范围for。


5. STL中的一些变化

1.新增一些容器:

array:

#include<array>
int main()
{
	//array作为容器比较鸡肋,它的作用和底层实现和数组并无二致
	//但是:
	//1.array支持迭代器,能够更好的兼容STL
	//2.array对于越界的检查
	
	int a1[10];
	array<int,10> a2;
	//a1[11] = 0; 不报错: *(a1+11) = 0
	//a2[11] = 0; 报错: a2.operator[](14) = 0
	return 0; 
}

forward_list: 单链表

//支持头插头删,不支持尾插尾删
//相较于list,每个结点省了一个指针

unordered_map:
unordered_set:

//底层为哈希表
//map、set底层为红黑树

2.针对已有容器,增加了一些提高效率的接口。比如:列表初始化initializer_list、移动构造、移动赋值、右值引用(&&)版本插入接口。


6. 右值引用和移动语义

6.1 左值引用(&)和右值引用(&&)

左值引用:

无论是左值引用还是右值引用,都是给对象起别名。左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址以及可以对它赋值,左值出现在赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能对其赋值,但是可以取其地址。实际上,可以取地址的对象,就是左值。

int main()
{
	//常见左值引用
	int a = 10;
	int &b = a;
	int *p = &a;
	int &c = *p;
	
	//定义时const修饰符后的左值,不能对其赋值,但是可以取其地址。
	const int d = 10;//d也是左值
	const int &e = d;
	
	return 0;
}

右值引用:

右值也是一个表示数据的表达式,如:字面常量、表达式返回值、传值返回函数的返回值(这个不能是左值引用返回)等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。右值引用就是给右值起别名。不能取地址的对象,就是右值。

int main()
{
	double x = 1.5;
	double y = 2.5;
	//常见右值
	10;
	x+y;
	fmin(x,y);
	
	//常见右值引用,不能取地址
	int &&r1 = 10;
	double &&r2 = x+y;
	double &&r3 = fmin(x+y);
}

交叉引用:

左值引用能否引用右值?右值引用能否引用左值?

int main()
{
	double x = 1.5;
	double y = 2.5;
	//常见右值:
	10;
	x+y;
	fmin(x,y);
	//常见左值:
	int *p = new int(0);
	int a = 1;
	const int b = 2;
	
	//左值引用能否引用右值?--不能直接引用,但是const左值引用可以引用右值
	//int &r1 = 10; 错误
	//int &r2 = x+y; 错误
	//int &r3 = fmin(x,y); 错误
	const int &r1 = 10; 
	const double &r2 = x+y;
	const double &r3 = fmin(x,y);
	
	//右值引用能否引用左值?--不能直接引用,但是可以右值引用move以后的左值
	//int *&& rr1 = p; 错误
	//int && rr2 = *p; 错误
	//int && rr3 = a; 错误
	//const int && rr4 = b; 错误
	int *&& rr1 = move(p);
	int && rr2 = move(*p);
	int && rr3 = move(a);
	const int && rr4 = move(b);

	return 0;
}

右值是不能取地址的,但是给右值取别名后,对右值引用可以取地址。因为给右值取别名后,会导致右值被存储到特定位置,且可以取到该地址。

int main()
{
	double x = 1.1,y = 2.2;
	int &&rr1 = 10;
	double &&rr2 = x + y;
	const double &&rr3 = x + y;
	cout << &rr1 << endl;//可以对右值的引用取地址
	cout << &rr2 << endl;//可以对右值的引用取地址

	rr1 = 11;
	rr2 = 22.22;

	return 0;
}

6.2 右值引用的场景和意义

右值引用的产生是为了弥足左值引用的不足,在某些情况下,左值引用只能传值,调用拷贝构造进行深拷贝,而右值引用则可以调用移动构造,进行资源转移,减少资源浪费。

string& operator+=(char ch)
{
	push_back(ch);
	return *this;//*this是左值,出了作用域后还在,允许引用返回
}

string operator+(char ch)
{
	string tmp(*this);
	push_back(ch);
	//只能传值返回,因为出了作用域若使用引用返回则返回tmp的别名
	//但是此时已经析构,tmp返回不成功,所以需要移动构造进行优化,使资源转移而不是释放
	return tmp;
}	

6.2.1 移动构造

C++11中,将右值分为两种:纯右值和将亡值(自定义类型)。将亡值不再调用析构函数,希望调用移动构造进行资源转移。

两个func函数形成重载,左值引用调用拷贝构造,右值引用的10调用移动构造函数形成资源转换。

在这里插入图片描述

6.2.2 移动构造在传值返回方面的价值

没有移动构造:
有接收变量:

在这里插入图片描述


无接收变量:

在这里插入图片描述

移动构造:

在这里插入图片描述

6.2.3 移动赋值

在这里插入图片描述

有移动构造时,右值引用会调用移动构造而不是拷贝构造,移动构造会进行资源转移,提高效率。没有移动构造时,则调用拷贝构造进行深拷贝。

6.3 完美转发

完美转发:正确传递在传参的过程中保留对象原生类型属性

C++11通过std::forward函数来实现完美转发, 比如:

void Fun(int& x) { cout << "左值" << endl; }
void Fun(int&& x) { cout << "右值" << endl; }
void Fun(const int& x) { cout << "const左值" << endl; }
void Fun(const int&& x) { cout << "const右值" << endl; }
template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }
//不使用forward函数,则会把右值变成左值。
int main()
{
	PerfectForward(10); //右值

	int a;
	PerfectForward(a); //左值
	PerfectForward(std::move(a)); //右值
	const int b = 10;
	PerfectForward(b); //const左值
	PerfectForward(std::move(b)); //const右值
	
	return 0;
}

原文地址:https://www.jb51.cc/wenti/3284671.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐