C++11重要特性总结

1.统一列表初始化{}

使用

在C++98时期,使用{}只能初始化数组,结构体。如下:

struct XX
{
	int x;
	int y;
};
int main()
{
	
	int a[] = { 0,1,2,3,4 };
	int b[5] = { 0 };
	XX x = { 1,2 };
	return 0;
}

C++11为了方便我们统一列表初始化数据:

  1. 使用{}可以初始化所有的内置类型和用户自定义类型。并且=可以省略
struct XX
{
	int x;
	int y;
};
int main()
{
	//C++98
	int a[] = { 0,1,2,3,4 };
	int b[5] = { 0 };
	XX x = { 1,2 };

	//C++11
	int aa[]{ 0, 1, 2, 3, 4 };
	int bb[5]{ 0 };
	XX xx{ 1,2 };

	//C++11中初始化列表也可以适用于new表达式中
	// pa指向int数组的第一个元素,是整形的。
	//将三个数都初始化为1
	int* pa = new int[3]{ 1, 1, 1};

	return 0;
}
  1. 创建对象时也可以使用{}来调用构造函数

	string str("hello world");  //c++98

	string str1 = { "hello world" };
	string str2{ "hello world" };

	map<int, string> m1 = { {1, "小明"}, {2, "小红"} };
	map<int, string> m2{ {1, "小明"}, {2, "小红"} };

原理

使用{}构造函数,其实是事先生成了一个initializer_list对象。
在这里插入图片描述
然后我们stl容器,都支持使用initializer_list对象调用构造:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因此我们实际上使用{}时,是先生成initializer_list对象,然后再构造。

2. 声明

关键字 auto

自动推断类型:非常方便
在这里插入图片描述

关键字 decltype

将变量的类型声明为指定类型:

  1. 可以使用该关键字声明变量
  2. 也可以使用该关键字创建指定类型的对象
	const int a = 10;
	double b = 1.1;

	decltype(a * b) ret;   //ret类型为 double
	decltype(&a) p;       // p的类型为 const int*
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;

	vector<decltype(a* b)> v;   //v的类型为double
	cout << typeid(v).name() << endl;

在这里插入图片描述

nullptr

在这里插入图片描述

3. 范围for

可以使用该语法遍历容器或者数组

	vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

在这里插入图片描述

5. stl增加的容器

在这里插入图片描述

6. 右值引用

左值和右值的区分

左值:可以取地址,可以对它赋值,可以出现在赋值符号的左边。
右值:不可取地址,不能出现在赋值符号的左边。

注意:定义时const修饰的后的左值,不能给它赋值,但是可以取地址,因此它是左值。

如下左值引用和右值引用例子:

	//下面都是左值
	int a = 10;
	int* p = &a;
	const int b = 10;

	//左值的引用
	int& ra = a;
	int*& rp = p;
	const int& rb = b;

	///
	int x = 10;
	int y = 20;
	int add(int, int);

	//下面都是右值
	20;     //字面常量
	x + y;    //表达式返回值
	add(x, y);  //函数返回值

	//下面是右值引用
	int&& name1 = 20;
	int&& name2 = x + y;
	int&& name3 = add(x, y);


加上const可以实现,左值引用给右值取别名:

	const int& cc = 10;
	cout << cc;    //  10

使用move函数,可以实现右值引用给左值取别名:

	int a = 10;
	int&& b = move(a);  //使a暂时变为右值
	cout << b;   // 10

左值和右值引用场景

看下面这段代码:分别是普通的左值引用构造,右值引用构造(注意看形参就可以区分)
然后主要介绍左值引用解决的什么情况,右值引用解决了什么情况。


		//拷贝构造函数
		string(const string& str)
			:_size(str._size)
			, _capacity(str._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str._str);
		}

		//移动构造
		string(string&& str)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			cout << "string(string&& str)--移动构造" << endl;
			swap(str);
		}



		//原赋值函数
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tem = new char[s._capacity + 1]; //先申请新空间
				strcpy(tem, s._str);   //拷贝
				//没有抛异常,往下执行
				delete[] _str;//释放空间
				_str = tem;   //赋回来

				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}


		//移动赋值
		string& operator=(string&& s)
		{
			if (this != &s)
			{
				cout << "string& operator=(string&& s)---移动赋值" << endl;
				swap(s);
			}
			return *this;
		}

移动构造

  • 左值引用解决的问题:直接减少拷贝。1. 左值引用传参 2.传引用返回(函数内的局部对象不能用传引用返回,因为出作用域会销毁)
  • 左值引用短板:如果是局部对象,就不能传引用返回,只能传值返回,传值返回(这样至少会产生一次拷贝构造)
    在这里插入图片描述

在这里插入图片描述

  • 右值引用本质上将参数右值的资源窃取过来,占为己有,(那么就不需要申请资源了),swap实现了这个功能。因为将亡值或者临时变量,本来就是要删除的数据,将这个数据直接返回,更高效。
  • 右值引用可以解决上面的资源浪费的问题:增加了移动构造后,因为返回值是将亡值,所以会被识别成右值,调用移动构造。
    在这里插入图片描述

移动赋值

当编译器识别出要赋值的参数为右值时,自动调用移动赋值,减少了拷贝,提高效率。
在这里插入图片描述

右值引用move()

将一个左值强制转化为右值
std::move()

下面例子将s1强制识别成右值,运行完之后,s1的数据被s2掠夺。
在这里插入图片描述

完美转发forward()

模板中的万能引用

模板中的&&不表示右值引用。它可以作推理使用:
当传进来左值时,折叠一个&,自动识别成左值引用。
当传进来右值时,识别成右值引用。
如下所示:

template<class T>
void Test(T&& t)
{
	Fun(t);
}
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;
}
int main()
{

	int a = 10;
	Fun(a);   //左值引用
	Fun(move(a));  //右值引用
	return 0;
}

forward作用

实际上,当模板识别成右值时,为了swap会自动把右值转换成左值(为了掠夺资源,变成左值),但是有时候我们可能需要保持它的右值属性(我们暂时不掠夺呢,把它传到应该掠夺的部分)。所以我们应该维持它的右值属性,继续往下传递。

使用forward可以保持右值属性不变,继续传下去。
在这里插入图片描述
按道理来说,上面小节的测试,应该都是左值引用,因为在Test里被识别成左值了。但是编译器应该做了一些处理,导致右值属性被保留。


工程中涉及到完美转发的情形就是利用forward一直保留变量的属性,直至进行资源掠夺阶段。

7. 新的类成员函数

移动构造和移动赋值的知识背景是右值引用部分。

移动构造

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

移动赋值

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

强制生成默认函数的关键字default

假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如: 我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
语法如下:

class string
{
//必须生成一个默认的移动构造
	string(string&& s) = default;
}

禁止生成默认函数的关键字delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class string
{
//不再生成该移动构造函数
	string(string&& s) = delete;
}

8. lambda表达式

lambda表达式是C++借鉴其他语言所得到的产物。
其底层实现逻辑类似于仿函数的逻辑。
产生原因:
每次写仿函数都要实现一个类,很麻烦。

lambda语法

[capture-list] (parameters) mutable -> return-type {statement}

  • [capture-list]:捕捉列表,编译器根据[]来判断下边的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表,与普通函数参数列表一致,如果不需要传递参数,可与()一同省略
  • mutable:默认情况下,lambda函数总是const函数,mutable可以取消其常量性,使用该修饰符时,参数列表不可省略(即使参数为空)
  • -> return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
  • {statement}:函数体。可以使用捕获到的变量

注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

使用示例:

struct Stu
{
	string _name;
	int _age;
};
int main()
{
	vector<Stu> v{ {"zhangsan", 10}, {"lisi", 20}, {"wangwu", 3} };

	//lanmda表达式,按年龄排序
	sort(v.begin(), v.end(), [](Stu s1, Stu s2)->bool {
		return s1._age < s2._age; });

	//定义一个lambda表达式,按字符串排序
	auto com = [](Stu s1, Stu s2)->bool {return s1._name < s2._name; };
	sort(v.begin(), v.end(), com);

	return 0;
}

捕捉列表具体介绍[]

捕捉列表描述了上下文哪些东西可以被lambda使用,以及实用的方式是传值还是传引用。

  • [var] :表示传递的方式是捕捉变量
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用的捕捉变量(不是取地址!!)
  • [&=]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递的方式,捕获当前this指针

注意:

  • 父作用域是包含lambda函数的语句块
  • 捕捉列表可以灵活使用,以逗号分隔
    比如:[=,&a,&b]:以引用的方式捕捉变量ab,剩下的按值传递。
    [&, a, this]:a和this按值传递捕捉,其他变量按引用捕捉
  • 捕捉列表不允许重复捕捉,否则会编译错误
    比如:[=, a]就会重复捕捉a
  • 块作用域以外的lambda函数捕捉列表必须为空
  • lambda表达式不能互相赋值

在这里插入图片描述

函数对象(仿函数)

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator0运算符的类对象。

class Mutil
{
public:
	int operator()(const int& x, const int& y)
	{
		return x * y;
	}
};

int main()
{

	Mutil t1;
	cout << t1(5, 6) << endl;

	auto t2 = [](int x , int y) {return x * y; };
	cout << t2(5, 6) << endl;


	return 0;
}
  1. 首先仿函数和lambda使用方法完全类似,并且底层汇编i代码,调用也类似
    在这里插入图片描述

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

9.可变参数模板Args

首先看一下可变参数模板的示例:

//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0~n任意个模板参数
template<class ...Args>
void ShowList(Args... args)
{
	//语法就是这样的
	cout << sizeof...(args) << endl;
}

int main()
{
	ShowList();
	ShowList('a');
	ShowList('a','b');
	ShowList('a','b',1,2);
	return 0;
}

运行结果:

在这里插入图片描述
可知可变参数包可以接收任意个数的参数。


上面的args的前面有省略号,表示args一个可变的模板参数,我们把带有省略号的参数称为称为“参数包”,它里面包含了0~N个模板参数。

我们无法直接获取args的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数。

下面举例,如何展开参数包里的参数:

int N = 0;
template<class T>
void ShowList(const T& val)
{
	//语法就是这样的,必须有一个单参数的,结束递归
	cout << "单参函数打印:" << val << endl;
}


//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args...args,这个参数包中可以包含0~n任意个模板参数
template<class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	//语法就是这样的
	N++;
	cout << val << endl;
	ShowList(args...);
}

int main()
{
	ShowList('a','b',1,2);
	cout << N;
	return 0;
}

在这里插入图片描述
代码解释:
首先main函数传进去四个参数,一个给T&模板,剩下三个给args函数包。以此类推,直到函数包只有一个参数2,最后一个参数传递,必须再写一个递归终止函数,否则会报错。

使用逗号表达式展开可变参数包

//template<class T>
//int PrintArg(T t)
//{
//	cout << t << " ";
//	return 0;
//}

template<class T>
void PrintArg(T t)
{
	cout << t << " ";
}



template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args),0)... };
	//int arr1[] = { PrintArg(args)... };
	cout << endl;
}


//上面的showlist会推演成下面的表达式:

template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(1),0),(PrintArg('a'),0)
		,(PrintArg(2),0),(PrintArg(string("123214")),0)};
	//int arr1[] = { PrintArg(args)... };
	cout << endl;
}

int main()
{


	ShowList(1,'a', 3, string("123214"));
	return 0;
}

在这里插入图片描述

在这里插入图片描述

11.包装器

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

引入背景

ret = func(x)

上面这段代码func的具体类型是什么样的?func可能是函数?函数指针?函数对象(仿函数)?也有可能是lambda表达式对象?这些都是可调用的类型!

这些在一起会导致模板效率低下,如下:

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 y)
	{
		return y / 3;
	}
};


int main()
{
	//函数名
	cout << useF(f, 11.11)<<endl;

	//函数对象
	cout << useF(Functor(), 11.11) << endl;

	//lambda公式
	cout << useF([](double d)->double {return d / 4; }, 11.11);

	return 0;

}

在这里插入图片描述
由上面的代码可知,count有三个,即useF实例化出了三份代码,分别是函数指针类型的、仿函数类型的、lambda表达式类型的。包装器可以很好解决上面的问题。


#include<functional>

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 y)
	{
		return y / 3;
	}
};


int main()
{
	函数名
	//cout << useF(f, 11.11)<<endl;

	函数对象
	//cout << useF(Functor(), 11.11) << endl;

	lambda公式
	//cout << useF([](double d)->double {return d / 4; }, 11.11);


	//函数名
	function<double(double)> f1 = f;

	//仿函数
	function<double(double)> f2 = Functor();

	//lambda公式
	function<double(double)> f3 = [](double d)->double {return d / 4; };

	cout << useF(f1, 11.11) << endl;
	cout << useF(f2, 11.11) << endl;
	cout << useF(f3, 11.11) << endl;
	return 0;

}

在这里插入图片描述

使用functional包装后,模板只实例化出一份。

包装器使用方法

在这里插入图片描述
记得要实例化成:返回值(参数)的格式。

下面是:普通函数,仿函数,lambda表达式,类成员函数,类静态成员函数的包装方法。

#include<functional>

int f(int a, int b)
{
	return a + b;
}


struct Functor
{
	int operator()(int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}

};



int main()
{
	//函数名
	std::function<int(int, int)> f1 = f;
	cout << f1(1, 2) << endl;


	//函数对象
	std::function<int(int, int)> f2 = Functor();
	cout << f2(1, 2) << endl;

	//lambda表达式
	std::function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };
	cout << f3(1, 2) << endl;

	//类的静态成员函数(无this指针)
	std::function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 2) << endl;


	//类的动态成员函数(得传入this指针)
	std::function<double(Plus, double, double)> f5 = &Plus::plusd;
	cout << f5(Plus(), 1.1, 2.0) << endl;
	return 0;
}

bind

bind是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
功能1:原本n个参数的函数->需要m个参数的函数
功能2:调整参数顺序

类的声明如下:
在这里插入图片描述

auto newFunc = bind(func, arg_list);

newFunc是一个新的可调用对象,arg_list是func的参数列表。当调用newFunc的时候,newFunc会调用func,并把arg_list中的参数传给func。

如下例子:

#include<functional>

int Plus(int a, int b)
{
	return a - b;
}


class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};


int main()
{
	//表示绑定函数Plus, 其中原函数的第二个参数被放到了第一个位置
	//原函数的第一个参数被放到了第二个位置
	function<int(int, int)> func1 = bind(Plus, placeholders::_2, placeholders::_1);
	//auto func1 = bind(Plus, placeholders::_2, placeholders::_1);  

	cout << func1(1, 2) << endl;   //因为参数颠倒了,变为了 2-1


	//此函数是,绑定了第二个参数,第一个参数需要自己指定
	auto func2 = bind(Plus, placeholders::_1, 1);
	cout << func2(2) << endl;


	//此函数是,绑定了第1个参数,第二个参数需要自己指定
	auto func3 = bind(Plus, 1, placeholders::_1);
	cout << func3(2) << endl;

	//此函数绑定了成员函数,需要制定类域的函数地址,需要传入this指针
	auto func4 = bind(&Sub::sub, Sub(), 1, 2);
	cout << func4() << endl;

	//此函数传入了对象的地址
	Sub s;
	auto func5 = bind(&Sub::sub, &s, 1, 2);
	cout << func5() << endl;
}
  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值