【C++】C++11的部分特性


前言

在C++11中,增加了不少的特性,部分是挺有用的,下面来简单介绍一些特性的使用与功能。

一、{ } 初始化

C++11中,支持STL中的容器也使用大括号进行初始化,并且在使用大括号进行初始化时,可以省略等号,比如:

list<int> l1 = { 1,2,3,4,5 };
list<int>l2{ 1,2,3,4,5 };

map<int, int> m1 = { {1,1},{2,2} };
map<int, int> m2{ {1,1},{2,2} };

对于类和结构体也支持使用大括号进行初始化,对于类来说使用大括号初始化就相当于调用其构造函数,比如:

class A
{
public:
	A(int x = 0,int y =0)
		:_x(x)
		,_y(y)
	{}

private:
	int _x;
	int _y;
};

int main()
{
	A a1{ 1,2 };
	A a2 = { 1,2 };
	return 0;
}

那么这个大括号是个什么类型呢?来看一下:

int main()
{
	auto e = { 1,2,3,4,5 };
	cout << typeid(e).name() << endl;
	return 0;
}

运行结果:
在这里插入图片描述

可以看到类型是initializer_list,这个initializer_list一般用于构造函数的参数,也可以用作operator= 的参数,在C++11中,STL的容器一般都支持initializer_list来进行构造。

二、auto

auto的功能就是根据等号右边的类型来自动推导等号左边的变量的类型,对于某些名称很长的类型来说还是挺便利的,比如说迭代器类型:

map<int, string> m1;

map<int, string>::iterator it1 = m1.begin();
auto it2 = m1.begin();

三、decltype

根据括号内的表达式的类型来决定声明变量的类型,比如:

int main()
{
	int a = 1;
	int b = 2;
	double c = 3.3;

	decltype(a * b)t1;
	decltype(a * c)t2;

	cout << typeid(t1).name() << endl;
	cout << typeid(t2).name() << endl;
	return 0;
}

运行结果:
在这里插入图片描述

四、范围for循环

范围for循环的格式:

for(auto e : arr)
{
	//...
}

其中e为变量名,可以自定义,而arr为能确定结尾的一段空间,比如数组、listvector等,但是对于指针是不能作为arr的,因为指针的结尾并不是明确的,而且范围for循环不需要自己来迭代e的值,每一次的循环体执行编译器都会自动迭代earr中的下一个值。

五、右值引用和移动语义

1. 右值引用

首先理解什么是右值,在理解右值之前,先回忆一下什么是左值,左值就是能被取地址的值,比如:

int a;
const char b;
int *pa = &a

以上的abpa、*pa都是左值,那什么是右值呢?右值也就是不能被取地址的值,比如:

int a = 10;
int b = 20;

10;
a+b;
func(a,b);

以上10a+bfunc(a,b) 的返回值都是右值,这些值都是不能被取地址的,那么现在来介绍右值引用,其实无论是左值引用还是右值引用,本质都是取别名,右值引用对右值(也称将亡值)取别名,右值本身是只能在当前行的作用域存活的,而如果右值被引用了,也就相当于把右值存储到特定的位置了,比如:

int&& a = 10;

左值引用是一个 &,右值是两个 &,其实左值也是可以引用右值的,但是因为右值具有常属性,所以左值在引用右值时需要加上const,也就表示不能被修改了,如下:

const int& a = 10;

那么右值是否可以引用右值呢?答案是可以的,但是不能直接引用左值,可以引用move( ) 后的左值,move( ) 是一个库函数,表示把当前左值当成右值,比如:

int x = 10;
int &&y = move(x);

如果被move( ) 的左值不是内置类型,比如list、vector,在被move( ) 之后,它本身的数据就会被转移到右值引用其的变量处,也就是资源被掠夺了,所以使用时需要注意,实例如下:

list<int> l1 = { 1,2,3,4,5 };
vector<int> v1 = { 1,2,3,4,5 };

move( ) 前:
在这里插入图片描述

list<int> l2 = move(l1);
vector<int> v2 = move(v1);

move( ) 后:
在这里插入图片描述
可以很明显的看到资源被转移了,所以使用时要谨慎。

2. 移动语义

移动语义分为移动构造和移动赋值,移动语义主要可以用于解决返回局部变量的问题,我们知道对于左值引用,如果要返回局部变量那么就必须要做深拷贝,因为局部变量出了当前作用域就会被释放,所以引用一个会被释放的返回值是有风险的,而我们不难发现,局部变量也就是一个右值(将亡值),那么我们就可以通过移动构造或者移动赋值来把这个将亡值的资源给拿过来,既然你出了作用域就会被释放,而我又想要你的值,那不如把你的空间给我,这样付出的性能代价就比直接深拷贝高效一些。

就拿string的移动构造和移动赋值来示范:

// 移动构造
string(string&& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	swap(s);
}

// 移动赋值
string& operator=(string&& s)
{
	swap(s);
	return *this;
}

移动构造和移动赋值,就是你把你的资源空间给我,而因为是和将亡值进行交换,所以我把我的空间资源换给你,等你出了作用域被释放了,我既拿到了想要的你的资源和空间,我本身的空间你又帮我释放了,就是很方便的。

六、类的默认成员函数

之前我们认知的类的默认成员函数有6个,分别是:

  • 默认构造函数
  • 拷贝构造函数
  • 析构函数
  • 拷贝赋值运算符重载
  • 取地址重载
  • const取地址重载

现如今添加了两个默认成员函数:移动构造和移动赋值,所以类现在有8个默认成员函数。对于之前的6个默认成员函数,我们不写编译器就会自动生成,但是新增的两个默认成员函数则不会,想要编译器自动生成这两个默认成员函数需要满足一个条件,即不能自主实现拷贝构造函数、拷贝赋值运算符重载、析构函数三个中的任意一个,那么编译器就会自动生成移动构造和移动赋值,它们对于内置类型都是按照字节进行拷贝或者赋值,对于自定义类型,如果该自定义类型实现了移动构造和移动赋值,那么会调用其移动构造或移动赋值,如果没有则相对的调用拷贝构造和拷贝赋值。

另外补充两个关键字:

  • default:强制生成该默认成员函数
// 移动构造
string(string&& s) = default;
  • delete:强制不生成该默认成员函数
// 拷贝构造
string(const string& s) = delete;

七、可变参数模板

可以创建接收多个参数的类模板和函数模板,示例:

void Show()
{
	cout << endl;
}

template<class T, class ...Args>
void Show(const T t, Args... args)
{
	cout << t << " ";
	Show(args...);
}

int main()
{
	Show();
	Show(1, 2, 3, 4, 5);
	Show(1, 'A', string("Hello"));
	return 0;
}

运行结果:
在这里插入图片描述
这段代码的意思是根据你传入的参数,进行递归输出,如果输入0个参数,就会匹配到void Show(),那么此时输出了换行就结束了;如果输入5个参数,那么会调用void Show(const T t, Args… args),此时1会被传给t,而后面的2、3、4、5会被传给args,函数体输出1 ,之后进行递归,它是这样递归的,args里面存有2、3、4、5,那么在递归时会把2往前推,推给t,而自己保留3、4、5,依次类推,直到args没有元素了,此时调用void Show() 进行换行,函数调用结束,而args可以接收多个参数,并且参数类型可以不同,所以1、A、Hello这个输出的执行过程是和之前一致的。

补充一个查看当前args里面所包含元素的语句:

cout << sizeof...(args) << endl;

用法很怪,记住就好。

八、lambda表达式

先简单写一个lambda表达式,比如:

int main()
{
	int a = 10;
	int b = 20;
	auto fun = [](int a, int b)->int {
		return a + b;
	};
	cout << fun(a, b) << endl;
	return 0;
}

上面的lambda表达式用于实现两个整数的相加,lambda实际上就是一个匿名的函数对象,所以可以直接调用lambda表达式,也可以把其赋值给一个变量,那么该变量的功能就是lambda的功能。

lambda的语法构成:

[capture-list] (parameters) mutable -> return-type { statement }
  • [ ]:捕捉列表,用于捕捉父域及以外的变量供函数体内使用,有多种捕获方式,比如:

[=]:以传值方式捕捉父域及以外的所有变量
[&]:以引用的方式捕捉父域及以外的所有变量
[x,y]:捕捉指定的变量,用逗号分割
[&x,&y]:捕捉指定的变量的地址,用逗号分割
[&,x]:除了x是传值捕捉,其他变量都是引用捕捉
根据以上情况可以合理搭配捕捉列表,但是 [&,=] 这种捕捉方式是不行的,因为矛盾了,不可能既按引用的方式捕捉父域及以外的所有变量,又按传值方式捕捉父域及以外的所有变量。

  • ( ):参数列表,用于传参,参考普通函数传参,不需要传参时,参数列表可以省略不写。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时,参数列表不可省略(即使参数为空),不需要使用时可以不写。
  • -> return-type:返回值类型,没有返回值,可以省略不写,返回值的类型明确,可以省略不写,由编译器自动进行推导。
  • { }:lambda表达式的函数体

由此可以得出一个最简单的lambda表达式:[ ]{ };

另外需要注意即使两个lambda表达式的内容即格式都完全一样也不能进行相互赋值,比如:

int main()
{
	auto fun1 = [](int a, int b) {return a + b; };
	auto fun2 = [](int a, int b) {return a + b; };

	fun1 = fun2;
	return 0;
}

这是错误的。

记住lambda表达式其实和仿函数差不多。

总结

温故而知新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值