【C/C++】C++11

发布于:2022-12-07 ⋅ 阅读:(1362) ⋅ 点赞:(0)


在这里插入图片描述

1.列表初始化

列表初始化而不是初始化列表,列表初始化是C++11新增的初始化方式,而初始化列表是类的构造函数对成员变量进行初始化。

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:

int array[]={1,2,3,4,5};
int array[5]={0};

但是对于一些容器或者自定义类型无法直接使用花括号{}进行初始化,比如

struct Date
{
    int year_;
    int month_;
    int day_;
}
vector<int> arr={1,2,3,4,5}
Date d={2022,9,20}

上面的初始化方式,编译器都无法编译通过。导致每次初始化自定义类型或者容器的时候都需要使用循环或访问类的成员变量才能够初始化赋值。

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

1.1C++11的列表初始化

内置类型的列表初始化

int x1=1;
int x2={2};
int x3{3};
int array[]={1,2,3,4,5,6};
int array[5]{0};
Date d{2022,9,20};

int* p1=new int(4);
int* p2=new int[3]{1,2,4};

// 标准容器
vector<int> v{1,2,3,4,5};
map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}}

自定义类型列表初始化

class Point
{
public:
	Point(int x = 0, int y = 0): _x(x), _y(y)
		{}
private:
	int _x;
	int _y;
};
Pointer p{ 1, 2 };
vector<Date>d={{2012,9,20},{2022,9,20},{2015,9,16}};
map<string, string> dict = { { "string", "字符串" }, { "sort", "排序" } };

1.2initializer_list

template<class T> 
class initializer_list;

​ 多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_lis类型参数的构造函数即可

在这里插入图片描述

auto li={1,2,3,4};
cout<<typename(li)<<endl;

class std::initializer_list<int>
    
initializer_list<double> ilt={3.3,4.5,9.9,5.5};
initializer_list<double>::iterator it=iit.begin();
while(it!=ilt.end())
{
    cout<<*it<<" ";
}
cout<<endl;
for(auto e:ilt)
{
    cout<<e<<" ";
}
cout<<endl;

输出:

3.3 4.5 9.9 5.5
3.3 4.5 9.9 5.5

1.3支持列表初始化的原理

比如vector的底层重载了initializer_list类型的构造函数和赋值重载

#include <initializer_list>
template<class T>
class Vector {
public:
// ...
	Vector(initializer_list<T> l): _capacity(l.size()), _size(0)
	{
		_array = new T[_capacity];
		for(auto e : l)
		_array[_size++] = e;
	}
	Vector<T>& operator=(initializer_list<T> l) 
    {
		delete[] _array;
		size_t i = 0;
		for (auto e : l)
		_array[i++] = e;
		return *this;
	}
// ...
private:
	T* _array;
	size_t _capacity;
	size_t _size;
}

其他类型的内置类型、模板或者自定义类型都重载了initializer_list类的构造函数和赋值重载。

2.变量类型推导

auto可以自动推导变量类型,使用的很多,不再赘述。

2.1decltype

decltype是根据表达式的实际类型推演出定义变量时所用的类型。

1.推导表达式作为变量定义类型

int a=10;
int b=20;

decltype(a+b) c;
cout<<typeid(c).name()<<endl;
输出:int

2.推演函数返回值的类型

void* getmemory(size_t size)
{
    return malloc(size);
}

//如果函数没有带参数,推导函数本身的类型
cout<<typeid(decltype(getmemory)).name()<<endl;
输出:void * __cdecl(unsigned int)
    
    
//如果带函数的参数列表,推导函数返回值的类型。注意:这里不会执行函数,只是推导函数
cout<<typeid(decltype(getmemory(0))).name()<<endl;
输出:void *

2.2nullptr

在C++98及以前,NULL的底层就是0;0即是NULL,又是字面常量,考虑到安全性的问题,C++11引入了nullptr。

3.默认成员函数控制

​ 在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本

​ 最常见的是声明了带参数的构造函数,必要时则需要定义不带
参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成

3.1显示缺省函数default

​ 在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。

class A
{
public:
	A(int a): _a(a)
	{}
	// 显式缺省构造函数,由编译器生成
	A() = default;
	// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
	A& operator=(const A& a);
private:
	int _a;
};
A& A::operator=(const A& a) = default;

3.2删除默认函数delete

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

class A
{
public:
	A(int a): _a(a)
	{}
// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
	A(const A&) = delete;
	A& operator(const A&) = delete;
private:
    int _a;
};

4.右值引用

4.1左值及左值引用

  • 左值是一个表示数据的表达式(如变量名或者解引用的指针),我们可以获取左值的地址或者对左值赋值,
  • 左值可以出现在=的左边也可以出现在=的右边
int a=10;	//a是左值,出现在=的左边
int b=a;	//b是左值,a是左值,a出现在=的右边  
  • const修饰过后的左值,虽然无法对他赋值,但是可以取地址

左值引用就是对左值的引用。左值引用在函数传参等地方,可以大大的提高C++的传参效率,而不用去调用拷贝构造或者赋值的运算符重载。

//string类的拷贝构造
string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

4.2右值及右值引用

  • 右值也是表示数据的表达式,如字面常量、表达式返回值和函数返回值【后面两个都是临时变量】
  • 右值只能出现在赋值符号的右边,不能出现在赋值符号的左边
  • 右值不能被取地址

右值引用就是对右值的引用。用const修饰的右值引用无法被修改

int x=4,y=5;
//下面的都是常见的右值
10;	//字面常量
x+y;	//表达式返回值
func(x,y);	//函数返回值


//对右值的右值引用
int&& rr1=10;
int&& rr2=x+y;
int&& rr3=func(x,y);

关于右值引用

​ 右值不能被取地址,但是右值引用对右值取别名后,会导致右值被存储在特点定的位置,且可以被取导致。也可以说右值引用是一个左值

int&& rr1=10;
int&& rr2=x+y;
int&& rr3=func(x,y);

rr1=20;
rr2=30;
const int&& rr3=x+y;
4.2.1纯右值和将亡值

​ C++11对右值进行了更细的划分。将字面常量认为是纯右值;表达式结果和函数返回值认为是将亡值

1.纯右值:10 a+b
2.将亡值:
    string("1111111") to_string(1234) s1+"hello"
4.2.2move函数

move(左值)可以把左值强制类型转换为一个右值。比如右值引用可以引用move后的左值

int tmp=10;
int&& rr5=move(tmp);	//右值引用引用move后的左值

注意:

  • 被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量不会被销毁。
int main()
{
	String s1("hello world");
	String s2(move(s1));
	String s3(s2);
    cout<<"s1:"<<s1<<endl;
    cout<<"s2:"<<s2<<endl;
    cout<<"s3:"<<s3<<endl;
	return 0;
}

输出:

s1:
s2:hello world
s3:hello world

以上代码,move将s1转化为右值后,在实现s2的拷贝时就会使用移动构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符串 。

4.3左值和右值的区别和总结

左值:

  • 左值引用只能引用左值,不能引用右值
  • 但是const左值引用既可以引用左值,也可以引用右值
int a=10;
//左值引用只能引用左值,不能引用右值
int& ra1=a;

//const左值引用既可以引用左值也可以引用右值
const int& ra2=10;	//const左值引用引用一个右值
const int& ra3=a;

右值

  • 右值引用只能引用右值,不能引用左值
  • 右值引用可以引用move后的左值
//右值引用只能引用右值,不能引用左值
int&& r1=10;

int a=10;
int&& r2=a; //error:无将左值绑定到右值引用

int&& r3=std::move(a);

4.4传值返回的缺陷

如果一个类涉及到资源管理或者深拷贝,那么用户就需要提供自定义的拷贝构造、赋值运算符重载以及析构函数。

否则编译器将会自动生成一个默认的,发生的是浅拷贝,容易出现二次析构和内存泄漏等问题。

//比如我们自定义的string类
class String
{
public:
    //构造函数
	String(char* str = "")
	{
		if (nullptr == str)
        {
            str = "";
        }
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
    //拷贝构造函数。需要程序员显示的写出, 否则会发生浅拷贝出现二次析构问题。
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
    String& operator=(const String& s)
	{
		if (this != &s)
		{
		char* pTemp = new char[strlen(s._str) +1];
		strcpy(pTemp, s._str);
		delete[] _str;
		_str = pTemp;
		}
		return *this;
	}
    
    
	String operator+(const String& s)
	{
		char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
		strcpy(pTemp, _str);
		strcpy(pTemp + strlen(_str), s._str);
		String strRet(pTemp);
		return strRet;
	}
	~String()
	{ 
        if (_str) 
            delete[] _str;
    }
    
private:
	char* _str;
};

传值返回的缺点是,返回的值需要拷贝构造生成一个临时变量,返回的临时变量构造好了后,函数的局部对象被销毁。【出了函数作用域,局部变量被销毁】。

比如下面的string opertaor+(const string&s):

在这里插入图片描述

​ 在编译器不做优化的情况下,strRet传值返回,需要创建一个临时变量,临时变量创建好了后,strRet被销毁,最后使用临时变量赋值构造ret,临时变量被销毁。

这中间进行了一次拷贝构造,一次赋值重载,生成了三个内容完全一样的对象,对于空间是一种浪费,程序的效率也会降低;尽管编译器做出来一定的优化,但是ret会对strRet做一次深拷贝,效率依旧不高。

4.5移动构造和移动赋值

在C++11中如果需要实现移动语义,必须使用右值引用 。

因为函数返回值或者表达式返回值对象的生命周期在创建好临时对象后就结束了,即将亡值。

​ C++11认为其为右值,在用将亡值构造临时对象时,就会采用移动构造,即将将亡值中资源转移到临时对象中。而临时对象也是右值,因此在用临时对象构造s3时,也采用移动构造,将临时对象中资源转移到s3中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。

在这里插入图片描述

编译器优化

一般的编译器也对移动构造和移动赋值做出来优化,不再移动构造临时变量,而是直接将资源交给ret。这样的优化极大的提高了程序的效率。

在这里插入图片描述

写法

将参数修改为右值引用,如下面自定义string的移动构造和移动赋值的写法

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


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

5.完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数

5.1万能引用

模板的万能引用只是提供了能够同时接收左值引用和右值引用的能力

template<class T>
void Perfectforward(T&& t)
{
    func(t);
}
  • 但是引用类型的唯一作用就是限制了接收的类型,后续,无论接收的是右值引用还是左值引用,在后续的使用中都退化成了左值。
void func(int& x)
{
	cout << "lvalue ref" << endl;
}
void func(const int& xx)
{
	cout << "const lvalue ref" << endl;
}
void func(int&& x)
{
	cout << "rlvalue ref" << endl;
}
void func(const int&& xx)
{
	cout << "const rvalue ref" << endl;
}
template <class T>
void Perfectforward(T&& t)
{
	func(t);
}
int main()
{
	int a = 10;
	int& ra = a;
	const int b = 20;
	const int& rb = b;
	Perfectforward(ra);
	Perfectforward(rb);
	Perfectforward(move(ra));
	Perfectforward(move(rb));
	return 0;
}

输出:

lvalue ref
const lvalue ref
lvalue ref
const lvalue ref

在后续的使用中,类型发生了折叠,编译器将引用全部视为是左值引用

5.2完美转发实现

所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)

C++11可以通过forward函数保留引用的类型,从而实现完美转发

void func(int& x)
{
	cout << "lvalue ref" << endl;
}
void func(const int& xx)
{
	cout << "const lvalue ref" << endl;
}
void func(int&& x)
{
	cout << "rlvalue ref" << endl;
}
void func(const int&& xx)
{
	cout << "const rvalue ref" << endl;
}
template <class T>
void Perfectforward(T&& t)
{
	func(std::forward<T>(t));
}
int main()
{
	int a = 10;
	int& ra = a;
	const int b = 20;
	const int& rb = b;
	Perfectforward(ra);
	Perfectforward(rb);
	Perfectforward(move(ra));
	Perfectforward(move(rb));
	return 0;
}

输出:

lvalue ref
const lvalue ref
rlvalue ref
const rvalue ref

注意:在每一级引用中,都需要调用forward()函数实现完美转发

6.可变模板参数

C++的新特征可变模板参数可以接收可变参数的函数模板和类模板。

下面是一个基本可变参数的函数模板

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

​ Args…表示可变模板参数,args里面包含了0到若干个参数,我们无法直接获取参赛包args的每个参数,只能通过展开参数包的方式来获取参数包中的每一个参数。

template<class ...Args>
void showlist(Args... args)
{
    //计算参数个数
	cout << sizeof...(args) << endl;
}
int main()
{
	showlist(1, 'x', 1.1);
	showlist(1, 2, 3, 4, 5);
	return 0;
}

输出:

3 5

​ 由于语法不支持使用args[i]的方式获取可变参数,所以只能使用一些其他方法获取参数包的值。

递归函数方式展开解包

//用于结束解包的模板
//当参数包只有最后一个参数时,就会匹配当前的模板,而不是可变参数模板
template<class T>
void showlist(const T& val)
{
	cout << "val type:" << typeid(val).name() << " ;" << val << endl;
}



//可变参数模板
template<class T,class ...Args>
void showlist(const T& val, Args... args)
{
	//计算参数个数
	cout << "val type:" << typeid(val).name() << " ;" << val<< endl;
	cout <<"sizeof:" << sizeof...(args) << endl;
	showlist(args...);
}
int main()
{
	showlist(1, 'x', 1.1);
	showlist(1, 2, 3, 4, 5);
	return 0;
}

输出:

val type:int ;1
sizeof:2
val type:char ;x
sizeof:1
val type:double ;1.1

val type:int ;1
sizeof:4
val type:int ;2
sizeof:3
val type:int ;3
sizeof:2
val type:int ;4
sizeof:1
val type:int ;5

逗号表达式解包

template <class T>
int Printargs(const T& t)
{
    cout<<"type:"<<typeid(t).name()<<"  value:"<<t<<endl;
    return 0;
}

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

int main()
{
	Printargs(1, 'x', 1.1);
	return 0;
}

输出:

type:int value:1
type:char value:x
type:double value:1.1

7.lambda表达式

​ 假如我们有Goods类,并想按照它的名字。价格和评价进行排序,那么在C++11之前我们可以借助仿函数使用sort函数排序。

class Goods
{
public:
	Goods(const char* str, double price, int evalute)
		:_name(str), _price(price), _evalute(evalute)
	{}
	string _name;
	double _price;
	int _evalute;
};
struct cmpprice
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price;
	}
};
struct cmpname
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._name < g2._name;
	}
};

struct cmpevalute
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._evalute < g2._evalute;
	}
};


int main()
{
	vector<Goods>v = {{"苹果",2.1,5},{"香蕉",3,4},{"橙子",2.2,3},{"菠萝",1.5,4}};
	sort(v.begin(), v.end(), cmpprice());
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
	sort(v.begin(), v.end(), cmpname());
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
	sort(v.begin(), v.end(),cmpevalute());
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
}

输出:

菠萝 苹果 橙子 香蕉
菠萝 橙子 苹果 香蕉
橙子 菠萝 香蕉 苹果

7.lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

[ 捕捉列表 ] ( 参数 )mutable->返回值{函数体}

  • 编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • 捕捉列表里面的变量带有const属性,如果想要去掉const属性,需要加上参数mutable
  • 参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
    连同()一起省略
  • 返回值类型:用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

使用lambda表达式对上面的vector进行排序

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)->bool {return g1._price < g2._price;});
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._name < g2._name;});
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._evalute< g2._evalute;});
	for (auto e : v)
	{
		cout << e._name << " ";
	}
	cout << endl;
}

输出:

菠萝 苹果 橙子 香蕉
菠萝 橙子 苹果 香蕉
橙子 菠萝 香蕉 苹果

7.2捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

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

需要注意的是,值传递的是变量的拷贝

  • a. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • b. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • c. 在块作用域以外的lambda函数捕捉列表必须为空。
  • d. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • e.lambda表达式之间不能相互赋值,即使看起来类型相同
int main(){	
	int a = 10, b = 20;
	cout << "a: " << a << "b:" << b << endl;
	auto func=[&a, &b]() {int c = a;a = b;b = c;};
	func();
	cout << "a: " << a << "b:" << b << endl;
    
    int c = 2, d = 3, e = 4, f = 5, g = 6,ret;
	//传值捕捉全部对象
	auto Func1 = [=]{return c + d*e / f + g;	};
	ret=Func1();
	cout << "ret:" <<ret << endl;
	return 0;
}

输出:

a: 10b:20
a: 20b:10 ret:10

7.3lambda表达式底层

lambda表达式的底层是实现了一个仿函数类型

class Rate
{
public:
	Rate(double rate): _rate(rate)
	{}
	double operator()(double money, int year)
	{ return money * _rate * year;}
private:
	double _rate;
};

int main()
{
	// 函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);
	// lamber
	auto r2 = [=](double monty, int year)->double{return 	monty*rate*year;};
	r2(10000, 2);
	return 0;
}

仿函数类的名称是lambda+uuid【uuid是通过唯一标识算法得到的字符串】

在这里插入图片描述

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

7.4lambda表达式赋值给同类型的函数指针

lambda表示的底层是转换为了仿函数,所以lambda也可以赋值给函数指针

int(*funptr)(int);
funptr = [](int n)->int {int ret = 0; for (int i = 1;i <= n;i++) { ret += i; }return ret;};
int sum=funptr(500);
cout << "sum:" << sum << endl;

输出:sum:125250

8.包装器

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

template <class Ret, class... Args>
class function<Ret(Args...)>;
/*
	Ret表示函数的返回类型
	Args表示参数列表
*/

C++11中可调用的对象有:函数指针,仿函数对象,lambda表达式。

ret = func(x);
上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下
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;
	return 0;
}

输出:

count:1
count:0033C140
5.555
count:1
count:0033C144
3.70333
count:1
count:0033C148
2.7775

三次count的地址都不同,说明模板实例化了三次,通过包装器可以减少模板实例化的次数

#include<functional>
template<class F,class T>
T useF(F f, T x)
{
	static int count = 0;
	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()
{
	//函数名
	function<double(double)>func1 = f;
	cout << useF(func1, 1.1) << endl;
	function<double(double)>func2 = Functor();
	cout << useF(func2,2.2) << endl;
	function<double(double)>func3 = [](double d)->double { return d / 4;};
	cout << useF(func3, 4.4) << endl;
	return 0;
}

输出:

count:1
0.55
count:2
0.733333
count:3
1.1

可以得到只进行了一次模板实例化

包装器的其他一些场景:

在这里插入图片描述

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long>st;
        unordered_map<string,function<long(long,long)>>opfunc=
        {
            {"+",[](long a,long b)->long{return a+b;}},
            {"-",[](long a,long b)->long{return a-b;}},
            {"*",[](long a,long b)->long{return a*b;}},
            {"/",[](long a,long b)->long{return a/b;}}
        };
        for(auto e: tokens)
        {
            if(opfunc.find(e)!=opfunc.end())
            {
                long right=st.top();
                st.pop();
                long left=st.top();
                st.pop();
                st.push(opfunc[e](left,right));
            }
            else
            {
                st.push(stoi(e));
            }
        }
        return st.top();
    }
};

包装非静态成员函数需要取地址。同时还需要示例化类并传递this指针,使用方式如下

#include <functional>
int f(int a, int b){return a + b;}
struct Functor
{
public:
	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()
{
	// 函数名(函数指针)
    function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
    function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表达式
    function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
	cout << func3(1, 2) << endl;
    
	// 类的静态成员函数
    function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
    
    //类的普通成员函数
    function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

8.2bind函数

bind是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

/*
	模板原型
*/
template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。

arg_list中的参数可能包含形如 _ n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。
数值n表示生成的可调用对象中参数的位置:placeholders::_ 1为newCallable的第一个参数,placeholders::_ 2为第二个参数 ,以此类推

8.2.1…调整对象参数个数
int f(int a, int b)
{
	return a - b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	Plus(int x = 2)
		:_x(x)
	{}

	int plusi(int a, int b)
	{
		return (a + b) * _x;
	}
private:
	int _x;
};
int main()
{
	function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	function<int(int, int)> func2 = Functor();
	cout << func2(10, 20) << endl;
	//plusi的调用需要三个参数,其中包括this指针
	function<int(Plus, int, int)>func3 = &Plus::plusi;
	cout << func3(Plus(5), 4, 3) << endl;
	return 0;
}

调用func3需要传递三个参数,我们可以通过bind函数,调整调用func3的参数个数

	//plusi的调用需要三个参数,其中包括this指针
	function<int(Plus, int, int)>func3 = &Plus::plusi;
	cout << func3(Plus(5), 4, 3) << endl;

	//调整参数个数为2个
	function<int(int, int)>func4 = bind(&Plus::plusi, Plus(5), placeholders::_1, placeholders::_2);
	cout << func4(4, 3) << endl;
	//调整参数个数为1个
	function<int(int)> func5 = bind(&Plus::plusi, Plus(5), 4, placeholders::_1);
	cout << func5(3) << endl;

输出:

35
35
35

8.2.2调整对象参数顺序

调整对象参数的顺序,只需要改变arg_list中占位符的顺序。比如想要把第一个参数的位置和第二个参数的位置进行交换,只需要将:

placeholders::_ 1,placeholders _ 2替换为 placeholders::_ 2,placeholders::_ 1

	function<int(int, int)>func1 = bind(f,placeholders::_2, placeholders::_1);
	cout << func1(200, 150) << endl;
	//原来的输出结结果为 50
	//交换后的结果为 -50
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到