【C++11】可变参数和lambda表达式

news2025/1/15 21:03:27

目录

1.可变参数模板

1.1可变参数的模板 

1.2参数包的展开方式 

1.21递归函数展开参数包

 1.3逗号表达式展开参数包

2.STL库中的emplace相关接口

3.lambda表达式 

3.1lambda的引入

3.2lambda的介绍

列表使用 lambda 表达式捕获

lambda实现swap函数 

lambda表达式之间不能相互赋值 


1.可变参数模板

可变参数模板可以说是C++的一大新收获,也是C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数的函数模板和类模板。但是可变参数模板上手需要一定的技巧性,以及对C++进行初步的认识,所以我们来具体讲解一下。

1.1可变参数的模板 

template<class ...Args>
返回类型 函数名(Args... args)
{ 
   //函数主体
}
//Args+省略号叫做模板参数包,args是函数形参参数包
//Args... args代表参数从0到多个

 例如:

template<class ...Args>
void ShowList(Args... args)
{ 
   //函数主体
}
  • 这里的template<>不禁让人想到了类模板和函数模板,这个其实和他很像,只不过那个指定参数了而这个Args...代表这从0到N的模板参数。
  • 最为关键的是Args并不是关键字,只不过大家都用它来形容可变参数模板,你也可以叫他Brgs还可以叫做Crgs我是没有啥意见。

接下来我们用ShowList写一个传入不同参数的代码。

//可变参数
template<class...Args>
void ShowList(Args...args)
{
	cout << sizeof...(args) << endl;  //获取函数包中参数的个数
	
}

接下来传参------》

int main()
{
	string str("hello");
	ShowList();      //0
	ShowList(1);     //1
	ShowList(11,str);  //2
	ShowList(1,'A',string("hello,world"), 12);  //4
}

特别提醒:但是我们无法直接获取参数包中的每个参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点 。语法并不支持使用args[i]的方式来获取参数包中的参数同样也不支持范围for那个。

template<class...Args>
void ShowList(Args...args)
{
	cout << sizeof...(args) << endl;  //打印传入参数的个数
	for (size_t i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " ";
	}
	cout << endl;
}

1.2参数包的展开方式 

1.21递归函数展开参数包

这里用到两部分递归函数,但是这两部分递归函数的函数名相同

  • 递归函数
  • 递归终止函数

递归展开参数包的方式如下:

  • 给函数模板增加一个模板参数,这样就可以从接收到的参数包中分离出一个参数出来。
  • 在函数模板中递归调用该函数模板,调用时传入剩下的参数包。
  • 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来。

每次我们都要先写递归函数,而递归终结函数可以理解为当参数包中所有参数都被取出后,就会调用这个杀死循环的终止函数了。

template<class T,class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << val << " ";
	ShowList(args...);
}

这个可变参数的整体就写好了,最棘手的就是那个递归终结函数了。

此时这个终结函数又会分为两类有参和无参两种情况,这两种情况还不太一样。

我们先看一下无参的情况:

void ShowList()
{
	cout << endl;
}
template<class T,class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << val << " ";
	ShowList(args...);
}

此时当参数包的个数为0个的时候,就会走此函数,完成递归终止。

  • 如果外部调用ShowList函数时就没有传入参数,那么就会直接匹配到无参的递归终止函数。
  • 而我们本意是想让外部调用ShowList函数时匹配的都是函数模板,并不是让外部调用时直接匹配到这个递归终止函数。也就是说这个通道是我的老员工用的并且我也不想借给外界人员用,所以我们不得不再给外部人员提供一个新道路。
//递归终止函数
void ShowListArg()
{
	cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	ShowListArg(args...); //将剩下参数继续向下传
}

//外部人员专业通道
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}

带参的情况:

//递归终止函数-------》带参情况
template<class T>
void ShowListArg(const T& val)  //终止函数带参
{
	cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{
	cout << value << " "; //打印传入的若干参数中的第一个参数
	ShowListArg(args...); //将剩下参数继续向下传
}

//外部人员专业通道
template<class ...Args>
void ShowList(Args... args)
{
	ShowListArg(args...);
}
int main()
{
	ShowListArg(1,'A',string("hello,world"), 12);  //4
}
  • 当传入参数包中参数的个数为1时,就会传入这个终止函数中。
  • 但该方法有一个弊端就是,我们在调用ShowList函数时必须至少传入一个参数,否则就会报错。因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个参数。

 1.3逗号表达式展开参数包

C++只允许数组里面是同一种类型,但是模板的可变参数就意味着我参数包的类型并不统一,会出现一会是int,一会是char……。为了解决此问题,我们可以单独封装一层函数(PrintArg),此函数专门用于获得参数包的每个数据并输出,我们可以使用逗号表达式将返回值重新放到数组中。

  • 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值进行返回。
  • 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
  • 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。
//逗号表达式展开参数包
template<class T>
void PrintArg(const T& t)
{
	cout << t << " ";
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗号表达式
	cout << endl;
}

int main()
{
	ShowList(1,'A',string("hello,world"), 12);  //4
}

  •  可变参数的省略号需要加在逗号表达式外面,表示需要将逗号表达式展开,如果将省略号加在args的后面,那么参数包将会被展开后全部传入PrintArg函数,代码中的{(PrintArg(args), 0)...}将会展开成{(PrintArg(arg1), 0), (PrintArg(arg2), 0)(PrintArg(arg3), 0), etc...}。

2.STL库中的emplace相关接口

 vector - C++ Reference (cplusplus.com) 我们可以进入cplusplus官网搜索vector,就可以看到C++11新增的emplace

template <class... Args>
  void emplace_back (Args&&... args);

 &&不是特指右值引用而是代表左右值引用都可以。

我们以vector的emplace_back和push_back为例进行说明 :

  • 调用push_back函数插入元素时,可以传入左值对象或者右值对象,也可以使用列表进行初始化。
  • 调用emplace_back函数插入元素时,也可以传入左值对象或者右值对象,但不可以使用列表进行初始化。
  • 除此之外,emplace系列接口最大的特点就是,插入元素时可以传入用于构造元素的参数包。
     
//emplace 相关应用
int main()
{
	vector<pair<int, string> > vec;
	pair<int, string> kv(22, "emplace");
	vec.push_back(kv);                                 //传左值
	vec.push_back(pair<int, string>(1222, "2022"));    //传右值
	vec.push_back({ 30, "abc" });                      //列表初始化

	vec.emplace_back(kv);                              //传左值
	vec.emplace_back(pair<int, string>(1222, "2022")); //传右值
	vec.emplace_back(50, "555");                       //传参数包
	for (auto e : vec)
	{
		cout << e.first << ": " << e.second << endl;
	}
}

由于emplace系列接口的可变模板参数的类型都是万能引用,因此既可以接收左值对象也可以接收右值对象还可以接收参数包

  • 如果调用emplace系列接口时传入的是左值对象,那么首先需要先在此之前调用构造函数实例化出一个左值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造函数。
  • 如果调用emplace系列接口时传入的是右值对象,那么就需要在此之前调用构造函数实例化出一个右值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,就会匹配到移动构造函数。
  • 如果调用emplace系列接口时传入的是参数包,那就可以直接调用函数进行插入,并且最终在使用定位new表达式调用构造函数对空间进行初始化时,匹配到的是构造函数。

使用emplace的原因:

  1. 之所以说emplace系列接口更高效是因为emplace系列接口最大的特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝
  2. 但emplace系列接口并不是在所有场景下都比原有的插入接口高效,如果传入的是左值对象或右值对象,那么emplace系列接口的效率其实和原有的插入接口的效率是一样的。

3.lambda表达式 

3.1lambda的引入

lambda表达式有如下优点:

  • 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
  • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
  • 在需要的时间和地点实现功能闭包,使程序更灵活。

但是lambda具体的应用在什么地方?--------->

在学校这个完整的教育系统中老师会根据学生的考试成绩进行排名,而学校会根据班级成绩从而对教师进行考核,现在学校的后勤部要根据这些指标进行升序,降序排列。这里就会用到lambda表达式,这里一般会选择仿函数来指定排序的主要方式。

//lambda表达式
struct Teacher
{
	string _name; // 名字
	double _point; // 班级成绩
	int _evaluate; // 总评分
	Teacher(const char* str, double price, double evaluate)
		:_name(str)
		, _point(price)
		, _evaluate(evaluate)
	{}
};
struct ComparePointLess
{
	bool operator()(const Teacher& gl, const Teacher& gr)
	{
		return gl._point < gr._point;
	}
};
struct ComparePointGreater
{
	bool operator()(const Teacher& gl, const Teacher& gr)
	{
		return gl._point > gr._point;
	}
};
int main()
{
	vector<Teacher> v = { { "李老师", 88, 8.9 }, { "张老师", 91, 8.7 }, { "徐老师", 86,9 }, { "王老师", 87, 8.7 } };
	sort(v.begin(), v.end(), ComparePointLess());//按成绩升序排
	sort(v.begin(), v.end(), ComparePointGreater());//按成绩降序排
}

这里我们就可以用lambda表达式解决这个问题了。

int main()
{
	vector<Teacher> v = { { "李老师", 88, 8.9 }, { "张老师", 91, 8.7 }, { "徐老师", 86,9 }, { "王老师", 87, 8.7 } };
	sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) {
		return g1._point < g2._point; });
	sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) {
		return g1._point > g2._point; });
	sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) {
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Teacher& g1, const Teacher& g2) {
		return g1._evaluate > g2._evaluate; });
}

3.2lambda的介绍

lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式可简单归纳如下:

[ capture -list] ( params ) mutable -> return { body; };
  • 其中 capture 是捕获列表,params 是参数表,
  • mutable 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • return 是返回值类型,body是函数。

lambda函数的参数列表和返回值类型都是可选部分,但捕捉列表和函数体是不可省略的,因此最简单的lambda函数如下:

int main()
{
	[]{}; //最简单的lambda表达式
	return 0;
}

一个完整的 lambda 表达式看起来像这样:

auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl; // 输出: 2

需要注意的是,初始化列表不能用于返回值的自动推导:

auto x1 = [](int i){ return i; };  // OK: return type is int
auto x2 = [](){ return { 1, 2 }; };  // error: 无法推导出返回值类型

这时我们需要显式给出具体的返回值类型。
另外,lambda 表达式在没有参数列表时,参数列表是可以省略的。因此像下面的写法都是正确的:

auto f1 = [](){ return 1; };
auto f2 = []{ return 1; };  // 省略空参数表

列表使用 lambda 表达式捕获

lambda 表达式还可以通过捕获列表捕获一定范围内的变量:

  • [] 不捕获任何变量。
  • [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  • [bar] 按值捕获 bar 变量,同时不捕获其他变量。
  • [&bar] 按值捕获外部作用域中所有变量,并按引用传递捕获bar变量。
  • [=] 表示值传递方式捕获所有父作用域中的变量包括this。(按值捕获)。
  • [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。

说明:

  1. 父作用域指的是包含lambda函数的语句块。
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如[=, &a, &b]。
  3. 捕捉列表不允许变量重复传递,否则会导致编译错误。比如[=, a]重复传递了变量a。
  4. 在块作用域以外的lambda函数捕捉列表必须为空,即全局lambda函数的捕捉列表必须为空。
  5. 在块作用域中的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  6. lambda表达式之间不能相互赋值,即使看起来类型相同。

lambda实现swap函数 

//lambda实现swap
int main()
{
	int a=5, b=7;
	cout <<"a: " << a <<" " << "b: " << b << endl;
	auto swap = [](int& a, int& b)->void
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	swap(a, b); //别忘了分号
	cout << "a: " << a << " " << "b: " << b << endl;
	
	return 0;
}
  • lambda表达式是一个匿名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量,此时这个变量就可以像普通函数一样使用。
  • lambda表达式的函数体在格式上并不是必须写成一行,如果函数体太长可以进行换行,但换行后不要忘了函数体最后还有一个分号

其实这个代码还可以进行优化,我们还可以利用捕捉列表进行捕捉 :

int main()
{
	int a=5, b=7;
	cout <<"a: " << a <<" " << "b: " << b << endl;
	//auto swap = [](int& a, int& b)->void
	//{
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//};  //不要忘了后面的分号
	//swap(a, b);

	auto swap = [&a, &b]
	{
		int tmp = a;
			a = b;
			b = tmp;
	};
	swap();  //()里面不需要传参
	cout << "a: " << a << " " << "b: " << b << endl;
	return 0;
}

 还有的交换函数会这样写:

auto swap = [a, b]()mutable
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	swap();  //()里面不需要传参
	cout << "a: " << a << " " << "b: " << b << endl;

注意:mutable的作用是取消lambda表达式的常量性,因为lambda总是一个const函数,光取消const属性还是不足以进行交换,这里还需要用到引用传递来捕获变量进行数据交换。

lambda表达式之间不能相互赋值 

注意任何的lambda表达式之间的不能进行相互赋值,即使是两者的类型相同。 

int main()
{
	int a = 5, b = 7;
	cout << "a: " << a << " " << "b: " << b << endl;
	auto swap1 = [](int& a, int& b)->void
	{
		int tmp = a;
		a = b;
		b = tmp;
	};  //不要忘了后面的分号

	auto swap2 = [](int& a, int& b)->void
	{
		int tmp = a;
		a = b;
		b = tmp;
	};  //不要忘了后面的分号
	
	cout << typeid(swap1).name() << endl;   // class <lambda_e7c7cc92a6dbdcb2c172418e9e71fa3d>
	cout << typeid(swap2).name() << endl;  //class <lambda_546794a8e16d3f9f8095bf1dc79cdca7>
	return 0;
}
  • lambda表达式之间不能相互赋值,就算是两个一模一样的lambda表达式。
  • 因为lambda表达式底层的处理方式和仿函数是一样的,在VS下,lambda表达式在底层会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>。
  • 类名中的uuid叫做通用唯一识别码(Universally Unique Identifier),简单来说,uuid就是通过算法生成一串字符串,保证在当前程序当中每次生成的uuid都不会重复。
  • lambda表达式底层的类名包含uuid,这样就能保证每个lambda表达式底层类名都是唯一的。
  • 因此每个lambda表达式的类型都是不同的,这也就是lambda表达式之间不能相互赋值的原因,我们可以通过typeid(变量名).name()的方式来获取lambda表达式的类型。

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

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

相关文章

开放路径最短优先协议OSPF(计算机网络)

​ 目录 开放最短路径优先(Open Shortest Path First) 链路状态算法 链路状态算法 链路状态数据库(link-state database) OSPF的分组类型 类型1&#xff1a;问候(Hello)分组 类型2&#xff1a;数据库描述(Database Description)分组 类型3&#xff1a;链路状态请求(Li…

Adobe 2023全家桶12月版本更新

Adobe 2023全家桶12月版本更新 Adobe 2023 发布有两个多月了&#xff0c;您们用上了新版本吗&#xff1f;12月又迎来了一次小版本更新&#xff0c;主要更新还是对已知问题的修复&#xff0c;当然也少不了一些新功能更新。 最新的Adobe2023全家桶&#xff0c;有更强大的内容&am…

SAP ABAP——SAP简介(二)【SAP主要产品时间线】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

知识图谱库汇总!——教育领域能够直接应用的知识图谱

教育领域开源的知识图谱实体 在教育领域,有许多开源的知识图谱实体可供使用。下面列出了一些例子: DBpedia:这是一个知识图谱,由 Wikipedia 的内容构建而成。DBpedia 中包含了许多关于人、地方、事物和概念的实体,并且这些实体都具有相关的属性和关系。 Wikidata:这是一个…

智牛股_第9章_CEPH_Swift+文件上传与下载

智牛股_第9章_CEPH_Swift文件上传与下载 文章目录智牛股_第9章_CEPH_Swift文件上传与下载学习目标第1章 CEPH Swift Api实践1. 目标2. 步骤3. 实现3.1 Ceph Swift Api 实践说明3.2 Ceph Swift Api 特点3.3 Ceph RGW 介绍3.4 Ceph 存储结构3.5 Ceph Swift Api 服务端的配置3.6 C…

计算机网络~物理层

一、物理层基本概念 1. 物理层接口特性 物理层解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体物理层主要任务&#xff1a;确定与传输媒体接口有关的一些特性(定义标准) 机械特性&#xff1a;定义屋里连接的特性&#xff0c;规定物理…

多播网络(Multicast)应用权限

本文介绍如何在苹果开发者官网申请 多播网络&#xff08;Multicast&#xff09;应用权限&#xff0c;从而正常使用 Wi-Fi 快连配网功能。本文适用于 iOS 版本的 OEM App 或者其他有相同需求的 App。 背景信息 Wi-Fi 快连配网又称 快连模式&#xff08;Easy-Connect&#xff0…

docker https 证书/多域名通配符自动续期(群晖https证书)

本文基于 freessl.cn 申请通配符域名自动续期。 使用docker的原因是为了方便可靠&#xff0c;不会因为不同的操作系统缺包无法安装 acme.sh&#xff0c;也不会在操作系统中留下灿烂内容&#xff0c;acme 版的docker 包含了运行环境。 主要步骤如下&#xff1a; 1、打开 http…

圣诞节学算法---线段树

线段树 快到圣诞节了&#xff0c;圣诞树是不是很漂亮&#xff1f;今天我们就来学习一下它的近亲的线段树 (话说这两玩意好像除了读音相似没啥关系) 引入 例题 1 给定一个数组 aaa 求数组中下标为l−rl - rl−r元素的和 看到这题大家都很容易想到用前缀和以O(n)O(n)O(n)预处…

3.2 多级放大电路的动态分析

一个 NNN 级放大电路的交流等效电路可用图3.2.1所示方框图表示。由图可知&#xff0c;放大电路中前级的输出电压就是后级的输入电压&#xff0c;即 U˙o1U˙i2\dot U_{o1}\dot U_{i2}U˙o1​U˙i2​、U˙o2U˙i3\dot U_{o2}\dot U_{i3}U˙o2​U˙i3​、⋯\cdots⋯、U˙o(N−1)U…

react笔记_07 hooks

什么是hook? 以前我们称函数组件为简单组件&#xff0c;因为函数组件是无状态的(没有state)。 而在React 16.8版本增加了 Hook&#xff0c;它可以让你在不编写 class 组件的情况下&#xff0c;也就是我们可以在函数组件中使用 state 以及其他的 React 特性。 Hook 不能在 c…

5G无线技术基础自学系列 | 5G服务完整性KPI

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 5G服务完整性KPI用来评估5G RAN中终端用…

java: 无效的目标发行版: 17 新建springBoot项目

问题 java: 无效的目标发行版: 17 详细问题 新建springBoot项目&#xff0c;对数据库配置后启动项目&#xff0c;控制台报错 java: 无效的目标发行版: 17 如下图 解决方案 查看JDK版本 &#xff08;事实上&#xff0c; 该步骤查看本机的已配置环境变量的JDK版本&#xff…

Python基础(十)模块与包

目录 1. 简介 1.1 模块 1.2 包 2. 使用 2.1 创建 2.2 引用 1. 简介 1.1 模块 Python 中一个以 .py 结尾的文件就是一个模块&#xff0c;模块中定义了变量、函数等来实现一些类似的功能。Python 有很多自带的模块&#xff08;标准库&#xff09;和第三方模块&#xff0c…

UMC产品UI升级说明

随着产品功能的逐渐完善&#xff0c;一款好的产品需要不断地打磨才能变得更完整、更稳定。所以&#xff0c;UMC作为数通畅联的核心产品&#xff0c;为了满足更多的需求&#xff0c;更好的视觉效果和体验感&#xff0c;一直都在不断地完善迭代。 本次升级主要是针对整体页面进行…

实现股票交易c接口​​​​​​​需要的注意事项有哪些?

实现股票交易c接口需要的注意事项有哪些&#xff1f;最近有很多朋友问小编这个问题&#xff0c;小编今天就说说&#xff01; 在基类列表中包含接口名称 为每一个接口的成员提供实现 如果类从基类继承并实现了接口&#xff0c;基类列表中的基类名称必须放在所有接口之前。(一个…

PMAC的PVT功能实现解析笔记

从上图中我们可以得到如下信息&#xff1a; 速度截面是一个抛物线 P0P_0P0​、V0V_0V0​是上一次指定的&#xff0c;P1P_1P1​、V1V_1V1​是当前期望的&#xff0c;TA是当前期望的运动时间 A0A_0A0​是上一次计算的&#xff0c;A1A_1A1​是当前计算的&#xff0c;加加速度dA/…

使用 x-sheet 构建在线疫情高峰预测数据表

背景 最近&#xff0c;一位大数据专家通过百度“发烧”的搜索指数、公开的疫情感染人数等指标&#xff0c;计算出每个城市的“超额发烧搜索指数累计面积”&#xff0c;并且通过城市的搜索指数累计增长、累计速度&#xff0c;就可以算出现在每一个有疫情的城市疫情大概的达峰时…

MyBatis-Plus保姆级快速上手教程

为简化开发而生 Mybatis简化JDBC操作 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 1、特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对…

都28了,半路转行学编程还来得及吗?

很多新来的粉丝&#xff0c;经常会问到&#xff1a;“我现在xx岁了&#xff0c;学编程晚吗?"&#xff0c;“程序员是不是吃青春饭啊&#xff0c;我都没有青春了&#xff0c;还能找到工作吗&#xff1f;”... 其实这类的问题&#xff0c;我以前都已经整理过文章&#xff0…