【C++】C++11(上)
🚀write in front🚀
📜所属专栏: C++学习
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我,你们将会看到更多的优质内容!!
文章目录
- 前言
- 一.统一的列表初始化
- 1.{}初始化:
- 2.std::initializer_list
- 二.类型的声明
- 1.decltype,auto,typeid:
- 2.nullptr
- 三.右值引用和移动语义:
- 1.左右值的概念:
- 1.左值:
- 2.右值:
- 3.左值引用:
- 4.右值引用:
- 5.左值给右值赋值:
- 6.右值给左值赋值:
- 7.const 左值引用和右值在函数中的识别:
- 2.左值引用的意义:
- 3.右值引用的场景:
- a.自定义中深拷贝的类,必须传值返回的场景:
- b.容器的插入接口,如果插入的对象是右值,也可以直接移动构造将资源转移给类:
- 四.完美转发
- 万能引用:
- 完美转发:
- 完善list
- 五.lambda表达式:
- lambda表达式的语法:
- lambda的底层:
- 六.包装器function
- bind:
- 1.参数调换顺序:
- 2.表示绑定函数 plus 的某参数为常量
- 3.绑定成员函数
- 总结
前言
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更
强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本节课程主要讲解实际中比较实用的语法。
一.统一的列表初始化
1.{}初始化:
在C语言里面,我们使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}
而在C++11里面增加了{}的用途,使其可用于所有的内置类型和用户自定义的类型。比如:
struct Point
{Point(int x, int y):_x(x), _y(y){cout << "Point(int x, int y)" << endl;}int _x;int _y;
};
int main()
{int aa = { 0 };//单参数的隐式类型换:string ikun = "坤坤";//多参数的隐式类型转化Point b = { 0,0 };const Point& r = { 3,3 };//在new里面初始化也可以用{}初始化了。int* ptr = new int[3]{ 4,3,2 };Point* pt1 = new Point[2]{ Point(1,2) ,Point(3,4) };//这里就是一个典型的多参数的隐式类型转化Point* pt2 = new Point[2]{ {1,2} ,{3,4} };
}
2.std::initializer_list
其实在前面的学习里面在报错里经常出现initializer_list
,我们先来看看他到底是个啥:
int main()
{
// the type of il is an initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
return 0;
}
initializer_list是一个类,在看完库介绍后,其实就是一个类似存储T类型的数组。他也有自己的构造函数,不过这里都是编译器处理的,我们自己无法模拟实现。与数组不同的是,这个初始化这个类的数组想开多大就写多大,便于后面传参数。
举个栗子:
我们在vector初始化的时候是可以这样初始化的:
vector<int> lt = { 1,2 };
而在查看vector的构造函数我们会发现,C++11新增了这个构造函数:
这就是为什么可以直接使用{}初始化vector的原因。
所以下面两个代码都用了{},但是意义是不一样的。
vector<int> v1 = { 1,2,3,4,3 }; // 调用initializer_list的vector构造函数
Point p1 = { 1,1}; // 直接调用两个参数的构造 -- 隐式类型转换
实际上,库里面的很多容器都在C++11加入了initializer_list的构造,比如:
// 这里{"sort", "排序"}会先初始化构造一个pair对象(隐式类型转换)
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
二.类型的声明
1.decltype,auto,typeid:
- typeid推出类型是一个字符串,只能看不能用
- auto必须初始化,没有初始化,无法推断类型
- decltype可以推出对象的类型,再定义变量,或者作为模板实参,但是可以不初始化。
int b;
cout<<typeid(b)<<endl;可以推出一个字符串类型
auto it=2;//必须初始化
decltype(b) de;//可以不初始化
vector<decltype(b)> v;//也可用于模板参数
decltype(malloc) func;//用于函数指针,减少复杂的书写
2.nullptr
因为C语言NULL使用了宏定义,为0,就会引发参数不匹配问题,所以C++引入了nullptr。在平时,多使用const
enum
inline
去替代宏。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
三.右值引用和移动语义:
1.左右值的概念:
1.左值:
简单的说,左值就是能取地址的值:
int* ptr = new int(0);
int b = 1;
const int c = 2;
"xxxxx";
const char* p = "xxxxx";
p[2];
int && p=b;
//这里p编译器也认为是左值!!
2.右值:
简单的说,右值就是不能取地址的值,因为不能取到其地址,所以不能修改:
10;
b + c;
func(b);//函数返回值是一个零时对象
3.左值引用:
对左值取别名:
int a = 0;
int& b= a;
4.右值引用:
对右值取别名:
int&& aa = 10;
double&& r6 = a + b;
5.左值给右值赋值:
int a=0;
int &&b=move(a);
这里的move是一个函数,底层比较复杂,简单说就是最后返回了一个a的引用,且这个引用的属性是一个右值(不是右值引用)。单独给一个左值move是不会改变他的性质的,比如:
move(a);
6.右值给左值赋值:
double b=1.2;
const int &a =b;
由于类型不匹配会发生隐式类型转换,转化出的临时值是一个右值int,用const修饰的左值接收。
7.const 左值引用和右值在函数中的识别:
const 左值引用可以接收右值,但是他可以和右值引用构成重载,所以当右值传参的时候也会走更匹配的(也就是右值引用).
2.左值引用的意义:
左值引用的核心就在于减少拷贝,比如函数传参的时候传引用,或者传引用返回的场景。但是左值拷贝还是有一些无法解决的缺陷,比如传值返回和容器的插入数据。
3.右值引用的场景:
a.自定义中深拷贝的类,必须传值返回的场景:
这里我们使用我们之前模拟实现的string类:
//拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);//s为右值引用,但是其属性被编译器识别为左值,所以他才能交换swap,否则无法实现资源转移//在这里的右值引用本质是通过指针来操作的,底层我也不知道}//赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}string& operator=(string&& s){cout << "string& operator=(string && s) -- 移动拷贝" << endl;swap(s);return *this;}
bit::string func()
{//这里本来是先构成成匿名对象,然后在拷贝构造,编译器优化为直接拷贝构造bit::string str="x";cin >> str;return str;
}int main()
{zxr::string ret1 = func();//左值引用:原本是先拷贝出一个临时对象,然后用临时对象拷贝给ret1。拷贝次数:2//编译器优化以后让str直接拷贝构造给ret1。拷贝次数:1//右值引用:原本是普通的拷贝构造出一个临时对象,然后用临时对象移动拷贝。拷贝次数:1//编译器优化以后,直接把str当作右值(move了一下),直接在用str移动构造了ret1。拷贝次数:0zxr::string ret2;ret2 = func();//左值引用:原本是先拷贝构造临时变量,然后在用临时变量通过=拷贝,有两次拷贝。拷贝次数:2//右值引用:原本是先拷贝构造临时变量,然后直接用=,此时的=不用在拷贝了,就减少了一次拷贝。拷贝次数:1//如果此时加了移动构造函数://右值引用:编译器直接把str当作右值移动拷贝,然后在通过=移动拷贝 拷贝次数:0return 0;
}
其实在这里,函数的传值返回的str是一个将亡值,出了函数就要被销毁掉,而我们这个时候就可以使用右值引用来接收这个将亡值,本来要重写拷贝一份值来构造新的类,而此时有一个将亡的值,为什么不把他用起来呢?此时我们就可以通过移动拷贝和移动赋值把他的资源直接用起来。(使用了现代写法,直接交换资源非常的划算)。
但是这里针对的是深拷贝的类,可以直接将堆里面的资源交换利用,就不用重新开新的空间。而浅拷贝就没有必要移动拷贝和移动构造,因为移动拷贝和普通拷贝的作用的是一样的。
b.容器的插入接口,如果插入的对象是右值,也可以直接移动构造将资源转移给类:
还是以上面的模拟实现的string为例子:
int main()
{
string s1("hello world");
// 这里s1是左值,调用的是拷贝构造
string s2(s1);
// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
// 资源被转移给了s3,s1被置空了。
string s3(std::move(s1));
return 0;
}
std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
所以,在实行下面的代码时:
int main()
{
list<bit::string> lt;
bit::string s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
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);
}
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;
}
在这里按常理来说是按注释那样右值匹配右值引用,左值匹配左值引用。但是结果会发现全是左值引用。这是为什么呢?
其实上面已经隐隐约约提到,右值是一个没有地址并且不能改变的值,而我们的右值引用是可以改变的!不然上面的移动语义就会出问题。
int a=0;
int &&aa=(move)a;
aa++;//可以改变,aa为左值
所以右值引用原生属性是右值,但是其变量的属性会被编译器识别成左值,否则移动构造的时候无法完成资源交换。
那就会有同学问,那右值引用都有自己的地址,那是重新拷贝了吗?那移动语义有什么意义呢?
其实底层上面右值引用也是通过指针来操作的,只是比较复杂,我们不必了解这么多,只需要知道右值引用可以接收右值,但是编译器把他的属性看成左值就可以了。
那么上面的万能引用如何处理呢?
这里就要用到完美转发。
完美转发:
传参的过程中保留对象原生类型属性(这里稳定理解就是之前指向的类型属性)
void PerfectForward(T&& t)
{// 完美转发,t是左值引用,保持左值属性// 完美转发,t是右值引用,保持右值属性Fun(forward<T>(t));
}
这里的forward(t),就可以使t保持他原有的属性。
完善list
这里我们以下面代码举个例子,修改一下我们之前的list
int main()
{
zxr::list<zxr::string> lt;
zxr::string s1("1111");
// 这里调用的是拷贝构造
lt.push_back(s1);// 下面调用都是移动构造
lt.push_back("2222");
lt.push_back(std::move(s1));
return 0;
在这里,如果我们使用我们自己的list
就不会显示移动拷贝函数。因为我们在list里面没有实现移动语义,他只能通过const
左值来接收。所以我们可以完善我们的list
:
void push_back(const T& val)
{insert(end(), val);
}void push_back(T&& val)
{insert(end(), forward<T>(val));
}//pos位置之前插入
iterator insert(iterator pos,const T& val)
{Node* cur = pos._node;Node* prev = cur->_prev; Node* newnode = new Node(val);newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;return pos;
}
//pos位置之前插入
iterator insert(iterator pos, T&& val)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(forward<T>(val));newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;return pos;
}void empty_init()
{_head = new Node(T());//这里会自动取调用右值引用的_head->_next = _head;_head->_prev = _head;
}list()
{empty_init();
}
list_node(const T& val) :_val(val)
{}//这里不属于万能引用,万能引用的识别是通过参数来识别,而这里的参数已经定好了(在list里面typedef list_node<T> Node)
ist_node(T&& val) :_val(forward<T>(val))
{}
在这里我们会发现,要将左值一直用到头,要在很多地方加上完美转发,不然根据右值引用在编译器认识下的左值属性,很可能会匹配到左值里面去。
五.lambda表达式:
当我们想通过一个对象调用函数的时候,我们已经学习了两种方法:
- 函数指针:
void(*p)(int)=func;
---->比较复杂,能不用就不用 - 仿函数:生成一个类,重载operator() ---->比较实用,但是头有点大。
下面我们以排序的代码为例:
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++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式
lambda表达式的语法:
lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement}
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
- ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
由上面不难看出,lambda表达式实际是一个匿名函数,所以只能用auto接收。
这里有几个点是要注意的:
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。其使用的方式如下:
[var]
:表示值传递方式捕捉变量var
[=]
:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]
:表示引用传递捕捉变量var
[&]
:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]
:表示值传递方式捕捉当前的this指针
捕捉表达式里面可以用引用的方式捕捉,也可以用传值的方式捕捉。但是传值的方式捕捉的值是const类型的,不能修改,他们是捕捉的值的拷贝。而引用的就可以修改,还会修改捕捉值原本的值。
int x=1;
int y=2;
int z=3;
auto func1 = [&x, y, z]
{x = y;//main函数里的x的值改变
};auto func2 = [x, y, z]
{x = y;//报错,因为不能修改传值的捕捉
};auto func3 = [x, y, z]mutable
{x = y;//和函数参数一样,只改变了形参没有改变实参,main函数里的x没有变
};
以上面排序的例子:
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; });
}
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:
[=, &a, &b]
:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]
:值传递方式捕捉变量a和this,引用方式捕捉其他变量
捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:
[=, a]
:=已经以值传递方式捕捉了所有变量,捕捉a重复
lambda表达式之间不能相互赋值,即使看起来类型相同
void (*PF)();
int main()
{
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
return 0;
}
lambda的底层:
我们先来看看下面的代码:
int main()
{auto f1 = [](int x, int y) {return x + y; };auto f2 = [](int x, int y) {return x + y; };cout << typeid(f1).name() << endl;cout << typeid(f2).name() << endl;f1(1, 2);
}
结果:
其实通过类型我们可以发现,其实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);
// lamber
auto r2 = [=](double monty, int year)->double{return monty*rate*year;
};
r2(10000, 2);
return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。
在通过反汇编代码我们会发现,底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。当然这个是编译器的工作,就和范围for的底层是迭代器一样。
六.包装器function
在学习了lambda之后,我们学会了三种函数对象:
函数指针
仿函数
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 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;
}
上面的模板函数会实例化成三分,因为虽然他们都属于函数对象,但是类型都不一样的:
这里要生成多份函数,就会导致其效率低下。这里就要介绍一下
包装器function
std::function是一个函数对象,它允许你将函数、函数指针、成员函数、lambda 表达式等不同类型的可调用对象封装成一个通用的对象,使得这些对象可以像函数一样被调用。因此,std::function本质上是一个仿函数(functor),但它提供了更灵活的方式来处理不同类型的可调用对象。
举个栗子:
double f(double i)
{return i / 2;
}struct Functor
{double operator()(double d){return d / 3;}
};function<double(double)> f1 = f;
function<double(double)> f2 = [](double d)->double { return d / 4; };
function<double(double)> f3 = Functor();//此时也就可以把函数对象放在vector里面
vector<function<double(double)>> v1 = { f1, f2, f3 };
vector<function<double(double)>> v2= { f, [](double d)->double { return d / 4; }, Functor() };
此时上面的模板参数就可以统一传这个函数对象了,也就实例化出一个函数:
//函数指针:cout << useF(f, 2.2) << endl;//仿函数:(函数对象)cout << useF(Functor(), 3.3) << endl;//lambda表达式cout << useF([](double x)->double {return x / 4; },4.4);
由此,我们就可以通过一个字符串对应一个指令,此时我们就可以对那个逆波兰表达式计算进行优化了:
bind:
std::bind
是 C++ 标准库中的一个函数,用于创建函数对象,允许绑定函数或函数对象的参数,以便稍后以不同的方式调用它们。std::bind通常与std::function
一起使用,以生成可调用对象,它可以方便地延迟函数调用或以不同的方式调用函数。
1.参数调换顺序:
int Sub(int a, int b)
{return a - b;
}
int main()
{function<int(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2);cout << rSub1(10, 5) << endl;function<int(int, int)> rSub2 = bind(Sub, placeholders::_2, placeholders::_1);cout << rSub2(10, 5) << endl;return 0;
}
在这里,bind里面的参数,第一个是函数名,后面就是函数的参数,其中palceholders::_n
就代表第几个参数,比如在调用rSub(5,10)时,5对应palceholders::_1
,10对应palceholders::_2
,而在bind里面的参数对应的顺序就是函数真正调用参数的顺序。其实这里面palceholders::_n
就是来调整传参顺序的。
2.表示绑定函数 plus 的某参数为常量
double Plus(int a, double rate,int b)
{return (a + b) * rate;
}
int main()
{function<double(int, int)> Plus1 = bind(Plus, placeholders::_1, 4.0,placeholders::_2,);//此时的rate就定为常量4.0了,传参就不传rate参数了cout << Plus1(5, 3) << endl;//等价于cout<<Plus(5,4.0,3)<<endl;}
对于常量,由于设置了常量,那个就不算参数了,因为我们不会传那个参数了。
3.绑定成员函数
当然,std::bind 还支持绑定成员函数和成员函数指针,以及任意可调用对象。你可以在调用时提供实际参数来替换占位符。
class SubType
{
public:static int sub(int a, int b){return a - b;}int ssub(int a, int b, int rate){return (a - b) * rate;}
};int main()
{function<double(int, int)> Sub1 = bind(&SubType::sub, placeholders::_1, placeholders::_2);SubType st;function<double(int, int)> Sub2 = bind(&SubType::ssub, &st, placeholders::_1, placeholders::_2, 3);cout << Sub1(1, 2) << endl;cout << Sub2(1, 2) << endl;function<double(int, int)> Sub3 = bind(&SubType::ssub, SubType(), placeholders::_1, placeholders::_2, 3);cout << Sub3(1, 2) << endl;cout << typeid(Sub3).name() << endl;
}
对于static静态成员函数,我们可以直接调用那个函数,但是对于非静态函数,我们要取函数的地址(这里是特殊处理,要加一个取地址符号,其他情况都不用),由于类要掉非静态函数肯定要一个对象,所以还要传一个对象或者对象的地址才行。
总结
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!
专栏订阅:
每日一题
C语言学习
算法
智力题
初阶数据结构
Linux学习
C++学习
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!