【C++】C++11(万能模版、完美转发,可变参数模版,lambda表达式,包装器function+bind绑定)

news2025/1/11 2:59:48

前言:

       我们上一章一节已经接触了C++11的一些特性,本章将继续更深入介绍C++11中其他使用且重要的一些功能。

目录

(一)万能模版和完美转发

1、万能模版

2、完美转发

(二)可变参数模版

1、可变参数模版的使用

2、emplace_back

(三)lambda表达式

1、引入

2、lambda表达式的定义

3、lambda表达式的用法

(四)包装器function及bind绑定

1、包装器

 2、bind绑定


(一)万能模版和完美转发

1、万能模版

万能模版样式:

  • 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
  • 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
  • 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值

 这里还是太抽象,我们来看一个例子:
 


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

这段代码的输出结果是:

输出全是左值引用,很显然这并不是我们想要的,因为无论传的是左值还是右值,都将退化成左值。

那怎么解决这一问题呢???这就要用到了我们下面讲解的完美转发。

2、完美转发

我们为了解决类似上述场景的问题,希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的:

完美转发:

这里就用到了我们forward的函数模板。

沿用上面的例子我们来看:


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

//std::forward<T>(t)在传参的过程中保持了t的原生类型属性。

template<typename T>
void PerfectForward(T&& t)
{
	//完美转发,按照原封不动的方式进行转发
	//Fun(t);
	Fun(std::forward<T>(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;
}

 运行结果:

C++11新提供的forward函数模板,可以解决上述问题,使得传右值不会退化成左值。

(二)可变参数模版

1、可变参数模版的使用

首先在学C语言的时候,我们用的printf函数是一个可变参数函数,printf从语法上说是可以写多个参数,然后该函数自己识别。

可变参数模版:

解释:

  • Args是一个模板参数包,args是一个函数形参参数包
  • 声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数
上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为 参数
,它里面包含了 0 N N>=0 )个模版参数。我们无法直接获取参数包 args 中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。

首先,我们介绍如何参数个数的方法:

template <class ...Args>
void ShowList1(Args... args)
{
	//参数个数
	cout << sizeof...(args) << endl;
}

//不一定非要用Args也可以取别的名字
template <class ...X>
void ShowList2(X... y)
{
	cout << sizeof...(y) << endl;
}

int main()
{
	ShowList1(1, 'x', 1.1);
	ShowList2(1, 2, 3, 4, 5);

	return 0;
}

主要使用sizeof...( )形式来获取参数个数。

那么我们怎么一一获取参数包的值的呢?有下面两种方法:

1、递归函数方式展开参数包
// 递归终止函数
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;
}
  • 类似于递归的一个过程,但是不是递归,最后都有一个结束条件,参数包中的模板参数是一个一个减少的。
  • 整个推导的过程是在编译的时候进行的(这是个编译的过程 – 编译时决议)

2、逗号表达式展开参数包

template <class T>
void PrintArg(T t)
{
 cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
 int arr[] = { (PrintArg(args), 0)... };
 cout << endl;
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
 return 0;
}
解释:
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...
(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数包。
输出结果如下:

2、emplace_back

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?

  • emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
  • 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
  • 可以不传pair,可以直接传参数
  • 拿到参数自己构建对象(保证了底层一定是右值 —— 直接去构造的)
     

 对比:

push_back():

  • 参数如果给的是左值是:构造 + 拷贝构造
  • 参数如果给的是右值是:构造 + 移动构造
  • 函数传值返回也是个右值

emplace_back():

  • 直接构造

两者的底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。但其实两者差别并不大。

(三)lambda表达式

1、引入

C++98中,我们会遇到下面这种场景:

如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法
#include <algorithm>
#include <functional>
int main()
{
int array[] = {4,1,8,5,3,7,0,9,2,6};
// 默认按照小于比较,排出来结果是升序
std::sort(array, array+sizeof(array)/sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
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());
}
人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,
都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,
这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

2、lambda表达式的定义

定义了一个可以调用的对象 / 匿名函数,一般定义在局部,特点是可以深度绑定了局部的数据。

  • lambda表达式,实际上是一个匿名函数,实际上是在定义一个函数(局部的函数)
  • lambda表达式通常是用来定义小函数
  • lambda表达式在局部是很好用的,最特别的就是捕捉列表

auto Add1 = [](int x, int y)->int{return x + y; }; Add叫lambda表达式的对象 – lambda定义的是一个对象。

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

注意:

  • 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
  • 因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

3、lambda表达式的用法

int main()
{
	int a = 0, b = 200;
	
	//一般是局部匿名函数  也可以写到全局(一般都不写返回值)
	//参数列表(无参的时候)和返回值(表达式自动推导)也可以省略掉
	auto Add1 = [](int x, int y)->double {return (x + y) / 3.0; };
	auto Add2 = [](int x, int y)->int {return (x + y) / 3.0; };

	//参数列表可以省略了
	auto Add3 = [a, b] {return (a + b) / 3.0; };

	//调用是和普通的函数一样的
	cout << Add1(a, b) << endl;
	cout << Add2(a, b) << endl;

	//调用没有区别,但是没有实参,因为捕捉了
	cout << Add3() << endl;
}
  • 一般是局部匿名函数 也可以写到全局(一般都不写返回值)
  • 参数列表(无参的时候)和返回值类型(表达式自动推导)也可以省略掉
  • 和普通函数调用没区别,但是没有实参,因为捕捉了

捕捉列表的用法:

用lambda表达式来实现交换两个数:

int main()
{
	int a = 0, b = 200;

	//方法一:
	auto Swap1 = [](int& x, int& y)->void {
		int tmp = x;
		x = y;
		y = tmp;
	};

	Swap1(a, b);
	cout << a << " " << b << endl;

	//方法二:
	//mutable 只是让传值捕捉变量const属性去掉了
	//mutable要和()参数列表配在一起
	//可以认为里面的a,b还是外面a,b的拷贝 -- 里面改变外面还是不变
	//所以mutable实际没什么价值
	//这样写还是没有交换 -- 只是编译通过了但是达不到我们想要的效果
	//auto Swap2 = [a, b]()mutable->void {
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//};

	//用引用的方式捕捉(按值捕捉不能改变)
	auto Swap2 = [&a, &b]()->void {
		int tmp = a;
		a = b;
		b = tmp;
	};

	Swap2();
	cout << a << " " << b << endl;
}

方法一:

  • 就像普通函数那样传引用传参

方法二:

  • 不能通过传值传参,首先捕捉列表捕捉的数据具有const属性,不能修改
  • 其次mutable可以让传值捕捉变量const属性去掉了,即使去掉了
  • mutable要和()参数列表配在一起
  • 所以mutable实际没什么价值
  • 可以认为里面的a,b还是外面a,b的拷贝 ,里面改变外面还是不变

所以只能按照引用的方式捕捉
 

各种混合捕捉:
 

int main()
{
	int c = 2, d = 3, e = 4, f = 5, g = 6, ret;

	//传值的方式捕捉全部对象
	auto Func1 = [=] {
		return c + d * e / f + g;
	};

	cout << Func1() << endl;

	//传引用捕捉全部对象
	auto Func2 = [&] {
		ret = c + d * e / f + g;
	};

	Func2();
	cout << ret << endl;

	//混着捕捉
	auto Func3 = [c, d, &ret] {
		ret = c + d;
	};

	Func3();
	cout << ret << endl;

	//ret传引用捕捉 其他全部传值捕捉
	auto Func4 = [=, &ret] {
		ret = c + d * e / f + g;

		//传值捕捉默认是加了const的
		//c = 1;
	};

	Func4();
	cout << ret << endl;

	return 0;
}

每一个lambda表达式类型名字不一样:

int main()
{
	auto Add = [](int x, int y)->int { return x + y; };

	cout << typeid(Add).name() << endl;
	return 0;
}

仿函数的名称后面叫uuid,一组随机字符串,通过某个算法得到的,使得每个lambda表达式的名字都不同。

注意:

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

(四)包装器function及bind绑定

1、包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

模板参数说明:

  • Ret: 被调用函数的返回类型
  • Args…: 被调用函数的形参

看下面一段程序:

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函数模板实例化了三份。
  • func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lamber表达式对象?
  • 所以这些都是可调用的类型!

如此丰富的类型,可能会导致模板的效率低下!

包装器可以很好的解决上面的问题:


#include <functional>
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()
{// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

  • function可以认为是一个类模板,包装可调用对象
  • 经过包装器包装之后得到的都是一个统一的类型function
  • 最后count的地址相同,并且count到3了,说明包装器是统一了类型

注意,特殊的包装:

 2、bind绑定

简介:

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。 一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

bind是个函数模板,调整了调用对象参数,用来调整个数和顺序,生成一个新的可调用对象:

包装器的特点就是统一了类型,但是值得注意的是包装时一定要注意匹配,如下:

这就在包装时不匹配,所在包装时一定要匹配。

下面的场景我们存一个map,这时就要求function包装器要统一了:

不过不免会有一些特殊情况:


func1和func2是可以的,但是func3就难受了呀,不匹配啊…

此时我们就可以通过bind函数来调整一下参数个数:

还可以调整参数顺序:

 

 详细代码:

#include <functional>
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,
		placeholders::_2);
	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	//func2的类型为 function<void(int, int, int)> 与func1类型一样
	//表示绑定函数 plus 的第一,二为: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2() << endl;
	Sub s;
	// 绑定成员函数
	std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,
		placeholders::_1, placeholders::_2);
	// 参数调换顺序
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, 
	placeholders::_2, placeholders::_1);
	cout << func3(1, 2) << endl;
	cout << func4(1, 2) << endl;
	return 0;
}

实际例子:


int Div(int a, int b)
{
	return a / b;
}

int Plus(int a, int b)
{
	return a + b;
}

int Mul(int a, int b, double rate)
{
	return a * b * rate;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

using namespace placeholders;

// 11:50继续
int main()
{
	// 调整个数, 绑定死固定参数
	function<int(int, int)> funcPlus = Plus;
	//function<int(Sub, int, int)> funcSub = &Sub::sub;
	function<int(int, int)> funcSub = bind(&Sub::sub, Sub(), _1, _2);
	function<int(int, int)> funcMul = bind(Mul, _1, _2, 1.5);
	map<string, function<int(int, int)>> opFuncMap =
	{
		{ "+", Plus},
		{ "-", bind(&Sub::sub, Sub(), _1, _2)}
	};

	cout << funcPlus(1, 2) << endl;
	cout << funcSub(1, 2) << endl;
	cout << funcMul(2, 2) << endl;

	cout << opFuncMap["+"](1, 2) << endl;
	cout << opFuncMap["-"](1, 2) << endl;



	int x = 2, y = 10;
	cout << Div(x, y) << endl;

	// 调整顺序 -- 鸡肋
	// _1 _2.... 定义在placeholders命名空间中,代表绑定函数对象的形参,
	// _1,_2...分别代表第一个形参、第二个形参...
	//bind(Div, placeholders::_1, placeholders::_2);
	//auto bindFunc1 = bind(Div, _1, _2);
	//function<int(int, int)> bindFunc2 = bind(Div, _2, _1);
	//cout << bindFunc1(x, y) << endl;
	//cout << bindFunc2(x, y) << endl;



	return 0;
}

感谢您的阅读!

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

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

相关文章

【Node.js】NPM 和 package.json

NPM npm 是 Node.js 的包管理工具&#xff0c;基于命令行&#xff0c;用于安装、升级、移除、管理依赖项。 常用命令&#xff1a; npm init&#xff1a;初始化一个新的 npm 项目&#xff0c;创建 package.json 文件。&#xff08;括号里为默认值&#xff09; description&am…

信息可视化和数据可视化的异同和其他比较,到底怎么区分呢?

什么是数据可视化? 根据维塔利弗里德曼的说法&#xff1a; “数据可视化的主要目标是通过图形方式清晰有效地传达数据。” 数据可视化是将数据转化为图表、图形、地图等视觉元素的过程&#xff0c;以便人们更容易理解数据的趋势、关系和模式。它通常强调的是对数据的视觉呈…

Git开发环境使用

目录 git简单介绍 问题 在Git Bash下的操作 在工具idea中使用git 创建分支 合并分支 部分命令合集 git简单介绍 Linux发布了一套属于自己的版本控制系统&#xff0c;运用git可以实现&#xff0c; 代码回溯 &#xff0c; 版本切换 因为是Git是分布式版本控制系统&#x…

unity脚本_Time c#

Time 用于游戏中参与位移 计时 时间暂停等 时间缩放比例&#xff1a; 首先在场景上新建一个空物体 将脚本挂载到空物体上运行 另外重要的两点

从「博客园」的困境,到「用爱发电」~

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

JavaScript中的map()和forEach()方法有什么区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【javaweb】学习日记Day11 - tlias智能管理系统 - 文件上传 新增 修改员工 配置文件

目录 一、员工管理功能开发 1、新增员工 postman报错500的原因 &#xff08;1&#xff09;Controller类 &#xff08;2&#xff09;Service类 &#xff08;3&#xff09;Mapper类 2、根据ID查询 &#xff08;1&#xff09;Controller类 &#xff08;2&#xff09;Serv…

【网络安全 --- kali2023安装】超详细的kali2023安装教程(提供镜像资源)

如果你还没有安装vmware 虚拟机&#xff0c;请参考下面博客安装 【网络安全 --- 工具安装】VMware 16.0 详细安装过程&#xff08;提供资源&#xff09;-CSDN博客【网络安全 --- 工具安装】VMware 16.0 详细安装过程&#xff08;提供资源&#xff09;https://blog.csdn.net/m0…

mysql MVCC(多版本并发控制)理解

最近看MVCC相关资料&#xff0c;这边做一个记录总结&#xff0c;方便后续理解。 目录 一、MVCC相关概念 二、MVCC实现原理 1.隐藏字段 2.undo log 3.Read View 4.MVCC的整体处理流程 5. RC&#xff0c;RR级级别下的innoDB快照读有什么不同 6.总结 一、MVCC相关概念 1…

在Ubuntu中批量创建用户

一、背景知识 在Linux操作系统中创建新用户可以使用useradd或adduser命令。 使用useradd命令创建用户时&#xff0c;不会在/home目录下创建用户文件夹&#xff0c;需要用户自己指定主目录和bash目录的位置。同时&#xff0c;创建的用户没有设置密码&#xff0c;无法进行登录&a…

electronjs入门-聊天应用程序,与Electron.js通信

随着第一章中构建的应用程序&#xff0c;我们将开始将其与Electron框架中的模块集成&#xff0c;并以此为基础&#xff0c;以更实用的方式了解它们。 过程之间的通信 根据第二章中的解释&#xff0c;我们将发送每个进程之间的消息&#xff1b;具体来说联系人和聊天&#xff1…

对于无法直接获取URL的数据爬虫

在爬学校安全教育题库的时候发现题库分页实际上执行了一段js代码&#xff0c;如下图所示 点击下一页时是执行了函数doPostBack&#xff0c;查看页面源码如下 点击下一页后这段js提交了一个表单&#xff0c;随后后端返回对应数据&#xff0c;一开始尝试分析获取对应两个参数&a…

MM-Camera架构-ProcessCaptureRequest 流程分析

文章目录 processCaptureRequest\_3\_41.1 mDevice1.2 mDevice->ops->process\_capture\_request1.3 hardware to vendor mct\_shimlayer\_process\_event2.1 mct\_shimlayer\_handle\_parm2.2 mct\_shimlayer\_reg\_buffer processCaptureRequest_3_4 sdm660的摄像头走…

stm32的时钟、中断的配置(针对寄存器),一些基础知识

一、学习参考资料 &#xff08;1&#xff09;正点原子的寄存器源码。 &#xff08;2&#xff09;STM32F103最小系统板开发指南-寄存器版本_V1.1&#xff08;正点&#xff09; &#xff08;3&#xff09;STM32F103最小系统板开发指南-库函数版本_V1.1&#xff08;正点&#xff0…

QChart使用说明

一.使用说明 Qt官网例程&#xff1a;https://doc.qt.io/qt-5/qtcharts-examples.html QChart&#xff1a;用于管理图表中的线、图例和轴的图形表示。可以简单理解为是一个画布。QChartView&#xff1a;视图组件&#xff0c;无法单独进行显示&#xff0c;需要依附其他组件进行…

高德地图开发实战案例:实现信息弹出框的富文本展示效果

marker.content "<p classcardsBg></p>";.cardsBg {width: 246px;height: 426px;background: url(../images/cards.png) no-repeat center center; }其中cards.png为整个弹出模态框的背景图片&#xff0c;做到这一步。仍旧会自带高德地图的样式&#xf…

C指针【嵌入式】

一、到底什么是指针 1.指针变量和普通变量的区别 &#xff08;1&#xff09;首先非常关键的是&#xff0c;指针的实质就是一个变量&#xff0c;它根普通变量没有任何本质区别。指针完整的名字应该叫指针变量&#xff0c;简称为指针。 #include<stdio.h> void main(){//a…

perl语言——length.pl脚本(统计fasta文件序列长度)

Perl脚本——stat.pl&#xff08;统计fasta文件序列长度&#xff09; 相比Perl语言&#xff0c;现在python用的多。但是perl依旧是生信学习的一门课程&#xff0c;还是有人在写&#xff0c;所以你至少要会读。 #!/use/bin/perl #perl解析器$inputFile $ARGV[0]; #输…

ComplexHeatmap热图专栏 | 1.1 基础热图绘制

1 写在前面 最近在作图&#xff0c;一直在寻找《小杜的生信笔记》前期发表的代码。众所周知&#xff0c;小杜的教程基本都是平时自己用到的绘图教程&#xff0c;也是自己一个分享和总结。 自己在后期作图的时候&#xff0c;也会去寻找自己前期的教程作为基础&#xff0c;进行…

吃鸡达人必备!超实用干货激爽分享!

大家好&#xff01;作为一名专业吃鸡行家&#xff0c;今天我将为大家分享一些关于提高游戏战斗力和分享顶级游戏作战干货的秘诀&#xff0c;还有一些方便吃鸡作图、装备皮肤库存展示和查询的技巧&#xff01; 首先&#xff0c;让我们来介绍一些吃鸡作图工具推荐。无论是新手还是…