秒懂C++之C11新特性

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

统一的列表初始化

initializer_list

decltype

右值引用

万能引用

新的默认成员函数

关键字default/delete

可变参数模板

emplace

 lambda表达式

function包装器

bind

结语


统一的列表初始化

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

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
 //一切皆可用{}初始化
int main()
{
	int a = 0;
	//不推荐
	int b = { 1 };
	int c{ 1 };

	int array1[]{ 1, 2, 3, 4, 5 };
	Point p{ 1,2 };

	// 多参数
	// C++98  构造
	Date d1(2024, 3, 23);

	// C++11 
	// 先构造 + 再拷贝构造 -> 优化,直接构造
	// 多参数的隐式类型转换
	Date d2 = {2024, 3, 23};
	Date d3 { 2024, 3, 23 };

	// 单参数
	// 构造
	string s1("1111");
	// 先构造 + 再拷贝构造 -> 优化,直接构造
	string s2  = "1111";

	


	Date* darr1 = new Date[3]{d1,d2,d3};
	Date* darr2 = new Date[3]{ { 2024, 3, 23 } ,{ 2024, 3, 23 } ,{ 2024, 3, 23 } };

	Date* darr3 = new Date(2023,3,34);
	Date* darr4 = new Date{ 2023, 3, 34 };

	return 0;
}

开辟空间这种初始化写法可以参考一下,其他的认识即可~不过这种初始化一般不用于内置类型中~因为看着怪怪的~

initializer_list

std::initializer_list 一般是作为构造函数的参数, C++11 STL 中的不少容器就增加
std::initializer_list 作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为 operator=
的参数,这样就可以用大括号赋值.

其实initializer_list本质就是两个指针,一个指向{}头,一个指向{}尾~

int main()
{
	
	Date d1 = { 2024,9,3 };
	vector<int> v1 = { 1,2,3,4,5,6,7,8,9};
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	
}

二者虽然同为先构造再拷贝构造,但vector是走的特殊的构造,在这里可以不限制参数个数~

我们再来深入了解其他场景下的应用~

int main()
{

	pair<string, string> kv1("sort", "排序");
	pair<string, string> kv2("string", "字符串");
	//可以识别为pair类型的initializer_list
	map<string, string> dict1 = { kv1, kv2 };
	//那么这个又是怎么识别的呢
	map<string, string> dict2 = { {"sort", "排序"}, {"string", "字符串"} };

	for (auto& kv : dict2)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	
}

decltype

关键字decltype将变量的类型声明为表达式指定的类型。
// 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;      // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}

ps:顶层const是指修饰自身的const,底层const是指修饰器指向的内容,而在查看变量类型时会默认去掉顶层const~

右值引用

左值是一个表示数据的表达式 ( 如变量名或解引用的指针 ) 我们可以获取它的地址 + 可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边 。定义时 const 修饰符后的左
值,不能给他赋值,但是可以取它的地址。 左值引用就是给左值的引用,给左值取别名。
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值 ( 这个不能是左值引
用返回 ) 等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址 右值引用就是对右值的引用,给右值取别名。

int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}

从语法层面上看,引用都是作为其别名,不开空间~左值引用是给左值取别名,右值引用是给右值取别名~

从底层来看,引用是用指针实现的,左值引用是存当前左值的地址。而右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址~

特殊情况:

int func1()
{
	static int x = 0;
	return x;
}
int main()
{
	// 左值引用能否给右值取别名 不能
	// 但是const左值引用可以
	const int& r1 = func1();
	const int& r2 = 10;

	// 右值引用能否给左值取别名 不能
	// 但是右值引用可以给move以后的左值可以
	int x = 0;
	int&& rr1 = move(x);

	return 0;
}

我们来分析以下场景:

当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

简要概括后我们再来深入理解右值引用出现的意义~

引用最开始被设计的意义是什么?

为了减少拷贝~

那么右值引用出现的意义是什么?

是为了解决左值引用不能解决的问题~

左值引用:

可以解决传递参数时的拷贝问题~例如 void func(const T& x)

可以解决部分返回对象拷贝的问题~(出了作用域后返回对象还在,那么就可以用左值返回来减少一次拷贝)

那么问题来了~如果出了作用域后返回对象不在了呢(局部对象)?用左值引用返回反而会引发一系列的问题,所以只能使用传值返回,而这个过程就无法避免拷贝(生成一个临时对象)~

既然临时对象也是拷贝后要销毁的,那么为什么不直接把vv拷贝给ret呢?所以编译器就作出了如下优化~遇到连续执行的拷贝+拷贝编译器会优化为一个拷贝~

不管怎样为了把优化后的拷贝也给节省,在C11中引入了右值引用的概念~

但仅仅有右值引用还是不够~目前的状况是:

用左值引用返回也只是减少了一次拷贝(不产生临时对象),这跟编译器优化没有区别,顶多是分开写两行的时候仍是一次拷贝,比编译器触发优化条件好一点~

用右值引用返回的话(把vv变成move(vv)左值变为右值)跟左值引用返回没有区别~

最重要的是无论是左值引用返回还是右值引用返回都有一个致命的缺陷,离开作用域vv就被销毁了,给它取别名然后赋值给ret是没有意义的,最终得到的只是空数据或者报错~因为你的别名是越界访问一个已经销毁的空间~

这样看来还不如用传值返回搞个临时对象安全点~

那么我们的右值引用要如何正确切入呢?

这里我们从拷贝构造进行切入~

这样还是会有一次拷贝,所以编译器再一次进行了优化~

这里不用手动加move的原因是为了兼容以前没有右值引用的版本~所以是编译器代我们处理左值转化为右值的过程~

ps:被move修饰后str本身是不会变化的,只是move以后的返回值变成了右值属性~

最终我们成功利用右值引用把最后一次的拷贝给优化掉了~

接下来我们再来分析另一种场景~

前面我们是根据拷贝+拷贝优化为移动构造

那如果是分开两行的拷贝+赋值拷贝呢?

最后一次的深拷贝不用在意,因为那是赋值拷贝用了现代写法生成的~

总结:左值引用无法解决的拷贝问题用右值引用解决了,深拷贝对象传值返回变为了移动资源,节省空间使用~

于是在C11发布右值引用后,所有容器都适配了移动拷贝与移动赋值~

接下来我们引入关于右值引用新的知识点~

int  main()
{
	// 右值被右值引用以后,右值引用r的属性是左值
	int&& r = 10;
	//对r进行修改不会报错
	r++;
		
	return 0;
}

这样设计有什么意义吗?右值引用后的别名属性变为了左值~

我们拿原先自己实现的容器list进行测试

int main()
{
    bit::list<bit::string> lt;
	bit::string s1("11111");
	
	//lt.push_back(move(s1));
	lt.push_back(s1);
	cout <<"---------" << endl;

	lt.push_back(bit::string("2222"));
	cout << "---------" << endl;

	lt.push_back("2222");
	cout << "---------" << endl;

	return 0;
}

给list适配上移动拷贝与移动赋值~

只要涉及到传参拷贝的都适配出右值版本来减少拷贝次数~

但这样子最终仍是走向深拷贝,这是为什么呢?

我们拿下面这个例子说明~

所以我们需要做的就是让左值x变为右值去走移动构造的版本~

最终实现移动拷贝

万能引用

// ->实例化以下四个版本的函数
void PerfectForward(int& t)
{
	cout << "void PerfectForward(int& t)" << endl;
}

void PerfectForward(const int& t)
{
	cout << "void PerfectForward(const int& t)" << endl;
}
 
void PerfectForward(int&& t)
{
	cout << "void PerfectForward(int&& t)" << endl;
}

void PerfectForward(const int&& t)
{
	cout << "void PerfectForward(const int&& t)" << endl;
}
int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

可以看到在没有出现万能引用之前,我们得根据参数的各个类型而配对出各种函数~这样的效率太低了,所以就有了万能引用的出现~

/万能引用
//左值 &&-> &
//右值 &&-> &&
template<typename T>
void PerfectForward(T&& t)
{
	cout << "void PerfectForward(T&& t)" << endl;
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

而在我们的模板中有了万能引用后,它会自动去识别,去推演参数的类型,就不用特意去搞匹配这一步了,因为万能引用知道你是什么类型~

不用把万能引用当作右值引用!

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
//万能引用
//左值 &&-> &
//右值 &&-> &&
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
	//cout << "void PerfectForward(T&& t)" << endl;
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

虽然节省了匹配这一步骤,但还有一点需要解决:右值引用后的属性变化~

我们也不要全部强制转化为右值~

有些场景下我们是确定可以使用右值的,有时候就需要用move辅助~

std::forward 完美转发在传参的过程中保留对象原生类型属性

有了完美转发后我们就可以在传参的过程中保留对象原生类型属性~

template<class T>
struct ListNode
{
 ListNode* _next = nullptr;
 ListNode* _prev = nullptr;
 T _data;
};
template<class T>
class List
{
 typedef ListNode<T> Node;
public:
 List()
 {
 _head = new Node;
 _head->_next = _head;
 _head->_prev = _head;
 }
 void PushBack(T&& x)
 {
 //Insert(_head, x);
 Insert(_head, std::forward<T>(x));
 }
 void PushFront(T&& x)
 {
 //Insert(_head->_next, x);
 Insert(_head->_next, std::forward<T>(x));
 }
 void Insert(Node* pos, T&& x)
 {
 Node* prev = pos->_prev;
 Node* newnode = new Node;
 newnode->_data = std::forward<T>(x); // 关键位置
 // prev newnode pos
 prev->_next = newnode;
 newnode->_prev = prev;
 newnode->_next = pos;
 pos->_prev = newnode;
 }
 void Insert(Node* pos, const T& x)
 {
 Node* prev = pos->_prev;
 Node* newnode = new Node;
 newnode->_data = x; // 关键位置
 // prev newnode pos
 prev->_next = newnode;newnode->_prev = prev;
 newnode->_next = pos;
 pos->_prev = newnode;
 }
private:
 Node* _head;
};
int main()
{
 List<bit::string> lt;
 lt.PushBack("1111");
 lt.PushFront("2222");
 return 0;
}

有了万能引用搭配完美转发,我们可以节省很多参数配对的问题,不过在类似拷贝问题这种需要分情况处理的场景不能只有万能引用,还是需要额外写出最匹配的函数出来~

新的默认成员函数

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

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

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

关键字default/delete

强制生成默认函数的关键字default:
C++11 可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用 default 关键字显示指定移动构造生成。
class Person
{
public:
 Person(const char* name = "", int age = 0)
 :_name(name)
 , _age(age)
 {}
 Person(const Person& p)
 :_name(p._name)
 ,_age(p._age)
 {}
Person(Person&& p) = default;
private:
 bit::string _name;
 int _age;
};
int main()
{
 Person s1;
 Person s2 = s1;
 Person s3 = std::move(s1);
 return 0;
}
禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在 C++98 中,是该函数设置成 private ,并且只声明补丁
已,这样只要其他人想要调用就会报错。在 C++11 中更简单,只需在该函数声明加上 =delete
可,该语法指示编译器不生成对应函数的默认版本,称 =delete 修饰的函数为删除函数。
class Person
{
public:
 Person(const char* name = "", int age = 0)
 :_name(name)
 , _age(age)
 {}
 Person(const Person& p) = delete;
private:
 bit::string _name;
 int _age;
};
int main()
{
 Person s1;
 Person s2 = s1;
 Person s3 = std::move(s1);
 return 0;
}

可变参数模板

template <class ...Args>
void ShowList(Args... args)
{

}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));

	return 0;
}

很奇妙,可以写入多个类型的参数

递归函数方式展开参数包

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

我们再来通过其底层原理给大家解释一遍

// 实例化以后,推演生成的过程
void ShowList(int val1, char ch, std::string s)
{
	_ShowList(val1, ch, s);
}

void _ShowList(const int& val, char ch, std::string s)
{
	cout << val << " ";
	_ShowList(ch, s);
}

void _ShowList(const char& val, std::string s)
{
	cout << val << " ";
	_ShowList(s);
}

void _ShowList(const std::string& val)
{
	cout << val << " ";
	_ShowList();
}

最后再搭配上万能引用与完美转发

emplace

我们发现最开始用实例化对象的时候都没有啥区别,可当直接输入数值时emplace反而节省了一次移动拷贝~

不过可以利用emplace的可变参数特性直接多参数插入~但这可不是插入多个值的意思~

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	std::list<Date> lt1;
	lt1.push_back({ 2024,3,30 });

	// 不支持
	//lt1.emplace_back({ 2024,3,30 });

	// 推荐
	lt1.emplace_back(2024, 3, 30);

	cout << endl;
	Date d1(2023, 1, 1);
	lt1.push_back(d1);
	lt1.emplace_back(d1);

	cout << endl;
	lt1.push_back(Date(2023, 1, 1));
	lt1.emplace_back(Date(2023, 1, 1));


	return 0;
}

实例化对象我们就不多说了,没有什么区别。不过在只有浅拷贝的Date类中直接用数值插入时emplace会减少一次拷贝构造~

既然已经验证了emplace插入的更优特性,那我们就对之前写的模拟实现list容器进行改写~

插入:

template<class ...Args>
		void emplace_back(Args&&... args)
		{
			emplace(end(), forward<Args>(args)...);
		}

迭代器:

template<class ...Args>
		iterator emplace(iterator pos, Args&&... args)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(forward<Args>(args)...);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			//return iterator(newnode);
			return newnode;
		}

构造:

template<class ...Args>
		ListNode(Args&&... args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(forward<Args>(args)...)
		{}

 lambda表达式

#include<algorithm>

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());
}

目前仿函数面临的问题就是如果想要比较其他数值那就得再写一个~这样太不方便了~

lambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement
}
  • [capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据 []
    判断接下来的代码是否为 lambda 函数 捕捉列表能够捕捉上下文中的变量供 lambda
    函数使用
  • (parameters) :参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以
    连同 () 一起省略
  • mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量
    性。使用该修饰符时,参数列表不可省略 ( 即使参数为空 )
  • ->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回
    值时此部分可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推
  • {statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。
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(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate < g2._evaluate; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate > g2._evaluate; });
}

 有了这个表达式我们就可以更简洁明了写出自定义类中的各个数值比较后的排序了~

int main()
{
	// 局部的匿名函数对象
	//auto add = [](int a, int b)->int {return a + b; };
	auto add = [](int a, int b) {return a + b; };
	cout << add(1, 2) << endl;

	auto swap1 = [](int& a, int& b)->void {
		int tmp = a;
		a = b;
		b = tmp;
	};

	int x = 1, y = 2;
	swap1(x, y);

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

	func1();

	return 0;
}

我们也可以去接收lambda表达式,在我的理解中auto推导类型,后面就是实例化出来的对象去调用(),()运算符重载里面就是我们表达式{}写的内容~

int main()
{
	int x = 1, y = 2;
	// 每次输入一个值跟x换
	// 传值捕捉,捕捉到的是当前的对象的拷贝
	auto swap1 = [x, y]()mutable {
		int tmp = x;
		cin >> x;
		y = tmp;
	};

	swap1();
	cout << x << endl;
	cout << y << endl;

	return 0;
}

我们也可以放弃通过参数列表进行参数传递,而是用[ ]进行捕获参数~只不过捕获过来的是其拷贝,修改后对外是不影响的~

int main()
{
	int x = 1, y = 2;

	// 传引用捕捉
	auto swap1 = [&x, &y]{
		int tmp = x;
		x = y;
		y = tmp;
	};

	swap1();
	cout << x << endl;
	cout << y << endl;
	cout << endl;

	int m = 3, n = 4;
	// 传值捕捉当前域的所有对象
	auto func1 = [=] {
		return x + y * m - n;
	};

	cout << func1() << endl;
	cout << endl;

	// 传引用捕捉当前域的所有对象
	auto func2 = [&] {
		x++;
		m++;
		return x + y * m - n;
	};

	cout << func2() << endl;
	cout << x << endl;
	cout << m << endl;
	cout << endl;


	// 传引用捕捉当前域的所有对象,某些对象传值捕捉
	auto func3 = [&, n] {
		x++;
		m++;

		// n++; 不行
		return x + y * m - n;
	};

	cout << func3() << endl;
	cout << x << endl;
	cout << m << endl;

	return 0;
}

操作空间也是蛮大的~

我们之前的sort操作传的是对象,那如果是使用容器适配比较方式的时候呢?还能用lambda传对象吗?——不行,只能传递类型~

decltype可以解决这个问题~

#include<queue>

int main()
{
	auto DateLess = [](const Date* p1, const Date* p2)
	{
		//return *p1 < *p2;
		return p1 < p2;
	};
	priority_queue<Date*, vector<Date*>, decltype(DateLess)> p1;

	return 0;
}

仿函数和lambda表达式相对比较的话,确实是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);

	// lambda
	auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
	r2(10000, 2);

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

	f1();
	f2();

	return 0;
}

ps:lambda底层是仿函数~

function包装器

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;
}

useF函数模板实例化了三份~

而在有了包装器后,我们就可以解决上述问题~

#include<functional>

int main()
{
	// 函数指针
	function<double(double)> fc1 = f;
	fc1(11.11);
	cout << useF(fc1, 11.11) << endl;

	// 函数对象
	function<double(double)> fc2 = Functor();
	fc2(11.11);
	cout << useF(fc2, 11.11) << endl;

	// lambda表达式
	function<double(double)> fc3 = [](double d)->double { return d / 4; };
	fc3(11.11);
	cout << useF(fc3, 11.11) << endl;

	return 0;
}

150. 逆波兰表达式求值 - 力扣(LeetCode)

function在其他的一些场景下还有奇效~

这里是通过map让操作符与操作符运算建立映射~,在识别到操作符后可以利用map[ ]的特性通过K值寻找到V值,让由返回V值的特性在返回过程中达到运算符运算的效果~

不过function也是要分情况的~

#include<functional>
int f(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)> fc1 = f;
	cout << fc1(1, 1) << endl;

	// 静态成员函数 要标明所在域
	function<int(int, int)> fc2 = Plus::plusi;
	cout << fc2(1, 1) << endl;

	// 非静态成员函数 必须加上&语法硬性规定 参数也要多一个,不可忽略this指针
	// 非静态成员函数需要对象的指针或者对象去进行调用
	/*Plus plus;
	function<double(Plus*, double, double)> fc3 = &Plus::plusd;
	cout << fc3(&plus, 1, 1) << endl;*/

	function<double(Plus, double, double)> fc3 = &Plus::plusd;
	cout << fc3(Plus(), 1, 1) << endl;

	return 0;
}

了解即可,混个眼熟~

bind

// 原型如下:
template < class Fn , class ... Args >
/* unspecified */ bind ( Fn && fn , Args && ... args );

// with return type (2)
template < class Ret , class Fn , class ... Args >
/* unspecified */ bind ( Fn && fn , Args && ... args );

作用一:改变参数顺序(了解)

作用二:改变参数个数

#include<functional>
int Sub(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()
{
	// 调整参数顺序,了解一下,意义不大
	int x = 10, y = 20;
	cout << Sub(x, y) << endl;

	auto f1 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f1(x, y) << endl;

	function<double(Plus, double, double)> fc3 = &Plus::plusd;
	cout << fc3(Plus(), 1, 1) << endl;

	 调整参数的个数
	 某些参数绑死
	function<double(double, double)> fc4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	cout << fc4(2, 3) << endl;

	function<double(double)> fc5 = bind(&Plus::plusd, Plus(), placeholders::_1, 20);
	cout << fc5(2) << endl;

	return 0;
}

结语

有些特性我们了解即可,毕竟太多了而且挺少使用,重点是要了解右值引用的出现~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值