学习->C++篇二十:C++11

目录

简介

统一列表初始化

简化声明方式

auto

decltype

nullptr

右值引用

概念

应用场景

完美转发

可变参数模板

新增类功能

lambda表达式

原理

语法格式

函数包装器


简介

        c++11是c++标准十年磨一剑产生的一个重要的标准,增加了很多新的特性,可以提高程序员的开发效率,使使用更加的简单,稳定,安全,功能更加强大,更好的运用于系统开发和库的开发,也同时使语法变得更像一门全新的语言。

        之前的篇幅已经谈过范围for、智能指针等,这些都是C++11新增的内容,在STL中,C++11新增array,forward_list,unordered_map,unordered_set容器和对应方法,array使用较少,forward_list是用单链表实现的。unordered_map和unordered_set的底层是用哈希表实现的,前面已经谈过,本篇不再赘述。

统一列表初始化

C++98标准允许用花括号对数组还有结构体的元素进行列表初始值的设定,C++11扩大了大括号的使用范围,使其可以用于所有的内置类型,还有用户自定义的类型,初始化的时候还可以省略等号。

创建对象可以使用列表初始化方式调用构造函数进行初始化对象

其实这一种列表初始化的方式,C++11新增的一个模板类型叫做initializer_list,花括号中用逗号分隔的元素的类型是相同的:

C++11对容器增加了initializer list作为参数的构造函数,这样子方便于初始化容器,比如:

使用示例:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<utility>
#include<vector>

using std::vector;
using std::pair;
using std::cout;
using std::endl;

struct point
{
	int _x;
	int _y;
	point(int x, int y) :_x(x), _y(y) {}
};
int main() 
{
	int a1 = 2;
	int a2{ 2 };
	int arr1[3] = { 1,2,3 };
	pair<int, int>p1(1, 2);
	vector<int>va(3, 0);
	point po1(1, 2);
	//  C++11
	int arr2[3]{ 1,2,3 };
	int arr3[]{ 1,2,3 };//动态数组,C++98不支持
	int* arr4 = new int[4] { 1, 2, 3, 4 };
	pair<int, int>p2{ 1,3 };
	vector<int>vb{ 1,2,3 };
	point po2{ 1,2 };

	return 0;
}

简化声明方式

auto

C++98在中auto是表明一个存储局部自动存储类型的,局部变量默认具有,所以没有什么价值

C++11将其用于自动推断类型,常用在范围for中,还常用于自动推导较长的类型

使用示例:

decltype

将变量的类型声明为表达式指定的类型,或者用于将变量转化为其类型,比如:

	double a = 1.0;
	int b = 2;
	decltype(a * b) c = 2.0;

推导出的c的类型也是double

nullptr

由于历史原因C++中NULL被定义为0,为了安全新增了nullptr表示空指针((void*)0):

右值引用

概念

左值:

        数据表达式(变量或解引用的指针),可以取地址,可以赋值

        const修饰的左值不可赋值,但可取地址

右值:

        数据表达式(字面常量,表达式返回值,函数返回值)

        不能赋值,不能取地址

左值引用:

        左值的别名

        只能引用左值,不能引用右值,const左值引用对于左值右值都可以引用

右值引用:

        右值的别名

        被存储到特定位置,可以取地址

        只能引用右值,不能引用左值,可以引用move后的左值

应用场景

左值引用:

        左值引用做参数和返回值都可以提高效率

        缺点:函数返回局部对象时不能左值引用返回,只能传值返回,导致至少一次拷贝构造

右值引用:

        解决左值引用短板

        string类,STL容器等新增移动构造函数和移动赋值函数

比如:

string类中新增移动构造,构造对象的时候将参数的资源窃取,不用做深拷贝,提高了构造的效率,底层实现是完成了底层指针的交换;string类新增移动赋值,将右值对象赋值给已存在的对象,将二者资源交换完成赋值,底层也是进行了指针的交换,效率很高。

移动构造函数:

移动赋值函数: 

完美转发

在模板中的&&不代表右值引用,而是万能引用,既能接收左值,也能接收右值

模板后续使用参数时,都会退化成左值:

 使用forward函数模板进行参数的完美转发,保留参数的原生类型属性:

可变参数模板

C++11之前模板的参数个数是有限的,C++11提供了可变个数的模板参数,但是语法较为特殊,使

用示例:

template<class...Args>
void ShowList(Args... args)//传递任意个数的参数
{
	//求参数个数
	cout << sizeof...(args) << endl;
}
int main()
{
	int a = 0;
	double b = 1.1;
	vector<int>c;
	ShowList();
	ShowList(a);
	ShowList(a, b);
	ShowList(a, b, c);
	return 0;
}

输出:

 Args是一个模板参数包,args是一个函数形参参数包,含0到任意个模板参数,使用sizeof...(args)求模板参数的个数。

倘若要取参数包中的参数,可以递归取参数包,记得要有递归终止函数,因为函数模板在编译期的时候生成函数,如果无限递归,就会发生编译错误。

template<class T>
void fun(T t)
{
	cout << t << endl;
}

template<class T,class ...Args>
void fun(T a,Args... args)
{
	cout << a << endl;
	fun(args...);
}

int main()
{
	fun(1, 1.1, 'a');
	return 0;
}

输出:

还可以使用逗号表达式展开参数包:

template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', "hello");
	ShowList(1, 'A', "hello",string("world"));
	return 0;
}

 输出:

上面代码同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(PrintArg(args), 0)...}将会展开成{(PrintArg(arg1),0), (PrintArg(arg2),0), (PrintArg(arg3),0), ... )},最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。逗号表达式在创建数组的过程中会先执行逗号表达式前面的部分PrintArg(args) 打印出参数,这个数组的目的只是为了在数组构造的过程展开参数包。

C++11新增了一些模板函数,函数原型采用了可变参数模板,比如:

 部分容器新增了emplace_back函数模板,函数模板支持可变参数,所以可以这么使用:

list<pair<int,string>>mylist;

mylist.emplace_back(1,“sort”);

新增类功能

新增移动构造和移动赋值函数:前面提到了右值引用的应用场景,C++11的类中新增的两个成员函数,是C++11新增的,在原来C++类中有6个成员函数。

对于移动构造和移动赋值函数编译器会自动生成,但自动生成的条件较为苛刻:

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

总结:

1.未实现且未实现析构函数,拷贝构造,赋值重载时才会自动生成

2.默认生成的,对内置类型逐字节拷贝,对自定义类型若实现移动..函数就调用,没有实现就调用拷贝构造

3.若实现其中一个,编译器不自动提供拷贝构造和拷贝赋值

还有包含之前篇提到过的:

成员变量类内给初始缺省值

default:强制编译器生成默认函数关键字

delete:禁止编译器生成默认函数关键字

final:实现最终类的关键字,当修饰虚函数时则虚函数不能被重写

override:用在派生类虚函数中,用于虚函数是否重写了基类某个虚函数

lambda表达式

在C++11之前,要进行自定义类型的排序,需要自己实现一个类及其仿函数,需要为类命名,较为麻烦。C++11提供了一个简便的方法,就是使用lambda表达式:

C++11之前:

struct Goods
{
	string name_;  // 名称
	double price_; // 价格
	int evaluate_; // 评价
	Goods(const char* str, double price, int evaluate)
		:name_(str)
		, price_(price)
		, evaluate_(evaluate)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl.price_ < gr.price_;
	}
};

struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl.price_ > gr.price_;
	}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

C++11之后:

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };

	sort(v.begin(), v.end(), [](const Goods& g1,const Goods& g2) 
        {return g1.price_ < g2.price_; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) 
        {return g1.price_ > g2.price_; });
}

原理

在sort函数中传入的是匿名对象或lambda表达式,其实lambda表达式就是匿名函数或函数对象。

底层编译器通过函数对象方式处理,自动生成一个类,类名为lambda_uuid(生成的全局唯一标识符),重载了operator(),从汇编代码可以看到(不同编译器实现不尽相同):  

语法格式

[捕捉列表](参数列表)mutable->返回值类型{语句}

捕捉列表:捕捉上下文变量供lambda表达式使用

[var]值传递捕捉变量var

[=]值传递捕获所有父作用域的变量,包括this(父作用域指包含lambda函数的语句块)

[&var]引用传递捕获变量var

[&]引用传递捕获所有父作用域的变量,包括this

[this]值传递捕获当前this

捕捉列表可有多项,以逗号隔开,比如:

[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

捕捉列表不允许变量重复传递

块作用域外的lambda函数捕捉列表必须为空

只能捕获父作用域中的局部变量,否则报错

参数列表:如果不需要参数传递,则可以连同()一起省略

mutable:lambda表达式参数默认带有const属性,使用mutable可以取消lambda函数默认的const属性,使用时参数列表不能省

->返回值类型,返回值类型明确时可省略,由编译器自动推导

{语句}:语句中可以使用参数和所以捕获到的变量

表达式之间不能相互赋值,即使类型看起来相同

(原因是底层生成的类都不同,每个lambda都是独一的):

auto f1 = []{cout << "hello world" << endl; };

auto f2 = []{cout << "hello world" << endl; };

//f1 = f2;error

函数包装器

function包装器又叫适配器,function是一个类模板,可以用来解决函数模板实例化多份的问题

看下面代码:

template<class F, class T>
T useF(F f, T x)
{
	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;
	return 0;
}

useF是函数模板,被调用了三次,会生成三个函数实例,因为函数、函数对象,lambda表达式,虽然本质类似,但是类型是不同的,它们都是可调用类型对象,要是useF函数模板能将它们当做一个类型看待,就只会生成一份函数实例了。

统一可调用类型对象:

函数指针,函数名

仿函数对象

lambda表达式

使用包装器解决上面的问题(引入头文件<functional>):

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

通过包装器,useF函数模板看到的func1和func2、fun3的类型都是相同的,这样一来就只会产生一份函数实例了。

函数原型:

 class function<Ret(Args...)>中Ret是被调用函数返回值类型,Args是被调用函数形参列表

可以使用bind函数模板搭配包装器使用,主要是为了调整函数参数个数和参数的顺序:

bind是一个函数模板,就像函数适配器,接收一个可调用对象,生成新的适配参数列表的可调用对象。

注意绑定类内成员函数时,要先传成员函数的默认隐含的参数this指针。

 使用示例:


#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,其参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, std::placeholders::_1,
		std::placeholders::_2);
	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	//func2的类型为 function<void(int, int, int)> 与func1类型一样
	//表示绑定函数 plus 的第一,二为指定的数值: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2() << endl;//参数数值被bind了,调用func2就一直是1+2了
	Sub s;
	// 绑定成员函数,传入this指针的类型
	std::function<int(int, int)> func3 = std::bind(&Sub::sub, &s,
		std::placeholders::_1, std::placeholders::_2);
	// 参数调换顺序
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, &s,
		std::placeholders::_2, std::placeholders::_1);
	cout << func3(1, 2) << endl;//相当于1-2
	cout << func4(1, 2) << endl;//参数顺序调整了,相当于2-1
	return 0;
}

总结:

可以用实际值绑定所传递参数

可以用placeholders::调换形参顺序,也可以调整形参个数

适配成员函数时,调整形参顺序前要先传一个this指针

C++11还封装了线程库和其他内容,后续介绍......

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值