c++11特性

初始化列表

底层是c++11新增类型initializer_list,他提供了构造函数来初始化。STL里的容器提供了initializer_list版本的构造函数。使用花括号初始化变量和对象{}。甚至可以省略等于号(=)。

下图,列举了三个容器实现initializer_list版的构造函数,其他就不一一列举了

查询网站:Reference - C++ Reference

使用场景:

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;


int main()
{
	//一切皆可以用花括号初始化{}
	vector<int> v = { 1,2,3,4,5 };
	pair<string, int> p = { "string",1 };
	set<string> s = { "string" };
	list<int> li = { 1 };
	//内置类型
	int i = 1;
	int j = { 1 };
	int k{ 1 };
	return 0;
}

下面这段代码能打印出他的类型

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;
int main()
{
	auto li = { 1,2,3,4 };
	cout << typeid(li).name() << endl;
	return 0;
}

自动推导

decltype,关键字decltype将变量声明为表达式的指定类型。

使用场景:

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;
// 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;
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
	F(1, 'a');
	return 0;
}

新增容器

<array>静态数组
<forward_list>单链表
<unordered_map>哈希表实现map
<unordered_set>哈希表实现set

array

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <array>
using namespace std;
int main()
{
	array<int, 10> arr1;
	//arr1[11]; //强制检查越界
	int arr2[] = { 0 };
	arr2[11]; //这里不会强制检查
	return 0;
}

forward_list

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <array>
#include <forward_list>
using namespace std;
int main()
{
	forward_list<int> s;
	s.push_front(1);
	s.push_front(2); 
	s.push_front(3); 
	s.push_front(4);
	//没有直接提供尾插的接口 为了效率的提升
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << endl;
		it++;
	}
	return 0;
}

unordered_map和unordered_set和map/set使用一模一样,只是底层使用了哈希表实现,这里不演示使用了。

左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

什么是左值?什么是左值引用?

左值是一个数据的表达式,可以对左值取地址,给左值赋值。const修饰后的左值,不能改变他的值。左值引用就是给左值取别名,给左值引用。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	int i = 1; //i是左值
	int* ptr = &i;//给左值取地址
	i = 3;//给左值赋值
	int& j = i;//给左值取别名 就是左值引用

	const int k = 10;//k是const修饰的左值
	//k = 1; const修饰后的左值不允许改变
	const int& l = k;//const修饰左值的引用
	return 0;
}

什么是右值?什么是右值引用?

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

右值引用的语法是 类型&& 比左值引用多一个&

int&& j = 10;

右值引用使用 右值引用能否给左值取别名

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	int i = 2 * 3;//2*3这个表达式就是右值
	int&& j = 10;//给10取别名 右值引用 右值引用的语法是 类型&& 比左值多一个&
	//右值引用 能否给左值取别名呢?
	//int&& k = i; i是左值 右值引用不能直接给左值取别名
	int&& l = move(i); //move之后的左值 右值引用可以取别名
	return 0;
}

左值引用和右值引用比较

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。

  2. 但是const左值引用既可引用左值,也可引用右值。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	//int& i = 2; 权限的放大
	const int& j = 2; //2是常量 不能改变 权限的平移
	return 0;
}

右值引用总结:

  1. 右值引用只能右值,不能引用左值。

  2. 但是右值引用可以move以后的左值。

左值引用使用场景

做参数和返回值都可以提高效率,但不过局部变量就不能使用左值引用,只能使用值拷贝,这是左值引用的短板。

右值引用使用场景

右值引用的产生是为了弥补左值引用的短板,目的是提高效率,主要的使用场景就是移动构造和移动赋值。那什么是移动构造和移动赋值呢?这里先简单理解下,那移动构造举例,当你使用将亡值构造对象的时候,必须要使用值拷贝做一个中介完成构造。使用右值引用做参数,编译器做了特别处理,把将亡值识别成了右值,重载了一个构造函数,使用swap,交换资源。这样就避免了多余的值拷贝,提高效率。移动赋值也是这个原理。

移动构造和移动赋值使用右值引用可以提高效率,本质是资源转移

c++98

c++11

上面的代码在vs2022上是不会调用移动构造的,编译器做了优化,会直接调用构造函数构造s。效率更高一点。vs2019上才会调用移动构造。

右值引用进一步剖析

右值引用属于左值属性,不属于右值属性。

证明

#include <iostream>
#include <vector>
#include <string>
using namespace std;
void _Ref(int& i)
{
	cout << "左值引用" << endl;
}
void _Ref(int&& i)
{
	cout << "右值引用" << endl;
}
int main()
{
	int&& i = 10;
	int j = 1;
	int& k = j;
	_Ref(i);
	_Ref(k);
	return 0;
}

为什么右值引用属于左值,不属于右值呢?右值引用的产生主要就是完成移动构造和移动赋值,实现资源转换,如果定义成右值,那就无法实现资源转换。因此,c++组委会只能定义成左值。

完美转发

模板的右值引用既可以接受右值,也可以直接接受左值,不需要move函数转换。这就是完美转发,模板&&,万能引用。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<typename T>
void _PerfectForward(T&& t)
{
	cout << "完美转发" << endl;
}
int main()
{
	_PerfectForward(10);
	int i = 10;//i是左值
	_PerfectForward(i);
	_PerfectForward(move(i));
	return 0;
}

forward<T>是一个函数模板,作用:完美转发在传参的过程中保留对象原生类型属性

#include <iostream>
#include <vector>
#include <string>
using namespace std;
void _Ref(int& i)
{
	cout << "左值引用" << endl;
}
void _Ref(int&& i)
{
	cout << "右值引用" << endl;
}
template<typename T>
void _PerfectForward(T&& t)
{
	_Ref(forward<T>(t)); //保持变量的原来属性
}
int main()
{
	_PerfectForward(10);//右值
	int i = 10;
	_PerfectForward(i);//左值
	_PerfectForward(move(i));//右值
	return 0;
}

新的类功能

默认成员函数:

构造函数 c++98
析构函数 c++98
拷贝构造函数 c++98
拷贝赋值重载 c++98
取地址重载 c++98(不重要)
const 取地址重载 c++98(不重要)
移动构造c++11
移动赋值c++11

移动构造和移动赋值规则:

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

default和delete关键字:

强制生成默认函数的关键字default 和 禁止生成默认函数的关键字delete

#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Person
{
public:
	Person() {};
	Person(string& name,int age)
		:_name(name),
		_age(age)
	{

	}
	Person(Person&& p) = default; //强制生成移动构造(默认函数)
	Person(const Person& p) = delete;//禁止生成拷贝构造(默认函数)
private:
	string _name;
	int _age;
};

可变参数模板

可变参数在c语言就出现过,就是我们熟悉printf和scanf函数,他么使用就是可变参数。在c++中我们把可变参数提升到了模板级别,叫可变参数模板。

//Args是一个模板参数包
//args是一个函数形参参数包
//声明一个参数包 Args ..args 这个参数包可以包含0到任意个参数
template<class ...Args>
void CppPrint(Args...args)
{

}

展开参数包有两种方式:函数递归终止和逗号表达式

函数递归终止

#include <iostream>
#include <vector>
#include <string>
using namespace std;
//Args是一个模板参数包
//args是一个函数形参参数包
//声明一个参数包 Args ..args 这个参数包可以包含0到任意个参数
template<class T>
void CppPrint(T _t)
{
	cout << _t << endl;
}
template<class T,class ...Args>
void CppPrint(T _t, Args...args)
{
	cout << _t << " ";
	CppPrint(args...);
}

int main()
{
	CppPrint(1);
	CppPrint(1,2);
	CppPrint(1,2,3);

	return 0;
}

逗号表达式

#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args),0)... };
	//(PrintArg(args),0)...展开就是
	//(PrintArg(arg1),0) (PrintArg(arg2),0) (PrintArg(arg3),0)
	cout << endl;
}

应用

他会避免多余的构造函数,会把你的参数,一直传下去,调用构造函数。对于内置类型来看没啥用,对于自定义类型,方便你传值。这里优化构造,其实也没有多优化。毕竟实现了移动构造,调用一次移动构造,消耗极低。

Lambda表达式


语法:

1.捕捉列表 -- 捕捉外界变量

  • [x] 传值捕捉x变量
  • [&x] 引用捕捉x变量
  • [=] 传值捕捉所有变量
  • [&] 引用捕捉所有变量

2.mutable 取消捕捉变量const修饰

3.参数:(无参可省略)

4.返回值: (一般都可省略)

5.函数体: 不可以省略

Lambda定义,用auto来推导他的类型

使用:和仿函数一样

#include <iostream>
using namespace std;
int main()
{
	auto add = [](int x, int y)->int {return x + y; };
	int ret = add(1, 2);
	cout << ret << endl;//ret = 3;
	return 0;
}

参数和返回值省略

#include <iostream>
using namespace std;
int main()
{
	auto add1 = [](int x, int y){return x + y; };//返回值可省略

	auto func = []{};//没有参数 参数也可以省略
	return 0;
}

捕捉变量使用场景

#include <iostream>
using namespace std;
void Func()
{
	cout << "全局变量Func" << endl;
}
auto sub = []{ cout << "全局变量sub" << endl; };
int main()
{
	//lambda表达式add的函数体中可以使用其他函数吗?
	auto add = []
		{
			Func();
			sub();
		};
	add();
	return 0;
}

#include <iostream>
using namespace std;
int main()
{
	auto sub = [] { cout << "局部变量sub" << endl; }; //局部变量
	auto add = []
		{
			sub();
		};
	add();
	return 0;
}

lambda表达式里默认不可以使用局部变量,可以使用全局变量。上面的代码会直接报错,无法运行。那如何使用局部变量呢?使用捕捉列表

#include <iostream>
using namespace std;
int main()
{
	auto sub = [] { cout << "局部变量sub" << endl; };
	auto add1 = [sub] //捕捉sub变量 值拷贝
		{
			sub();
		};
	add1();
	int i = 0;
	auto add2 = [=] //捕捉所有变量 值拷贝
		{
			sub();
			//i++; 值拷贝默认捕捉变量都是const修饰
			cout << i << endl;
		};
	add2();
	auto add3 = [&] //引用捕捉所有变量 
		{
			i++;//是否为const修饰取决于变量本身
		};
	add3();
	cout << i << endl;
	return 0;
}

mutable使用场景

他的作用就是取消从const修饰的功能,值拷贝使用。

#include <iostream>
using namespace std;

int main()
{
	int a = 1;
	auto func1 = [=]() mutable {a++; }; //能修改 不影响外界值
	func1();
	cout << a << endl;
	auto func2 = [&]() {a++; }; //能修改 影响外界值
	func2();
	cout << a << endl;
	return 0;
}

lambda的底层

lambda的底层是仿函数:

#include <iostream>
using namespace std;


int main()
{
	auto add1 = [](int x, int y) {return x + y; };
	add1(1, 2);
	auto add2 = [](int x, int y) {return x + y; };
	add2(1, 2);
	// add1 = add2 不可以 原因:类型不同
	return 0;
}

这里从汇编就可以看得出来

包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

function使用:

#include <iostream>
#include <vector>
#include <string>
#include <functional>
using namespace std;
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
	// 函数名 函数指针
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	cout << endl;
	//把可调用对象存储到一个容器中
	//包装器
	vector<function<double(double)>> v = { f,Functor(),[](double d)->double { return d / 4; } };
	for (auto& e : v)
	{
		cout << useF(e, 11.11) << endl;
	}
	return 0;
}

包装器可以把不同类型包装成同一个类型,所以上面的useF函数经过包装器之后就之后生成一份,不会生成三份。

bind绑定参数

配合function使用,改变参数位置

bind使用

#include <iostream>
#include <vector>
#include <string>
#include <functional>
using namespace std;
int _sub(int x, int y)
{
	return x - y;
}

int main()
{
	function<int(int, int)> func1 = bind(_sub, placeholders::_2, placeholders::_1);
	int ret = func1(5, 10);//5 - 10 ?
	cout << ret << endl;//5
	return 0;
}

bind绑定参数除了通过placeholders指定,还可以直接上传参数

bind(_fun,1, placeholders::_2, placeholders::_1);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值