C++11讲述(跑路人笔记)

{}初始化

C++11

可以使用花括号对一切都可以进行初始化.

例子–{}初始化

#include<iostream>
using namespace std;
//日期类用于下方举例
class Date
{
public:
	Date(int year = 2002,int month = 8,int day = 26)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		;
	}
	void Print()
	{
		cout << "year: " << _year << "   ";
		cout << "month: " << _month << "   ";
		cout << "day: " << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//初始化x为1
	int x{ 1 };
	//初始化y为1;
	int y = { 1 };
	//开辟十个int空间并不完全初始化(没有初始化位置被自动初始化为0.)
	int* arri = new int[10]{ 1,2,3,4,5,6,7,8,9 };
	//一个自定义空间来进行初始化
	Date D{ 0,0,0 };
	//开辟3个自定义类型并进行不完全初始化(没有初始化部分调用默认构造函数)
	Date* arrD = new Date[3]{ {1,1,1},{2,2,2} };
	//开辟10个char类型并初始化为字符串.
	char* arrc = new char[10]{ "123456" };
	cout << "打印x  " << x << endl;
	cout << "打印y  " << y << endl;
	cout << "打印arrc  " << arrc << endl;
	cout << "打印arri" << endl;
	for (int i = 0; i < 10; i++)
	{
		cout << arri[i] << " ";
	}
	cout <<endl << "打印日期类内容" << endl;
	cout << "D的内容如下" << endl;
	D.Print();
	cout  << "arrD内容如下" << endl;
	for (int i = 0; i < 3; i++)
	{
		arrD[i].Print();
	}
	return 0;
}

执行内容如下:

image-20230315201919388

{}初始化一切的原因

之所以可以支持{}初始化,是因为{}会被初始化为,然后将花括号里的内容都存放在initializer_list中,再由我们的initializer_list这个列表来进行初始化赋值.

下面我们还是在initializer_list - C++ Reference (cplusplus.com)里来了解一下这个类里面的内容和结构.

template<class T> class initializer_list;//声明
auto il = { 10, 20, 30 };  // 这个类型就是我们的initializer_list

下面我们来看一下我们的initializer_list一共有几个函数在这个类里.

如下图:

image-20230315200512253

他的内容就只有begin() end() size();

就像一个迭代器一样.当我们使用{}来初始化的时候他就将{}内容里的东西都搬运到自己的list列表里(里面的内容会被const修饰),然后在我们需要他里面的内容的时候将他们搬运出来用于元素的初始化.

至于这个是如何对所有自定义类型和内置类型都可以使用的以及这个过程博主不答毕竟这个目前只是对initializer_list的一个简单了解.

我们的库里带的像vector list 等类型其实都是在构造函数哪里增加了一个关于initializer_list的构造函数,我们用vector来看看.

image-20230315203911182

我们来看看C++11和98比都增加了什么?

可以看到我们最后就增加了initializer_list的初始化的构造函数,当然其他的也包括了.至于其他增加的我们后面都会看到的.

右值引用

右值引用,看不懂的地方就只有右值这个是啥罢了.我们来看看右值的定义.

不过直接看右值有些难以理解我们先看一下我们左值吧

左值其实就是一个我们可以对其取地址并且可以对他进行赋值(const虽然不可以进行赋值但是依旧是左值).

左值其实就可以理解为在赋值符号左边的值.

对应右值其实就是出现在符号右边的值,他不可以被取地址,不可以对他进行赋值.

像: 10 (x+y) 任意函数返回值都是右值.

image-20230221224828981

右值引用形势是使用&& 来引用右值的.

形势如: int&& i = 10

对比左值引用: int& i = a (a是变量)

  1. 我们的左值引用只能引用左值不能引用右值,但是加上const他就可以既能引用左值又可以引用右值.
  2. 右值引用只可以引用右值不能引用左值但是可以引用move后的左值.(move是一个函数可以将左值属性转变为右值属性).
  3. 右值引用所引用的值我们反而可以通过右值引用来得到他的地址并且可以改变他了.
    1. image-20230327162559667
  4. 右值引用的引用变量本身属于左值(会在[注意](# 注意)部分讲解),这个其实和上面第3条也对应.前

move()函数在<utility>库中他的作用就是将一个左值短暂的(仅在move所在行部分)变成一个右值,我们可以借助它使用移动构造函数(就在没几行的下面会讲).

我们的右值又可以被分为纯右值和将亡值两种.

纯右值: 10 ,a+b…

将亡值: string(“123456”);std::to_string(123) + “hello”;

纯右值我们可以记忆为 单纯的在赋值符号右边的值.

将亡值是出了这一行他的寿命就完成了就死掉(被析构)啦.就想匿名对象一样.

当我们需要将亡值的数据转移到另一个类型的时候在C++98我们是只能使用拷贝构造, 但是我们在C++11的时候增加了一写新的构造函数 ----移动构造.

移动构造简讲

当传给构造函数的类型是右值的时候就有可能调用移动构造函数.

我们的移动构造函数有点像我们的每个类型所带的swap函数.在移动构造函数中我们将 将亡值的所有资源都转移到我们调用的类型中来.

就是直接将指针和其他数据资源进行调换.

让我们来看看vector的移动构造函数的声明:

image-20230318164632497

类似移动构造思想的例子

移动构造的增加可以很不错的提高我们的数据处理效率.

比如我们的string类型如果我们想直接使用"123";来对我们的string来进行构造.

  1. C++98中编译器就需要去调用拷贝构造函数("123"会先被弄成匿名对象string(“123”)然后传给我们要构造的string), 而string(“123”)的类型是右值,所以在C++11中编译器就会去调用移动构造函数,将匿名对象string(“123”)中的资源转移给我们要构造的函数里从而增大效率.

  2. 我们的operator=()也增加了类似移动构造的声明.我们把它叫做移动赋值

    1. image-20230318170146688
  3. 我们之前都是通过类里实现swap来实现类似移动构造的思想,现在我们可以通过右值引用来直接将库中swap进行优化了. 看下图可知通过operator=重载的右值引用版本来实现直接资源的交换.(这样我们在C++11中就可以不去专门调用类里实现的swap了普通的库内swap就可以满足我们了.)

    1. image-20230318170401816
    2. 上图C++11中的swap中c变量用移动构造得到了a变量里的内容,a变量通过移动赋值得到了b变量里的内容,b变量通过移动赋值得到了c变量里的内容.就完成了资源的交换.

注意

我们的右值引用会在传递中失去自己的右值属性.因为右值引用本身其实是有地址且可以被赋值的.

我们用一个代码来验证一下.

void func(string& s)
{
	cout << "我被调用我的参数是左值引用" << endl;
}
void func(string&& s)
{
	cout << "我被调用我的参数是右值引用" << endl;
}

int main()
{
	string&& s11 = std::to_string(123);
	func(std::to_string(123));
	func(s11);
	return 0;
}

下图是我们的运行结果,可以看出s11真的是左值.这就很难受了.因为我们使用右值引用了之后也许还要向下传递,而我们可不想把一个右值传递成左值.所以C++11出现了一个补救的措施-----完美转发.

image-20230318172144624

完美转发

先来看看他的使用方式吧

image-20230318173221449

forward<类型>(参数);来看看forward - C++ Reference (cplusplus.com)怎么说.

必须说明的是我在上图中举的例子十分简单,完美转发在实际使用时要比较注意,毕竟我们也不想在想调用右值引用的时候调到左值引用的函数,所以在传递右值引用参数的时候请思考一下是否需要完美转发.

image-20230318173717294

而且完美转发只作用一次,就是说下一层函数再调用右值引用函数的时候需要再次使用完美转发.

举个例子:

image-20230318174419919

看,我们在func里再次使用参数s来调用func就会跑到左值引用哪里,所以如果我们想再次调用右值引用参数的函数我们就需要对s进行再一次的完美转发.

(不过我们的这个代码再使用完美转发就会死递归了=-=).

万能引用

在C++11中模板有万能引用,他的形式和右值引用一样.

template<typname T>
void func(T&& t)//万能引用
{}

这里的T&& t就是万能引用,也就是说无论是左值还是右值传过来的时候我们这个都可以接收.

但是因为右值引用的属性是左值所以我们在用了万能引用引用来的t再次传递的时候要使用完美转发以保证调用到的是右值引用函数.

当然有人会想用move函数来将t参数的类型转变成右值,但是也会将原本是左值类型的参数转变为右值所以不可取.

移动构造&移动赋值

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

移动构造移动赋值例子

 // 移动构造
 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);//这个大家理解就好,我犯个懒.毕竟我们在上面的swap里swap用到了移动赋值,这里我们又用了swap是不对的但是我还是犯个懒OvO.
 return *this;
 }

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

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

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

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

// 以下代码在vs2013中不能体现,在vs2019下才能演示体现上面的特性。
class Test2
{
public:
	Test2(int a = 0)
		:_a(a)
	{
		cout << "Test2(int a)" << endl;
	}
	Test2(Test2&& a)
	{
		cout << "Test2(Test2&& a)" << endl;
	}
private:
	int _a;
};
class Person
{
public:
	Person(int t2 = 1, int age = 0)
		:_t2 (t2)
		, _age(age)
	{}
	/*Person(const Person& p)
   类成员变量初始化
   C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这
   个我们在类和对象默认就讲了,这里就不再细讲了。
   强制生成默认函数的关键字default:
   C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
   因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
   使用default关键字显示指定移动构造生成。这些关键字在后面标题为一些小小的知识点里也有讲
	:_name(p._name)
	,_age(p._age)
	{}*/
	/*Person& operator=(const Person& p)
	{
	if(this != &p)
	{
	_name = p._name;
	_age = p._age;
	}
	return *this;
	}*/
	/*~Person()
	{}*/
private:
	Test2 _t2;
	int _age;
};
int main()
{
	Person s1;

	Person s3 = std::move(s1);
	Person s4;
	return 0;
}

可变模板参数

首先我要说明的是,在这篇文章所讲述的可变模板参数内容较少,只讲一小些 使用和特性,并不能全部讲述,因为这个比较抽象.

可变类型参数是结合模板而形成的.

我们来看看这玩意长啥样吧.

template<class ...Args>
void Test(Args... args)//可变类型参数
{
	;
}
int main()
{
	Test(1,23,4);//可变类型参数的使用
	return 0;
}

从上面的代码我们可以看出,这个可变类型参数在使用的时候我们是可以传多个参数过去的,但是我们要如何才能吧这些数据从这些个参数包里提取出来呢?

像数组一样使用下标吗?

image-20230324222345915

当然不是.

是这样的:

推理可变模板参数内容

递归推参数包(我们把Args… args叫做参数包)

template<class T>
void Test(const T& val)
{
	cout << val << "->" << typeid(val).name() << endl;
	cout << " end";
}

template<class T,class ...Args>
void Test(const T& val, Args... args)
{
	cout << val <<"->" << typeid(val).name() << endl;
	Test(args...);
}
int main()
{
	//Test(1,23,4);//可变类型参数的使用
	Test(1, 23, 'x',"xxxxxxx");
	return 0;
}

image-20230324224445986

这里其实就是通过模板里T类型一个个的把参数包里的东西一个个拆除出来,直到我们不需要向我们的数据包里传递内容了也就会调用那个没有可变模板参数的函数这样我们就完成了所有数据的拆出.

void ShowList()//0个数据的时候作为结束
{}
template<class T,class ...Args>
void ShowList(const T& val, Args... args)//1个或以上调用这个函数
{
	cout << sizeof...(args) << endl;
	cout << val << "->" << typeid(val).name() << endl;
	ShowList(args...);  
}
int main()
{
	ShowList(12, "xxx", 3333);
	return 0;
}

image-20230324230012587

这个推导的原理和上一个的几乎一样只是结束的位置从还剩一个数据变成了0个数据的时候结束.向下推导的方式还是通过T那个类型参数.

不过我们可以通过sizeof…(args)当他等于0的时候使用return;来结束函数吗?

不可以

失败的:

image-20230310182713074

问题:什么是运行逻辑什么是编译逻辑,运行逻辑和编译逻辑分别在什么时候出现?

什么是编译和运行

所以我们上面这张图它就会无限的编译下去,因为C/C++语言的编译是一下子编译完的,所以就会导致失败.


template <class T>
int PrintArg(const T& t)
{
	cout << t << " ";
	return 0;//为了走形势,方便后面走{}初始化的继续进行
}
template<class... Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
    //int arr[] = {(PrintArg(args)...,0)};//这样我们就可以不让PrintArg函数返回值了因为这个逗号表达式会自动返回0;
}

int main()
{
	ShowList(1, 'x', "xxxx");
	cout << endl;
	ShowList(1, 2, 3, 4, 5, 6);
}

image-20230325160826316

这个将数据包内容打印出来的原因原理我也并不是十分了解,我只能将它理解成在初始化列表的作用下,我们的PrintArg函数只要还有数据传入我们就向下继续调用直到没有数据传入.

不过这个代码里的一些小东西还是值得说一下的,比如PrintArg的返回值,这个返回值没有意义只是为了走一个初始化列表的形势.包括下面那个逗号表达式都是为了让他返回一个数字方便初始化列表往下走.

lambda

lambda表达式:

[capture-list] (parameters) mutable -> return-type { statement }一种匿名的函数构造形势.一般在局部使用当然也可以创建在全局

拆解:

[capture-list] :捕捉列表,可以向上捕捉变量在函数内使用,我们的编译器通过[]来判断是否为lambda表达式.内容可以为空不可以省略[]

(parameters): 函数接收参数 如Add(int a,int b);他们括号里的意思相同,当然也可以省略(在没有使用mutable的时候)

mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->return-type :正如他英语翻译意思,return-type就是返回值类型,这个也可以不写,我们的lambda会自己推理可以省略

{statement}: 函数内容;

(ps:从上面的内容可以看出我们最简单的lambda是[]{}当然这个lambda什么都干不了就是了=v=);

下面是一个简单的例子.

void test_lambda()
{
	int a = 10;
	int b = 20;
	auto sum = [](int l, int r)->int {return l + r; };//lambda
	cout << sum(1, 2) << endl;
}

例子大全ovo

我们来把所有的lambda都用一下.

	auto sum1 = [a, b]() {return a + b; };
	cout << sum1() << endl;

image-20230318194133928

当然这里的a和b是不可以被改变赋值的.

image-20230318194210254

加上mutable的时候虽然可以赋值改变a或者b了但是这里的a和b是临时变量,在函数内改变并不可以改变本体.(不过我们可以捕捉他的引用在下面会有例子)

image-20230318194256190


	int a = 10;
	int b = 20;
	auto sum3 = [&a, &b]()mutable {a = 3; return a + b; };
	cout << sum3() << endl;
	cout << a << endl;

image-20230318194623030

这里我们的a和b就是本体了,我们在函数体内进行更改也可以改变到本体了.


	int a = 10;
	int b = 20;
	auto sum4 = [=]() {return a + b; };
	cout << sum4() << endl;

这里捕捉列表里的=的意思是捕捉上文里所有的变量以值传递的方式用于我们函数体内的使用.

image-20230318194925126

当然这里的例子里他依旧是const类型的a和b,而且他俩是临时变量.


	int a = 10;
	int b = 20;
	auto sum5 = [&]()mutable {a = 3; return a + b; };
	cout << sum5() << endl;
	cout << a << endl;

&和=类似只是他是将前方所有的变量以引用的形势捕捉到函数体内使用

image-20230318195827355


我们这些在捕捉列表里的其实可以混合使用

如下:

	int a = 0;
	int b = 3;
	int c = 2;
	int d = 7;
	auto sum6 = [=, &a]()mutable {a = 1; b = 4; return b + c + d; };
	cout << sum6() << endl;
	cout << a << endl;//a本体改变
	cout << b << endl;//b本体没有发生改变

image-20230318201643968

混合使用的时候使用 用,来分割开.

捕捉列表声明:

  • [var] 使用值传递的方式捕捉var变量
  • [=] 表示值传递方式捕捉所有上方变量(包括this)
  • [&]表示引用传递的方式捕捉所有上方变量(包括this_
  • [&var] 表示引用的方式捕捉var变量
  • [this] 表示值传递捕捉类里的this指针.

注意:

  1. 捕捉列表不允许重复的变量传递,否则会造成编译错误

  2. 如:[=,a],=已经传递过a了捕捉a就会导致重复传递使编译错误.

  3. lambda表达式之间不能互相赋值,即使看起来类型相同.

    1. void (*PF)();
      int main()
      {
       	auto f1 = []{cout << "hello world" << endl; };
       	auto f2 = []{cout << "hello world" << endl; };
          // 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
       	//f1 = f2;   // 编译失败--->提示找不到operator=()
          // 允许使用一个lambda表达式拷贝构造一个新的副本
       	auto f3(f2);
       	f3();
       	// 可以将lambda表达式赋值给相同类型的函数指针
       	PF = f2;
       	PF();
       	return 0;
      }
      
  4. 在块作用域以外的lambda函数捕捉列表必须为空.

    1. 块作用域:被{}包裹的作用域
    2. 及全局外的lambda捕捉列表为空
      1. image-20230323221715991
  5. 在块作用域中的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非次作用域或者非局部变量都会报错

    1. 及在一个块作用域里的lambda没办法捕捉全局和其他块作用域的变量.
      1. image-20230323221801227
  6. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割.

    1. 如:[=,&a,&b]这里就是以引用传参的方式传递a和b变量其他所有变量使用值传参传递.

lambda底层

lambda底层内容:仿函数类型.

我们每创建一个lambda函数我们的编译器就会自动的为我们创造一个仿函数类型.并将我们的lambda替换成一个仿函数他的类型名称为:lambda_uuid;(uuid是一个不会重复的字符串,用于标识)image-20230313212604371

因为仿函数的类型总是不相同的所以在两个哪怕相同的lambda也是不可以进行赋值的.

lambda重点

  1. 用法格式

  2. 捕捉列表规则

  3. 底层原理

包装器

包装器可以将许多不同类型的可执行语句变成相同类型的语句.

比如我们可以将:仿函数 lambda表达式 函数 他们只要是具有相同的返回值类型和参数类型我们就都可以将他们转换成相同类型的参数.我们先来看看都是如何使用的.

double fun1(double l)//普通函数
{
	return l  / 2;
}

struct fun2
{
	double operator()(double l)//仿函数
	{
		return l  / 3;
	}
};

void testfunction()
{
    //f1对应普通函数
	std::function<double(double)> f1 = fun1;
    //f2对应仿函数
	std::function<double(double)> f2 = fun2();
    //f3对应lambda表达式
	std::function<double(double)> f3 = [](double i)->double {return i / 4; };
	cout << f1(10) << endl;
	cout << f2(10) << endl;
	cout << f3(10) << endl;
}

运行结果如下:

image-20230326154158178

当然类里的成员函数和静态成员函数等也是可以被包装器包装的.

class Test2
{
public:
	Test2(int a = 0,int b = 0)
		:_a(a)
		,_b(b)
	{}
	void test1()//内部还有一个this指针
	{
		cout << _a << endl << _b << endl;
	}
	//内部没有this指针
	static void test2()
	{
		cout << "我是个在Test类里的静态函数" << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{

	Test2 t2(10,2);
	t2.test1();
	//非静态成员函数
	std::function<void(Test2)> f1 = &Test2::test1;//记得取地址
	//静态成员函数
	std::function<void()> f2 = &Test2::test2;//记得取地址
	//非静态成员函数
	f1(Test2());
	//静态成员函数
	f2();
	//Different(fun1, 11.11);
	//testfunction();
}

image-20230326160046805

非静态成员函数因为在参数中其实是有this指针的,所以在创建包装器的时候应该告知包装器他的类名,如我们f1里的Test2,然后在使用的时候我们需要传递一个匿名对象(毕竟我们不可能在外面传递一个this指针过去)根据需要我们设置这个匿名对象.

注意:在创建类成员函数的包装器的时候我们记得取地址.

接下来我们来验证一下,在相同返回值类型相同参数类型的包装器类型是相同的和没有使用包装器包装的其他类型是不相同的.

我们来创建三种类型的函数普通函数 lambda表达式 仿函数 就不使用类内函数了我们在包装器的场景中一般不讲类内函数包装在一起.

来看看我们用去区别不同类型的函数:

//通过不同的类型我们的模板创建不同的函数后不同的count来区别出类型是否相同
template<class T ,class X>
int Different(T t, X x)
{
	static int count = 1;//通过看他的地址和本身所携带的数字来推算是否是同一个类型
	cout << "count: " << count++ << endl;
	cout << "count: " << &count << endl;
	return t(x);//t注定是一个可执行变量x注定是一个数字所以直接这样写了.
}

下面是我们的所有测试内容

template<class T, class X>
double Different(T t, X x)
{
	static int count = 1;
	cout << "count: " << count++ << endl;
	cout << "count: " << &count << endl;
	return t(x);
}

double fun1(double l)
{
	return l / 2;
}

struct fun2
{
	double operator()(double l)
	{
		return l / 3;
	}
};
int main()
{
	Different(fun1, 11.11);//测试普通函数
	Different(fun2(), 11.11);//测试仿函数
	Different([](double l)->double {return l / 4; }, 11.11);//lambda表达式
	//Different(fun1, 11.11);
	//testfunction();
}

image-20230326163326529

那我们用包装器将他们包装一下来看看是否是统一类型呢?

int main()
{
    //包装后
	std::function<double(double)> f1 = fun1;
	std::function<double(double)> f2 = fun2();
	std::function<double(double)> f3 = [](double i)->double {return i / 4; };
	cout << Different(f1, 11.11) << endl;
	cout << Different(f2, 11.11) << endl;
	cout << Different(f3, 11.11) << endl;
    //使用的非包装器
	//cout << Different(fun1, 11.11) << endl;
	//cout << Different(fun2(), 11.11) << endl;
	//cout << Different([](double l)->double {return l / 4; }, 11.11) << endl;
}

image-20230326163916906

相同了.

这样的特征除了可以帮助我们提高模板的使用效率,还可以将它用在map上,我们可以通过包装器将字符和函数对应起来.这样我们在使用的时候就很方便了.

比如下面我对map的使用:
image-20230326165727217

当然笔者这里写的实在很图简便,不过我想表达的意思就在其中就是了.(当然包装器也可以很好的表达出lambda的类型)

bind

通过和包装器一起使用调整参数的个数和顺序.

我们来看看如何使用吧:

class Plus
{
public:
	Plus(int x = 2)
		:_x(x)
	{}
	int plusi( int r)
	{
		return _x + r;
	}

private:
	int _x;
};
void test_band()
{
	std::function<int(int)> f1 = std::bind(&Plus::plusi, Plus(), std::placeholders::_1);//bind使用
	cout << f1(10) << endl;
}
int main()
{
	test_band();//运行结果为12	
	return 0;
}

运行结果为12

我们来看看bind使用给我们带来了什么结果,首先plusi这个函数是个类内函数他里面是有this指针的,按照正常将我们需要在包装器哪里就给他类名然后在使用的时候我们再传递我们的成员来方便他使用.

但是我们经过了bind之后我们不仅在包装器类型上发生了改变而且我们在使用运行的时候也没有传递成员.

这就是包装器的作用之一,调整参数个数.

来看看他是如何使用的吧.

std::function<int(int)> f1 = std::bind(&Plus::plusi, Plus(), std::placeholders::_1);//bind使用

&Plus::plusi :指定对象,原本是直接给我们包装器的,现在在bind里做第一个参数.

Plus(): 第一个参数,不过我们这里将它绑定在了包装器上,这也是我们使用bind的原因.后续我们在使用的时候也就不需要传递了,而且这样也让我们类内成员函数和普通类型函数拥有了相同的类型.

std::placeholders::_1:placeholders是命名空间,_1是里面的元素表示需要在使用时显示传递的第一个参数,整体作为占位符存在.

占位符是根据我们想要传递的参数个数决定的,我们想要传递2个就要有两个占位符.而占位符的顺序其实就是我们传递参数的顺序.

我来演示一下:

int reduce(int l, int r)
{
	return l - r;
}
void test_band()
{
	std::function<int(int, int)> f1 = std::bind(reduce,std::placeholders::_1, std::placeholders::_2);
	std::function<int(int, int)> f2 = std::bind(reduce,std::placeholders::_2, std::placeholders::_1);//这个placeholders的顺序就是我们的传递参数的顺序._1,_2就是正常顺序,_2,_1就是顺序逆反了.当然我们需要传递三个参数的时候就传递到_3就好了,顺序自己调整就好.
	cout << f1(1, 2) << endl;
	cout << f2(1, 2) << endl;
}

image-20230326180903048

如图.

切换顺序就是这样的.不过很少用就是了.

一些小小的知识点

decltype
一个关键字,将变量的类型声明为表达式指定的类型
举例: int x = 1; decltype(x) ret;这个ret的类型就是int 同理decltype(&x)ret的类型就是int*

array
array是提供C++11提供的对标int a1[10]数组的一个类型. array是将数据存储在栈上的而vector是储存在堆上的.

default
强制生成默认构造函数
如:Person(Person&& pp) = default;

delete
强制不生成默认构造函数
如:Person(Person&& pp) = delete;
如果用户调用就会报错.

结尾

我们C++11就暂时讲到这里剩下的线程库就先不讲了后面再说.阿巴巴

拜拜~.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

就一个挺垃圾的跑路人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值