【C++】C++11新特性

news2024/9/21 0:29:38

目录

一.列表初始化initializer_list

1.什么是列表初始化

 2.列表初始化的原理

二.auto/decltype/nullptr

1.auto - 自动类型推导

2.decltype - 指定类型

3.nullptr - C++空指针

三.范围for

四.右值引用/移动构造/移动赋值/万能引用/完美转发

1.什么是右值

2.左值与右值的概念

3.左值引用与右值引用

4.右值引用的价值(移动构造/移动赋值)

1).为何需要右值引用

2).回顾左值引用的价值

3).右值引用的价值

4).移动构造与移动赋值的实现(string类举例)

5.万能引用与完美转发

1).万能引用, 发生引用折叠

2).完美转发forward(x)

6.C++11类的八大默认成员函数

1.C++98 vs C++11

2.默认生成的移动构造与移动赋值

五.default/delete/final/override

六.可变参数模板

1.参数包的概念

2.计算参数包中参数个数

3.递归函数的方式展开args参数包

4.以数组的形式展开args参数包

5.可变模板参数在stl中的应用

七.lambda表达式

1.lambda表达式的优势

2.lambda表达式的语法

3.lambda表达式捕捉列表的多种捕捉方式

4.lambda表达式使用原理与底层实现原理

1).lambda表达式的使用原理

2).lambda表达式的实现原理

5.总结

八.function包装器/bind(绑定)包装器适配器

1.function包装器

2.bind适配器


一.列表初始化initializer_list

1.什么是列表初始化

在C++98中, 允许使用{}对数组或结构体进程初始化

C++11扩大了{}的适用范围, 并命名为列表初始化, 让所有的内置类型和自定义类型都可以使用列表初始化, 使用列表初始化可以加"=", 也可以不加

#include<iostream>
using namespace std;

struct point
{
	int _x;
	int _y;
	point(int x, int y)
		:_x(x)
		,_y(y)
	{}
};

int main()
{
	//对于内置类型变量
	//正常初始化
	int a1 = 10;
	double b1 = 3.14;
	//列表初始化
	int a2 = { 10 };
	int a3{ 10 };
	double b2 = { 3.14 };
	double b3{ 3.14 };

	//对于数组
	//正常初始化(也是用的列表)
	int arr1[] = { 1,2,3,4,5 };
	//列表初始化新特性
	int arr2[]{ 1,2,3,4,5 };

	//对于自定义类型变量
	//正常初始化(调用构造)
	point p1(13, 23);
	//列表初始化(本质:也是调用构造)
	point p2 = { 5,5 };
	point p3{ 5,5 };

	return 0;
}

列表初始化对于以上的演示并不常用, 且由于可读性比较差应该谨慎使用

列表初始化的真正意义是可以让stl容器进行使用(通过实现initializer_list构造)

//列表初始化
list<int> l1 = { 1,2,3,4,5,6,7,8,9,10 };
list<int> l2{ 1,2,3,4,5,6,7,8,9,10 };

vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };
vector<int> v2{ 1,2,3,4,5,6,7,8,9,10 };

 2.列表初始化的原理

对于内置类型而言, 是强制的语法规定, 暂且不谈

对于自定义类型而言, 如何支持的初始化列表, 其实本质上就是调用构造函数

1.对于大括号中的内容而言, 一开始在编译器看来就是一堆数据, 如果{...}这堆数据有匹配构造函数,

那么就直接用这些数据去调构造函数即可, 如下图

2.如果类中没有{...}这堆数据符合的构造函数, 那么列表{...}中的内容就会构造成一个initializer_list对象, 在C++11中新增了一个类型是initializer_list, 且该类型支持迭代器与范围for

initializer_list

initializer_list在C++中定义为类模板, 其模板参数根据列表内数据而定, 故列表{..}中的所有数据必须为同一类型

使用auto自动类型推导, 最终推到结果x是一个initializer_list类型对象

例子1: vector<int> v = {1,2,3,4,5}; 在C++98中是没有这样的构造可以调用的, 而在C++11中, 新增了一个initializer_list构造, 所以列表{...}中的数据由于没有找到匹配的构造函数, 就去调用了initializer_list构造, 过程就是现将列表构造成一个initializer_list对象, 再去调用相应的构造函数

例子2: map<string, string> dict = {{"left", "左边"}, {"right", "右边"}};先将{"left", "左边"}与{"right", "右边"}都构造为键值对(pair<string, string>对象), 后调用initializer_list构造

二.auto/decltype/nullptr

1.auto - 自动类型推导

对于内置类型和函数返回值, auto可以直接推导

对于自定义类型, auto无法推导还未定义的变量的类型

错误演示

2.decltype - 指定类型

指定某一变量在定义时的类型, 指定为与其他变量相同的类型, 但必须合理!

即使用double类型数据初始化, 也不会是double类型, 而是隐式转换为int类型

使用方法的扩展

decltype(a*b)

3.nullptr - C++空指针

C语言中, NULL是指针类型, 即void*, C++中, NULL是int类型

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

为了更加安全, C++11中新增nullptr(空指针)

例如: 在C++代码中, 函数重载时NULL优先匹配int类型, 而nullptr优先匹配指针类型

三.范围for

范围for的底层就是迭代器的遍历

只是在形式上变得非常简洁, 但是底层与迭代器是一模一样的

所以只要是能够支持迭代器, 则一定可以支持范围for

用上面的initializer_list类的迭代器遍历举例

initializer_list<int> list = { 1,2,3,4,5,6 };
initializer_list<int>::iterator it = list.begin();
while (it != list.end())
{
	cout << *it << ' ';
	++it;
}
cout << endl;
for (auto elem : list)
{
	cout << elem << ' ';
}
cout << endl;

四.右值引用/移动构造/移动赋值/万能引用/完美转发

1.什么是右值

对于内置类型右值 -- 纯右值

对于自定义类型右值 -- 将亡值

简单来说: 就是一个即将被释放的值

例如: 

1).函数传值返回的返回值

2).表达式(x+y, x*y)

3).字面常量(注意: 字符串常量属于左值)

4).匿名对象

2.左值与右值的概念

左值一定可以被取地址, 如果不能取地址那么就是右值

左值可以修改, const修饰左值则不可修改, 右值不可以修改

左值可以在等号左边也可以在等号右边, 右值必须在等号右边

注: 字符串常量是左值!

//函数以值形式返回
string func()
{
	return "hello str\n";
}

int main()
{
	int a = 10;//左值
	int b = 5;
	cout << &a << "-" << & b << endl;

	"hello";//左值
	cout << &"hello" << endl;
    
    100;//右值    

	a + b;//右值

	func();//右值

	return 0;
}

总结:

左值: 可取地址, 可修改, 可以在等号左右

右值: 不可取地址, 不可修改, 不可在等号左边 

3.左值引用与右值引用

引用左值的变量就是左值引用, 引用右值的变量就是右值引用

int& i = a;//左值引用
int&& j = 10;//右值引用

左值引用可以引用左值, 也可以引用右值, 但是在引用右值时必须加上const来延长其生命周期

右值引用只能引用右值

const int& k = 55;//被const修饰的左值引用可以引用右值
//int&& m = a;//右值引用不可以引用左值

右值引用可以引用move之后的左值 

int&& z = move(a);//右值引用可以引用move之后的左值

右值引用完右值或是move的左值之后,  该引用会变成左值, 编译器会为其开辟空间, 并且可以对该右值引用进行修改, 若不想被修改可以用const修饰

int&& e = 89;
cout << &e << endl;
e = 44;
cout << e << endl;

 

const int&& u = 100;//const修饰右值引用防止被修改

4.右值引用的价值(移动构造/移动赋值)

1).为何需要右值引用

由于左值引用想要引用右值必须加const, 也就意味着不可修改, 在某些场景下无法解决特定问题

例如, 当我以一个右值传参, 且这个参数如果是普通形参的话需要发生深拷贝, 我既不希望他发生深拷贝, 又要对内部数据进行修改

        不希望拷贝说明一定要以引用方式传参

        想要引用右值, 不能使用左值引用, 如果想要使用则需要用const左值引用

        需要对数据进行修改, 说明该形参不能被const修饰

这时C++11中的右值引用就很好的解决了上述问题, 右值引用可以直接引用右值

2).回顾左值引用的价值

(1).做参数, 可以减少拷贝增加效率, 或者可以作为输出型参数

(2).做返回值, 可以减少拷贝增加效率, 或者可以支持修改原数据, 例如vector与map中的operator[]

但左值引用有些场景效率仍不是最优

例如在stl的容器中, 对于传值返回的自定义类型对象, 在拷贝或赋值时, 需要调用其拷贝构造或者赋值重载, 并且这两个都必须是深拷贝, 需要重新开辟空间进行拷贝, 会大大降低效率

在C++11中, 新增右值引用, 间接性的解决了这个问题, 因为有了右值引用, 所以支持了移动构造与移动赋值

3).右值引用的价值

对于传值返回的自定义类型对象拷贝或赋值时, 编译器会选择最匹配的函数进行调用, 如果此时支持了移动构造与移动赋值就会去进行调用

移动构造与移动赋值不是拷贝, 而是资源转移, 相当于我要拷贝你, 而你是一个右值, 那么你就是一个将亡值, 我只需要将你我资源互换, 在你即将死亡时把我的数据也带走一起释放掉

与传统的拷贝构造相比, 移动构造减少了开辟空间的资源消耗, 仅仅是简单的资源转移(swap交换), 进一步提高效率, 右值引用的价值就体现于此

什么时候会调用移动构造呢?

当使用一个匿名对象拷贝时, 或者当使用一个返回了对象的值的函数时(返回值是string等,例如to_string, string类的operator+)

概括来说就是用一个右值对象去拷贝时, 如果实现了移动构造, 编译器优先调用移动构造, 如果没有实现的话, 编译器就退而求其次, 去调用拷贝构造

所以由于右值引用的出现, 使得C++支持了移动构造与移动赋值, 右值引用并没有延长将亡值的生命周期, 准确来说, 如果使用了移动构造或是移动赋值, 则是延长了资源的生命周期!

4).移动构造与移动赋值的实现(string类举例)

用string类举例

namespace zsl
{
    //移动构造
    string(string&& s)
        :_str(nullptr)
        ,_size(0)
        ,_capacity(0)
    {
        std::swap(_str, s._str);
        std::swap(_size, s._size);
        std::swap(_capacity, s._capacity);
        //如果string类内实现了swap
        //swap(s);
    }
    //移动赋值
    operator=(string&& s)
    {
        std::swap(_str, s._str);
        std::swap(_size, s._size);
        std::swap(_capacity, s._capacity);
        //如果string类内实现了swap
        //swap(s);
    }
}

总结: 只有拷贝时需要深拷贝的类, 移动构造和移动赋值才有意义, 其本身就是为了减少拷贝提高效率而存在

5.万能引用与完美转发

1).万能引用, 发生引用折叠

模板函数类模板中如果有右值引用作为参数的函数, 那么这个右值引用就是万能引用

//万能引用

//非模板函数下, &&是右值引用,只能引用右值
//且右值引用是左值,因为可以取地址
void func1(int&& a)
{
	cout << &a << endl;
}

//模板下(无论是函数模板还是类模板),&&是万能引用,会发生引用折叠,可以引用右值也可以引用左值
//并且万能引用自身也变为左值
void func(int& a)
{
	cout << "左值引用" << endl;
}
void func(const int& a)
{
	cout << "const左值引用" << endl;
}
void func(int&& a)
{
	cout << "右值引用" << endl;
}
void func(const int&& a)
{
	cout << "const右值引用" << endl;
}

template<class T>
void func2(T&& a)
{
	//cout << &a << endl;
	func(a);
}

int main()
{
	int c = 20;
	const int i = c;

	func1(10);
	//func1(c);报错,因为右值引用不能引用左值

	func2(c);//左值
	func2(10);//右值

	func2(i);//const左值
	func2(move(i));//const右值

	return 0;
}

2).完美转发forward<T>(x)

基于以上的引用折叠之后, 在模板中, 不管是左值还是右值, 传给右值引用后都变为了左值, 在去拿这个形参调用时, 优先匹配到的就都是左值引用参数的函数

如果我们想在传右值给万能引用时, 虽然发生了引用折叠但是仍想恢复回来, 也就是让他仍保留右值属性, 这时就要用到完美转发: std::forward<T>(x);

以下图片还是基于以上代码, 在这就不重复的编写代码, 只需要将万能引用参数进行完美转发即可

注: 完美转发之后只影响当次传参时为右值, 在这之后, 还是可以对a进行取地址的, 也就是说在完美转发传参结束后, a仍是左值, 下一次若仍有需求还要传参的话, 还需再进行完美转发

如果不是模板的话, 传入右值给右值引用时, 这时的右值引用依旧是一个左值, 如果还需要继续向下传参的话, 依旧可以使用完美转发, 此时T需要填具体类型

例如forward<int>(x); 

6.C++11类的八大默认成员函数

1.C++98 vs C++11

C++98中创建一个空类, 编译器会默认生成六个成员函数

构造, 析构, 拷贝构造, 赋值重载, 取地址重载, const取地址重载

C++11中创建一个空类, 在C++98的基础上又新增了两个默认生成的成员函数

移动构造, 移动赋值

C++98中

拷贝构造与赋值重载通过const左值引用的参数, 来完成左值与右值的拷贝

C++11中

拷贝构造与赋值重载主要来完成左值的拷贝

移动构造与移动赋值主要来完成右值的拷贝, 减少了不必要的深拷贝, 采用资源转移的方式拷贝

2.默认生成的移动构造与移动赋值

以移动构造举例, 如果在类中没有显式实现移动构造, 且没有显式实现析构, 拷贝构造, 赋值重载, 则编译器会自动生成一个移动构造

编译器自动生成的移动构造会做什么?

对于内置类型直接进行值拷贝

对于自定义类型, 去看这个自定义类型是否有移动构造, 如果有则优先调用移动构造, 如果没有则调用拷贝构造

注: 当一个类需要进行深拷贝, 就要自己显示去写移动构造和移动赋值

且如果显式实现了移动构造移动赋值, 编译器就不再会自动生成拷贝构造赋值重载

五.default/delete/final/override

default: 强制生成默认成员函数

Date() = default;

delete: 强制禁止生成默认成员函数

~Date() = delete;

final: 在一对继承关系中, 禁止父类虚函数被重写

virtual void func() final;

override: 在一对继承关系中, 检查子类是否重写父类虚函数, 若没重写则报错

virtual void func() override;

六.可变参数模板

1.参数包的概念

//Args是一个模板参数包, args是一个函数形参参数包
template<class ...Args>
void ShowList(Args... args)
{
    //...
}

把带有...的参数称为参数包, 参数包中含有N个参数(N>=0)

Args是一个可变模板参数包, args是一个函数形参参数包

关于可变参数模板的使用, 我们无法直接获取到参数包args中每个参数的值, 只能够通过递归展开函数包或者数组打印的方式来依次展开每个参数

2.计算参数包中参数个数

语法: sizeof...(args)

template<class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	ShowList(29, "world");
	ShowList('a', 1, "hello", 3.14);
	return 0;
}

3.递归函数的方式展开args参数包

每一次调用ShowList都会将第一个参数传给val, 将剩余参数传给args参数包

接下来递归调用ShowList直至参数包中剩余参数为0, 调用ShowList()结束递归

//递归出口,最后一次args...的参数为0个
void ShowList()
{
	cout << "结束递归\n";
}

template<class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << "当前获取到的参数: " << val << "\t参数包中剩余参数个数: " << sizeof...(args) << endl;
	ShowList(args...);
}

int main()
{
	//ShowList(29, "world");
	ShowList('a', 1, "hello", 3.14);

	return 0;
}

  

4.以数组的形式展开args参数包

使用数组来间接依次遍历, 数组初始化时采用逗号表达式使数组初始化为int类型

template<class T>
void MyPrint(const T& val)
{
	cout << val << endl;
}

template<class ...Args>
void ShowList(Args... args)
{
	int a[] = { (MyPrint(args), 0)... };
}

int main()
{
	ShowList('a', 1, "hello", 3.14);

	return 0;
}

  

5.可变模板参数在stl中的应用

emplace与insert的使用相同, 只是传入参数不同

emplace与push_back的使用相同, 只是传入参数不同

emplace/insert, emplace_back/push_back其不同是体现在emplace系列是传入的可变参数, 而普通插入是直接传值或对象

例如

vector<pair<string, int>> v;
v.push_back(make_pair("left", 1));
v.emplace_back("right", 3);

list<pair<string, int>> l;
l.push_back(make_pair("sort", 0));
l.emplace_back("hello", 2);

如果是传统的插入方式, 则需要先构造一个pair对象, 然后再调用拷贝构造插入

emplace则不需要构造pair对象, 由于支持可变参数模板, 且传入的是可变参数, 则可以直接用传入的可变参数, 那么就可以依次遍历参数包来直接构造到插入位置, 也就不需要可以的再去构造一个pair对象了

所以在stl的插入中, 如果支持了emplace系列, 且要插入的是一个自定义类型, 则完全可以代替传统的insert或push_back, 可以减少一次构造, 更进一步提高效率

七.lambda表达式

特例说在最前: lambda捕捉列表对于全局变量的捕捉问题, 假设此时有全局变量int a = 10

vs下使用[=]或[&]捕捉没有问题, 若[a]或[&a]捕捉则编译报错, linux则没有类似问题

1.lambda表达式的优势

让一个对象有像函数一样使用的方式:

1.函数指针(C语言很喜欢使用这种方式)

2.仿函数(C++98且STL中很多地方用到了仿函数)

3.lambda表达式(C++11)

站在使用者角度来看, lambda相较于仿函数而言更加简洁, 更加灵活, 更加直观

站在lambda底层实现的角度来看, 这两者差别不大

2.lambda表达式的语法

语法: [捕捉列表](参数列表)mutable->返回值类型{函数体实现}

捕捉列表: 可以捕捉"父"作用域的变量("父"代表当前函数栈帧), 在使用lambda时, 捕捉变量的过程类似于函数传参的过程(并不是传到显式写在参数列表中的形参, 这个过程对于使用者而言是透明的), 捕捉列表不可省略, 编译器根据[]来判断这是否为一个lambda表达式

参数列表: 与普通函数传参一致, 如果不需要传递参数则可将参数与()一同省略, 如果当mutable存在时, 参数列表便不可胜率, 如果参数为空应该写为()

mutable: 如果列表以值的形式捕捉变量, 则捕捉到的变量默认都有const属性, 想要修改则需要mutable, 一般情况下不需要mutable, 并且在使用mutable时, 参数列表不可省略

->返回值类型: 显式指明该lambda要返回的返回值类型, 若返回值类型为void则与->一同省略不写, 若返回值类型很明确, 则也同样可以省略, 便由编译器自动推导

{函数体}: 与普通函数一样, 此处为函数实现, 与普通函数不同的是, 除了可以使用参数列表中的内容, 还可以使用所有捕捉列表捕捉到的变量

3.lambda表达式捕捉列表的多种捕捉方式

[x] -- 值传递捕捉x

[=] -- 值传递捕捉所有"父"作用域变量

[&x] -- 引用传递捕捉x

[&] -- 引用传递捕捉所有"父"作用域变量

[this] -- 值传递捕捉当前this指针

1."父"作用域指只要捕捉列表能够看到的, 就能够被捕捉

2.可以混合捕捉, [=, &a, &b][&, a, b], 但不可[=, a]这样捕捉, 因为a构成了重复传递, 总之一个变量只能被同一种形式的捕捉捕捉一次

3.lambda表达式不可以互相赋值

4.lambda表达式使用原理与底层实现原理

1).lambda表达式的使用原理

lambda表达式在定义时会返回一个对象, 这个对象的类型对于使用者而言是透明的, 需要使用auto来接收, 让编译器自动推导

先创建lambda表达式对象: auto x = lambda表达式;

再以类似函数调用的方式去调用该对象: x(有参则传参, 无参则省略);

int g_val = 10;

int main()
{
	auto lambda1 = [=]()mutable->int {return g_val; };
	cout << "捕捉全局变量: " << lambda1() << endl;

	//交换x1与x2的值
	int x1 = 5, x2 = 1;

	//方式一
	auto lambda2 = [](int& x1, int& x2) {swap(x1, x2); };
	lambda2(x1, x2);
	cout << "x1:" << x1 << "  x2:" << x2 << endl;

	//方式二
	auto lambda3 = [&] {swap(x1, x2); };
	lambda3();
	cout << "x1:" << x1 << "  x2:" << x2 << endl;

	if (true)
	{
		int b = 21, c = 22, d = 23, e = 24;
		//可以捕捉到x1和x2
		auto lambda4 = [b, c, d, e, x1, x2] {printf("%d,%d,%d,%d,%d,%d\n", b, c, d, e, x1, x2); };
		lambda4();
	}

	return 0;
}

2).lambda表达式的实现原理

lambda表达式的底层实现, 本质上与仿函数的底层原理相同, 先创建一个类, 再构造一个对象, 调用时就通过对象调用operator()

以上结论可以通过汇编观察

//对比lambda实现与仿函数实现

class FunClass
{
public:
	FunClass(double rate)
		:_rate(rate){}
	double operator()(int x, int y)
	{
		cout << "I am FunClass -> operator\n";
		return x * y * _rate;
	}
private:
	double _rate = 0; 
};

int main()
{
	int a = 10, b = 5;
	//创建仿函数对象fc
	FunClass fc(0.5);
	//调用仿函数
	double res1 = fc(a, b);
	//创建lambda表达式对象
	auto ld = [](int x, int y)->int
	{
		cout << "I am Lambda -> operator\n"; 
		return x * y;
	};
	//调用lambda表达式
	int res2 = ld(a, b);

	cout << "仿函数: " << res1 << '\t' << "lambda: " << res2 << endl;
}

对于每个lambda表达式, 编译器都会为其创造独一无二的类, 即便是两个完全一致的lambda表达式也是如此

对于用户而言, 编译器为lambda表达式创建类这一过程是透明的, 用户是没有办法参与的

每个lambda表达式创建的类的类名是: lambda + uuid组成, 以确保该类是唯一的

lambda表达式的对象不能互相赋值, 但是可以赋值给函数指针

5.总结

lambda表达式

对于使用者而言: 是匿名的, 使用起来更加直观便捷

对于编译器而言: 是命名的, 底层实现就是仿函数

总的来说, 可以说lambda表达式是让编译器替使用者做了更多的事(底层创建仿函数), 与范围for的原理相同

lambda表达式与仿函数对比, 使用差别很大, 但lambda底层实现就是仿函数的实现

八.function包装器/bind(绑定)包装器适配器

1.function包装器

函数指针, 仿函数对象, lambda表达式对象, 可以统一传给function包装器

使用function包装器需要包头文件: functional

使用方式: function<返回值类型(参数包, 存放参数类型)>

例如: 有一个int参数, 一个bool参数, 一个double参数, 返回值类型为float: function<float(int, bool, double)> f;

#include<functional>

int func(int a, int b)
{
	return a * b;
}

class myclass
{
public:
	int operator()(int a, int b)
	{
		return a * b;
	}
};

class Date
{
public:
	static void print_static()
	{
		cout << "static:2022_12_7" << endl;
	}
	void print_none_static()
	{
		cout << "none static:2022_12_7" << endl;
	}
};

int main()
{
	//函数指针传给包装器对象
	function<int(int, int)> f1 = func;
	cout << f1(5, 10) << endl;
	//仿函数对象传给包装器对象
	function<int(int, int)> f2 = myclass();
	cout << f2(5, 10) << endl;
	//lambda表达式对象传给包装器对象
	function<int(int, int)> f3 = [](int a, int b) {return a * b; };
	cout << f3(5, 10) << endl;
	//静态成员函数(函数指针)传给包装器对象
	function<void()>f4 = Date::print_static;
	f4();

	//非静态成员函数(函数指针)传给包装器对象
	//特别说明:成员函数需要用this指针调用,&Date::print_none_static拿到成员函数地址之后,还需要传一个对象才能调用
	function<void(Date)>f5 = &Date::print_none_static;
	f5(Date());

	return 0;
}

函数指针, 仿函数, lambda表达式本质都是在做相同的事, 只是方式不同, 通过function包装器可以让这三者统一起来

同时还解决了lambda表达式返回的对象只能用auto接收的问题, 例如在map<string, function<int(int, int)>>中, 便可以用包装器来接收lambda表达式对象

2.bind适配器

bind的概念

bind - 绑定 - 可以调整函数传参顺序与个数

bind返回对象可以用auto接收, 也可以用function包装器接收

bind的使用

1.bind的第一个参数可以传: 函数指针, 函数地址, 仿函数对象, lambda对象, function包装器

2.其余的参数(参数包): 传_1, _2... (_1, _2是定义在std::placeholders中的), 使用前一般要using namespace placeholders; 或者placeholders::_1 ... 

_1代表第一个形参, _2代表第二个形参, _n代表第n个形参

根据第一个参数中的形参个数, 参数包应传对应的_1, _2, _3... , 若不填_n则也可以用指定值绑定

#include<functional>

int func(int a, int b)
{
	return a * b;
}

class myclass
{
public:
	int operator()(int a, int b)
	{
		return a * b;
	}
};

class Date
{
public:
	static void print_static()
	{
		cout << "static:2022_12_7" << endl;
	}
	void print_none_static()
	{
		cout << "none static:2022_12_7" << endl;
	}
};

using namespace placeholders;

int main()
{
	//函数指针传给包装器对象
	function<int(int, int)> f1 = func;
	cout << f1(5, 10) << endl;
	//仿函数对象传给包装器对象
	function<int(int, int)> f2 = myclass();
	cout << f2(5, 10) << endl;
	//lambda表达式对象传给包装器对象
	function<int(int, int)> f3 = [](int a, int b) {return a * b; };
	cout << f3(5, 10) << endl;
	//静态成员函数(函数指针)传给包装器对象
	function<void()>f4 = Date::print_static;
	f4();

	//非静态成员函数(函数指针)传给包装器对象
	//特别说明:成员函数需要用this指针调用,&Date::print_none_static拿到成员函数地址之后,还需要传一个对象才能调用
	function<void(Date)>f5 = &Date::print_none_static;
	f5(Date());

	auto b1 = bind(f1, _1, _2);
	auto b2 = bind(func, _1, _2);
	auto b3 = bind(myclass(), _1, _2);
	auto b4 = bind([](int a, int b) {return a * b; }, _1, _2);

	cout << "b1: " << b1(2, 3) << " b2: " << b2(2, 3) << " b3: " << b3(2, 3) << " b4: " << b4(2, 3) << endl;

	return 0;
}

bind的用途

1.bind可以调整传参顺序, 这个调整是根据bind中的_1,_2来自动调整的

#include<functional>
using namespace placeholders;

int func(int a, int b)
{
	cout << "a: " << a << " b: " << b << endl;
	return a * b;
}

int main()
{
	auto b1 = bind(func, _2, _1);
	cout << b1(7, 8) << endl;

	return 0;
}

2.bind可以调整传参个数, 通过在传参之前就绑定指定值的方式

#include<functional>
using namespace placeholders;

class Date
{
public:
	static void print_static()
	{
		cout << "static:2022_12_7" << endl;
	}
	void print_none_static()
	{
		cout << "none static:2022_12_7" << endl;
	}
};

int main()
{
	//function版,需要传一个Date对象
	function<void(Date)> f = &Date::print_none_static;
	f(Date());
	//bind版,传参前绑定Date()匿名对象,传参数即可不用再传
	function<void()> bf = bind(&Date::print_none_static, Date());
	bf();

	return 0;
}

在某些场景下, 如果需要用map<string, function<int(int, int)>> m;来实现一个事件响应, 不管是类中的成员函数还是类外的全局函数, 都可以传给同一个包装器, 例如

#include<functional>
#include<map>
using namespace placeholders;

int my_mul(int x, int y)
{
	return x * y;
}

class myclass
{
public:
	int my_add(int x, int y)
	{
		return x + y;
	}
};

int main()
{
	map<string, function<int(int, int)>> mf =
	{
		{"*", my_mul},
		{"+", bind(&myclass::my_add, myclass(), _1, _2)}
	};

	cout << mf["*"](9, 9) << endl;
	cout << mf["+"](9, 9) << endl;

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

react面试题总结一波,以备不时之需

React组件的构造函数有什么作用&#xff1f;它是必须的吗&#xff1f; 构造函数主要用于两个目的&#xff1a; 通过将对象分配给this.state来初始化本地状态将事件处理程序方法绑定到实例上 所以&#xff0c;当在React class中需要设置state的初始值或者绑定事件时&#xff…

首版20年后开源反病毒引擎 ClamAV 1.0 发布

导读ClamAV 是一个开源的&#xff08;GPL&#xff09;反病毒引擎&#xff0c;用于检测木马、病毒、恶意软件和其他恶意威胁。它为用户提供了许多实用程序&#xff0c;包括一个可扩展的多线程守护程序、一个命令行扫描器和一个自动更新数据库的高级工具。 ClamAV 是一个开源的&a…

rust编译器教我做人,为啥还要学习rust语言,因为想使用rust做一些底层服务,更深入的研究技术。

目录1&#xff0c;继续学习Rust语言&#xff0c;确实学习成本很高&#xff0c;学了两周还在学习入门概念&#xff0c;和编译器斗争2&#xff0c;rust学习曲线非常高&#xff0c;为啥还要坚持学习&#xff0c;一直想写一些服务研究研究底层的技术啥的3&#xff0c;rust对前端也有…

git回退版本 简单易懂

进行git版本回退的时候 查看git提交的版本 使用git log查看提交日志&#xff1a; git loggit log命令显示从最近到最远的提交日志 如果嫌输出信息太多&#xff0c;可以试试加上–prettyoneline参数,代码如下&#xff1a; $ git log --prettyoneline 日志会进行减少 根据…

【分布式系统】分布式缓存Redis集群原理与环境搭建

文章目录集群原理缓存分片算法Hash算法一致性Hash算法二者区别集群方案通信协议缓存路由缓存扩展保障可用性搭建redis安装步骤集群安装基本配置启动节点创建集群访问集群添加主节点加入集群分配槽添加从节点切换从节点删除节点手动切换故障升级节点问题记录安装gccjemalloc/jem…

3.21 小红书薯条改版了,都改了些什么呢?【玩赚小红书】

一、薯条是什么&#xff1f; 咱们在座的大部分都是小红书深度用户&#xff0c;相信对薯条再熟悉不过了。 「薯条」 就是小红书自助式的投放工具&#xff0c;通过投放薯条&#xff0c;能给我们提供更多的曝光度&#xff0c;带来更多的赞藏评&#xff0c;从而提高内容的转化率&…

通信原理 | 彻底搞懂卷积

先从多项式运算说起 下面这个多项式,大家应该都会算: 一般做法是先逐项相乘,再合并同类项,需要两步才能完成,那有没有一步就能完成的方法呢? 下面的方法就可以做到 总结这种计算过程: 反褶:多项式按照x的降幂排列,将其中任意一个多项式各项按照升幂排列。 平移:将…

加拿大学生服务部门都有哪些?

咱们今天来聊聊和同学们密切相关的大学里的学生服务部门都有哪些&#xff1f;同学们有了各种问题可以去找谁解决&#xff1f;知道了这些团队&#xff0c;同学们远在千里上着网课&#xff0c;就不会有孤立无援的感觉啦。 1. Registrition Office 这是负责新生注册管理的部门。大…

(附源码)ssm在线学习系统 毕业设计 261624

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

安装阿里的龙蜥系统

1.打开VMware workstations 点击文件 选择新建虚拟机 2.选择虚拟机类型的配置 3.如何安装操作系统 4.选择操作系统的版本 5.修改虚拟机名称和位置 6.设置指定磁盘的容量 默认20G 7.检查新建虚拟机所有参数检查选择的所有参数是否有误&#xff0c;没问题就点完成&#xff0…

java面试强基(19)

HashMap 和 Hashtable 的区别&#xff1f; 线程是否安全&#xff1a; HashMap 是非线程安全的&#xff0c;Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。&#xff08;如果你要保证线程安全的话就使用 ConcurrentHashMap 吧&#xff01;&am…

CCES软件开发ADSP-21489的详解

作者的话 21489和21479在自己写代码C编程的开发模式下&#xff0c;可以使用 Visual DSP软件&#xff0c;也可以使用 CCES 软件。CCES 软件是基于 Eclipse内核的&#xff0c;所以你会发现使用起来跟很多其他的调试工具很类似。本篇会简单的讲一下如何用CCES 软件来做开发。 PS…

【发福利啦!】畅享上百万卡时NPU普惠算力,启智与昇思MindSpore社区联合推出算力支持计划

启智社区与MindSpore联合&#xff0c;为MindSpore开发者提供 365天*24小时 上百万卡时的MindSporeNPU普惠算力&#xff0c;欢迎MindSpore产学研开发者申请&#xff0c;基于MindSpore开发你自己的模型算法套件和应用 嘿~因为了解到日理万机的你可能没有时间仔细阅读完整篇文章&…

[windows] opencv + ffmpeg + h264 + h265 源码编译教程

1 前言 此方法可支持读写 H264/H265编码的视频 2 环境准备 官网下载 msys 安装 建议默认路径安装&#xff0c;避免不必要的麻烦 打开MSYS2 MSYS命令行 打开后&#xff0c;能看见下图中的MSYS标记 在MSYS 命令行上执行下面命令安装依赖库&#xff0c;安装的时候建议每个…

C# 实现模拟PID调试(学习专用无硬件)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace PID控制 {public class PIDModel{public float goal; //定义设定值public float thisValue; //定义实际值public float err…

DBCO-ICG-disulfo_二磺酸-吲哚菁绿-二苯并环辛炔_disulfo-ICG-DBCO

一、理论分析&#xff1a; 中文名&#xff1a;二磺酸-吲哚菁绿-二苯并环辛炔、水溶性吲哚菁绿-二苯基环辛炔 英文名&#xff1a;disulfo-ICG-DBCO、DBCO-ICG-disulfo CAS号&#xff1a;N/A 化学式&#xff1a;C63H62N4Na2O11S3 分子量&#xff1a;1193.37二、产品详情&#xff…

Bootstrap Table pagelist设置后失效

Bootstrap Table pagelist设置后不生效、失效、不起作用、不能使用问题。 前言 在使用Bootstrap Table进行数据展示时&#xff0c;设置pagelist选项后不生效。bootstrap版本为 v3.3.7。 经过 网上搜索尝试使用以下几种方式解决&#xff0c;发现均不行&#xff0c;你们可以参…

深入理解ReentrantReadWriteLock源码

1. ReentrantReadWriteLock简介 之前我们介绍过ReentrantLock&#xff0c;它是基于AQS同步框架实现的&#xff0c;是一种可重入的独占锁。但是这种锁在读多写少的场景下&#xff0c;效率并不高。因为当多个线程在进行读操作的时候&#xff0c;实际上并不会影响数据的正确性。 …

分享5款小众软件,大家按需下载

今天推荐一些可以大幅度提升办公效率的小软件&#xff0c;安全无毒&#xff0c;下载简单&#xff0c;最重要的是没有广告&#xff01; 1.进程调试——Process Lasso Process Lasso是一款独特的调试进程级别的系统优化工具 &#xff0c;主要功能是基于其特别的算法动态调整各个…

Linux——文件系统inode与软硬链接

目录 一.inode &#xff08;一&#xff09;.背景知识 &#xff08;二&#xff09;.inode 二.软硬链接 &#xff08;一&#xff09;.软链接 &#xff08;二&#xff09;.硬链接 一.inode &#xff08;一&#xff09;.背景知识 我们知道&#xff0c;磁盘是按磁道与扇区划分…