当前位置: 首页 > backend >正文

【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表达式:

 当我们想通过一个对象调用函数的时候,我们已经学习了两种方法:

  1. 函数指针:void(*p)(int)=func; ---->比较复杂,能不用就不用
  2. 仿函数:生成一个类,重载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++学习
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

http://www.xdnf.cn/news/4590.html

相关文章:

  • RDD的处理过程
  • vue3的新特性
  • Spring cloud loadBalancer 负载均衡
  • Qwen2-VL详解
  • Unity3D 游戏内存优化策略
  • Anchor-based 和 Anchor-free
  • 修改图像分辨率
  • SLAM:单应矩阵,本质矩阵,基本矩阵详解和对应的c++实现
  • AtCoder 第404场初级竞赛 A~E题解
  • 【无标题】云计算运维
  • 代码随想录算法训练营第60期第二十九天打卡
  • 前端代码规范详细配置
  • CSS手动布局
  • 60页PDF | 四川电信数据湖 + 数据中台实施方案:覆盖数据能力、数据资产及数据治理的全流程建设指南
  • 从xjtu-sy数据集中看轴承故障的发展趋势与基本特征
  • 南京大学OpenHarmony技术俱乐部正式揭牌 仓颉编程语言引领生态创新
  • 5. HTML 转义字符:在网页中正确显示特殊符号
  • Linux系列:如何用perf跟踪.NET程序的mmap泄露
  • 水印落幕 7.0 | 专门用于去除图片和视频中水印的工具,支持自定义水印添加
  • 【测试开发】BUG篇 - 从理解BUG到如何处理
  • 递归element-ui el-menu 实现无限级子菜单
  • Spring 项目无法连接 MySQL:Nacos 配置误区排查与解决
  • AI——认知建模工具:ACT-R
  • #黑马点评#(二)商户查询缓存
  • 新疆地区主要灾害链总结
  • 网络编程(一)
  • seamless_communication,facebook推出的开源语音翻译项目
  • 代码随想录算法训练营 Day39 动态规划Ⅶ 打家劫舍
  • 数据可视化:php+echarts实现数据可视化(包含echart安装引入)
  • 数据压缩实现案例