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

深入理解C++ Lambda表达式:语法、用法与原理及其包装器的使用

深入理解C++ Lambda表达式:语法、用法与原理及其包装器的使用

  • lambda表达式
    • C++98中的一个例子
    • lambda表达式语法
      • lambda表达式各部分说明
      • 捕获列表说明
    • 函数对象与lambda表达式
  • 包装器
    • function包装器
  • bind

🌏个人博客主页: 个人主页

在这里插入图片描述

本文深入介绍了C++中的Lambda表达式的语法、用法、捕获列表以及其底层原理,强调了其作为函数对象的功能。还探讨了标准库中std::function包装器和std::bind函数的应用,通过它们可以灵活地封装、绑定和简化可调用对象的处理,提升代码的简洁性和可读性。

lambda表达式

C++98中的一个例子

在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 _ecaluate;//评价//...Goods(const char* str,double price,int evaulate):_name(str),_price(price),_ecaluate(evaulate){}
};	

如果我们用运算符重载的方法控制比较逻辑,只能控制一种比较方法,但是如果我们希望希望按照价格,或者评价,名字来排序就没办法实现,所以我们可以通过仿函数来完成。

例如我们如果希望按照价格比较,就提供对应的仿函数:

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(), ComparePriceGreater());sort(v.begin(), v.end(), ComparePriceLess());return 0;
}

但是这里还是有的不方便,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

lambda表达式语法

lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明

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

注意:

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

如果我们要用lamba表达式写一个两个变量相加的函数,可以这么写。

[](int x, int y)->int {return x + y; };

这个表达式就是一个lamba对象,可以像函数一样直接调用,但是对这个lamba表达式直接调用不太方便,我们通常都是赋值给一个lamba对象,这个对象的类型可以使用auto进行推导。

int main()
{auto add1 = [](int x, int y)->int {return x + y; };cout << add1(1, 2) << endl;
}

auto add1 = [ ](int x, int y) -> int { return x + y; }; 这行代码会先生成一个 lambda 对象,并将其赋值给 add1

int main()
{auto add1 = [](int x, int y)->int {return x + y; };cout << add1(1, 2) << endl;auto func1 = []{//返回值可自动推导类型,所以可以省略//无参数可以省略cout << "hello world" << endl;return 0;};func1();return 0;
}

如果我们不想用仿函数,来完成比较逻辑,就可以这样写:

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)->bool{return g1._price < g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._price > g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool{return g1._evaluate > g2._evaluate;});return 0;
}

捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    • [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。
    • [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    • [=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
  4. 在块作用域以外的lambda函数捕捉列表必须为空。
  5. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错,但是可以使用全局域的。
  6. lambda表达式之间不能相互赋值,即使看起来类型相同,因为编译器生成的类名不同。
int x = 0;int main()
{// 只能用当前lambda局部域和捕捉的对象和全局对象int a = 0, b = 1, c = 2, d = 3;// 所有值传值捕捉auto func1 = [=]{int ret = a + b + c + d + x;return ret;};// 所有值传引用捕捉auto func2 = [&]{a++;b++;c++;d++;int ret = a + b + c + d;return ret;};// 混合捕捉auto func3 = [&a, b]{a++;// b++;int ret = a + b;return ret;};// 混合捕捉// 所有值以引用方式捕捉,d用传值捕捉auto func4 = [&, d]{a++;b++;c++;//d++;int ret = a + b + c + d;};// 混合捕捉// 所有值以传值方式捕捉,d用传引用捕捉auto func5 = [=, &d](){//a++;//b++;//c++;d++;int ret = a + b + c + d;};return 0;
}

因为捕获列表只能捕捉父作用域的对象,因为,成员函数默认传的是this指针,所在,这里应该捕捉的应该this指针。

class AA
{
public:void func(){//auto f1 = [this]auto f1 = [=]{cout << _a1 << endl;cout << _a2 << endl;};}
private:int _a1 = 1;int _a2 = 2;
};

函数对象与lambda表达式

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

int main()
{double rate = 0.03;Rate r1(rate);cout << r1(10000, 2) << endl;//600auto r2 = [rate](double money, int year)->double{return rate * money * year;};cout << r2(10000, 2) << endl;//600
}

从使用方式上来看,函数对象与lambda表达式完全一样。

函数对象将rate作为其成员变量,在定义对象时给出初始值即可,然后再调用重载的operator()。

在C++中,lambda表达式通过捕获列表捕获外部变量,并将这些变量作为成员变量存储在lambda对象中。lambda对象的调用本质上是调用其 operator() 成员函数。

捕获列表和成员变量

  1. 捕获列表:
    • 捕获列表用于指定 lambda 表达式可以访问哪些外部变量。
    • 捕获列表中的变量会被存储为 lambda 对象的成员变量。
  2. 成员变量:
    • 捕获的变量在 lambda 对象内部被存储为成员变量。
    • 捕获列表中的变量可以通过值捕获(=)或引用捕获(&)。

构造函数的初始化参数

  • 构造函数:
    • lambda 对象的构造函数会使用捕获列表中的变量来初始化成员变量。

调用 operator() 成员函数

  • 调用:
    • 当你调用 lambda 对象时,实际上是调用了其 operator() 成员函数。
    • operator() 成员函数包含了 lambda 表达式的主体代码。

在这里插入图片描述

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,这个类的名字是lamba + uuid 并且会在在该类中重载operator()。

包装器

function包装器

template <class T> function;     // undefined
template <class Ret, class... Args> class function<Ret(Args...)>;
//模板参数说明:
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参

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

包装器包装的是可调用对象,可调用对象一般有三类:函数指针(类型不好写),仿函数(需要单独写一个类),lamba(没办法写类型)。

//普通函数
int f(int a, int b)
{return a + b;
}
//仿函数
struct Functor
{
public:int operator()(int a, int b){return a + b;}
};
int main()
{//function包装可调用对象,可以统一类型function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };return 0;
}

通过一个命令就对应一个动作,也就是说一个字符串对应一个函数(可调用对象),这里我们会通常使用map实现,但是map的第二个参数可能是函数指针,仿函数对象,lamba对象。

如果第二个参数个函数指针的话虽然可以实现但是函数指针类型写起来太麻烦,如果用仿函数的类型实现的话就只能固定死一种类型,lamba表达式没有类型,所以这里就可以使用function来实现,可接受各种类型。

int main()
{function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };map < string, function<int(int, int)>> opFuncMap;opFuncMap["函数指针"] = f1;opFuncMap["仿函数"] = f2;opFuncMap["lamba"] = f3;cout << opFuncMap["函数指针"](2, 3) << endl; //5cout << opFuncMap["仿函数"](2, 3) << endl;	// 5cout << opFuncMap["lamba"](2, 3) << endl;  //5return 0;
}

function包装器简化代码,求解逆波兰表达式的步骤如下:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;  // 创建一个栈用于存储中间结果// 定义一个映射,将字符串操作符映射到对应的 lambda 表达式map<string, function<int(int, int)>> op = {{"+", [](int a, int b) { return a + b; }},{"-", [](int a, int b) { return a - b; }},{"*", [](int a, int b) { return a * b; }},{"/", [](int a, int b) { return a / b; }}};// 遍历输入的逆波兰表达式for (const auto& str : tokens) {// 如果当前字符串是操作符if (op.count(str)) {// 从栈中弹出两个操作数int right = st.top();st.pop();int left = st.top();st.pop();// 应用对应的操作符,并将结果压入栈中st.push(op[str](left, right));} else {// 如果当前字符串是数字,将其转换为整数并压入栈中st.push(stoi(str));}}// 栈顶元素即为最终结果return st.top();}
};

function还可以包装成员函数。

class Plus
{
public://静态成员函数static int plusi(int a, int b){return a + b;}//普通成员函数double plusd(double a, double b){return a + b;}
};int main()
{//包装静态成员函数function<int(int, int)> f1 = Plus::plusi;//包装普通成员函数Plus p;function<double(Plus* ,double, double)> f2 = &Plus::plusd;cout << f2(&p, 1.1, 2.0) << endl;function<double(Plus, double, double)> f3 = &Plus::plusd;cout << f3(p, 1.1, 2.0) << endl;return 0;
}

注意:

  1. 规定类的成员函数名取地址才是成员函数的地址。
  2. 成员函数的第一个默认的参数是this指针,这里为什么这里的第一个参数用对象也可以呢?

其实包装器包装对象的本质也是一个仿函数,实际要包装的对象作为自己的成员变量这样的方式存起来,然后调用operator(),operator()函数里面再去要包装的可调用对象,所以,这里的第一个参数只是为了调用operator(),所以这里使用指针,对象都可以调用成员函数。

bind

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

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

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

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

int Sub(int a ,int b)
{return a - b;
}int SubX(int a, int b,int c)
{return a - b - c;
}using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int main()
{//调整参数顺序auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;//绑定本质返回是防函数//调整参数顺序//_1代表第一个实参//_2代表第二个实参auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;//调整参数个数auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub3(5) << endl;auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 10);auto sub6 = bind(SubX, _1,100, _2);cout << sub6(5, 10);auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 10);}

function在包装成员函数的时候第一个参数显示传比较麻烦这里就可以使用bind进行绑定。

//bind一般用于绑定一些固定参数
function<double(double, double)> f3 = bind(&Plus::plusd, Plus(), _1, _2);
int main()
{auto func = [](double rate, double money, int year)->double{int ref = 0;for (size_t i = 0; i < year; i++){ref += money * rate;money += money * rate;}return ref;};function<double(double)> func_3_1_5 = bind(func, 0.015, _1, 3);function<double(double)> func_5_1_5 = bind(func, 0.015, _1, 5);function<double(double)> func_30_1_5 = bind(func, 0.015, _1, 30);cout << func_3_1_5(100000) << endl;cout << func_3_1_5(100000) << endl;cout << func_3_1_5(100000) << endl;return 0;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

相关文章:

深入理解C++ Lambda表达式:语法、用法与原理及其包装器的使用

深入理解C Lambda表达式&#xff1a;语法、用法与原理及其包装器的使用 lambda表达式C98中的一个例子lambda表达式语法lambda表达式各部分说明捕获列表说明 函数对象与lambda表达式 包装器function包装器 bind &#x1f30f;个人博客主页&#xff1a; 个人主页 本文深入介绍了…...

C# 编程语言:跨时代的革命

C# 是一种由微软开发的现代、类型安全、面向对象的编程语言&#xff0c;自2000年推出以来&#xff0c;它已经成为.NET平台的核心组成部分。在本文中&#xff0c;我们将探讨C#语言的特点、优势以及它在软件开发领域中的应用。 C# 语言特点 类型安全和自动垃圾回收 C# 是一种类…...

恋爱脑学Rust之Box与RC的对比

在遥远的某个小镇&#xff0c;住着一对年轻的恋人&#xff1a;阿丽和小明。他们的爱情故事就像 Rust 中的 Rc 和 Box 智能指针那样&#xff0c;有着各自不同的「所有权」和「共享」的理解。 故事背景 阿丽和小明准备共同养一株非常珍贵的花&#xff08;我们称之为“心之花”&…...

Rust 力扣 - 1423. 可获得的最大点数

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 题目所求结果存在下述等式 可获得的最大点数 所有卡牌的点数之和 - 长度为&#xff08;卡牌数量 - k&#xff09;的窗口的点数之和的最小值 我们遍历长度为&#xff08;卡牌数量 - k&#xff09;的窗口&#…...

Android15音频进阶之Cuttlefish搭建音频开发环境(九十二)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…...

发现不为人知的AI宝藏:发现AI新天地! —— 《第八期》

在人工智能&#xff08;AI&#xff09;领域&#xff0c;尽管ChatGPT、Midjourney等知名产品广为人知&#xff0c;但还有许多小众而有趣的AI工具等待你的探索。本文将推荐五款实用的AI工具&#xff0c;它们不仅功能强大&#xff0c;而且使用简单&#xff0c;帮助你在各种场景中提…...

基于物联网设计的地下煤矿安全监测与预警

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成 1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发 1.5 模块的技术详情介绍【1】NBIOT-BC26模块【2】MQ5传感器【4】DHT11传感器【5】红外热释电人体检…...

Java 23 的12 个新特性!!

Java 23 来啦&#xff01;和 Java 22 一样&#xff0c;这也是一个非 LTS&#xff08;长期支持&#xff09;版本&#xff0c;Oracle 仅提供六个月的支持。下一个长期支持版是 Java 25&#xff0c;预计明年 9 月份发布。 Java 23 一共有 12 个新特性&#xff01; 有同学表示&…...

.NET 8 中 Entity Framework Core 的使用

本文代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89935738 概述 Entity Framework Core (EF Core) 已成为 .NET 开发中数据访问的基石工具&#xff0c;为开发人员提供了强大而多功能的解决方案。随着 .NET 8 和 C# 10 中引入的改进&#xff0c;开发人…...

ai数字人分身123口播克隆数字人小程序源码_博纳软云

功能配置 一、用户 用户管理小黑屋用户反馈登录设置短信参数 二、作品 视频作品背景音乐库背景音乐分类 三、形象分身 上传记录视频要求参数配置 四、声音克隆 克隆记录参数配置声音要求文案示例 五、AI文案 生成记录创作模型模型分类Al配置 六、充值 充值订单积分套…...

从0开始学PHP面向对象内容之(类,对象,构造/析构函数)

上期我们讲了面向对象的一些基本信息&#xff0c;这期让我们详细的了解一下 一、面向对象—类 1、PHP类的定义语法&#xff1a; <?php class className {var $var1;var $var2 "constant string";function classfunc ($arg1, $arg2) {[..]}[..] } ?>2、解…...

openGauss数据库-头歌实验1-5 修改数据库

一、查看表结构与修改表名 &#xff08;一&#xff09;任务描述 本关任务&#xff1a;修改表名&#xff0c;并能顺利查询到修改后表的结构。 &#xff08;二&#xff09;相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 1.如何查看表的结构&#xff1b; 2.如…...

《JVM第3课》运行时数据区

无痛快速学习入门JVM&#xff0c;欢迎订阅本免费专栏 运行时数据区结构图如下&#xff1a; 可分为 5 个区域&#xff0c;分别是方法区、堆区、虚拟机栈、本地方法栈、程序计数器。这里大概介绍一下各个模块的作用&#xff0c;会在后面的文章展开讲。 类加载子系统会把类信息…...

阅读笔记 Contemporary strategy analysis Chapter 14

来源&#xff1a;Robert M. Grant - Contemporary strategy analysis (2018) Chapter 14 External Growth Strategies: Mergers, Acquisitions, and Alliances 合并、收购和联盟 Ⅰ Introduction and Objectives 企业并购与联盟是公司实现快速扩张的重要战略工具。通过这些手段…...

2024网鼎杯青龙组wp:Crypto2

题目 附件内容如下 # coding: utf-8 #!/usr/bin/env python2import gmpy2 import random import binascii from hashlib import sha256 from sympy import nextprime from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import long…...

能通过Ping命令访问CentOS 9 Stream,但在使用Xshell连接

能通过Ping命令访问CentOS 9 Stream&#xff0c;但在使用Xshell进行SSH连接失败 1. **确认SSH服务状态**&#xff1a;2. **检查SSH配置**&#xff1a;要检查和设置PermitRootLogin选项&#xff0c;您需要编辑SSH配置文件/etc/ssh/sshd_config。以下是具体步骤&#xff1a;1. 打…...

Oracle 第19章:高级查询技术

在Oracle数据库中&#xff0c;高级查询技术是数据库管理员和开发人员必须掌握的重要技能。这些技术能够帮助优化查询性能&#xff0c;简化复杂的查询逻辑&#xff0c;并提高数据处理的效率。本章将重点讨论两个关键概念&#xff1a;子查询和连接与并集操作。 子查询 定义: 子…...

Excel:vba运行时错误“7“:内存溢出错误

我这里出现这个错误是在批注中插入图片时报错 原因:我插入的图片不都是jpg的类型的&#xff0c;但是其中的两张图片是webp类型的&#xff0c;但是我把文件后缀名修改成了jpg&#xff0c;以为变成了jpg类型的图片&#xff0c;但是图片在批注里面无法显示&#xff0c;所以运行到第…...

【MyBatis源码】BoundSql分析

基础 BoundSql是对SQL语句及参数信息的封装&#xff0c;它是SqlSource解析后的结果。Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的&#xff0c;而是与BoundSql交互。BoundSql是对Executor组件执行SQL信息的封装&#xff0c;具体实现代码如下&#xff1a; …...

KTHREAD--InitialStack和KernelStack和TSS的esp0

InitialStack和TSS.esp0的关系,在这里可以看到 mov ecx, [esi_KTHREAD.InitialStack] ; esi: newthread lea eax, [ecx-210h] ; 越过FPXSAVE指令存储地址 test byte ptr [eax-1Ah], 2 ; 判断efalgs寄存器的VIF位是否为1 jnz short loc_458743 sub eax, 10h…...

Skia基础运用(Ubuntu环境下使用BUILD.gn)

1.拉取代码 git clone https://skia.googlesource.com/skia python tools/git-sync-deps // 这一步可能会出现部分错误&#xff0c;再次执行直到成功 // 这里面拉取完三方库之后会拉取node&#xff0c;linux等压缩包&#xff0c;从google下载上面执行完&#xff0c;代码就完全…...

Vue中props和data的优先级哪个更高?

前言 Vue组件之间的数据传递是一个非常重要的环节。而在组件内部&#xff0c;我们经常会用到props和data来管理和传递数据。那么&#xff0c;问题来了&#xff1a;当props和data有冲突时&#xff0c;哪个优先级更高呢&#xff1f; 为了更好地理解这个问题&#xff0c;我们先来…...

springboot2.x使用SSE方式代理或者转发其他流式接口

文章目录 1.需求描述2.代码2.1.示例controller2.2.示例service2.3.示例impl 3.测试 1.需求描述 使用SSE的方式主要还是要跟前端建立一个EventSource的链接&#xff0c;有了这个连接&#xff0c;然后往通道里写入数据流&#xff0c;前端自然会拿到流式数据&#xff0c;写啥拿啥…...

consul入门教程

一、介绍Consul Consul是由HashiCorp开发的一种服务发现和配置管理工具&#xff0c;它可以提供分布式系统所需的多个关键功能&#xff0c;如服务发现、配置管理、键值存储等。Consul可以帮助开发人员轻松构建分布式系统&#xff0c;提高系统的可靠性和可扩展性。 二、Consul实…...

软考:大数据架构设计

大数据总结 大数据处理系统的特征 1、鲁棒性和容错性 2、低延迟读取和更新能力 3、横向扩容 4、通用性 5、延展性 6、即席查询能力 7、最少维护能力 8、可调试性 Lambda架构 批处理层 存储数据集和生成Batch View 管理主数据集&#xff0c;原始的&#xff0c;不可变的&…...

token无感刷新+处理并发的后端方案

问题描述&#xff1a; 当用户通过登陆后进入一个web网站&#xff0c;会把token保存到localStorage。假设token过期时间30min。 那么当用户在网站快乐地玩耍了30min后&#xff0c;这时进行了一次提交表单&#xff0c;它会被重定向到登陆页面。 作为用户&#xff1a;我表单填了…...

【系统设计】让 Java “动起来”:动态语言与静态语言的比较及 DSL 实现

在编程语言的世界里&#xff0c;语言的特性决定了它们在不同场景下的适用性。动态语言和静态语言是两种常见的编程范式&#xff0c;它们的差异不仅影响开发者的使用习惯&#xff0c;还决定了它们在某些应用场景中的表现。在这篇博文中&#xff0c;我们将通过Python和Java这两种…...

TCP Analysis Flags 之 TCP Keep-Alive

前言 默认情况下&#xff0c;Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态&#xff0c;并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时&#xff0c;会对每个 TCP 数据包进行一次分析&#xff0c;数据包按照它们在数据包列表中出现的顺序进行处理。可…...

mfc140u.dll丢失怎么办? mfc140u.dll文件缺失的修复技巧

mfc140u.dll 是 Microsoft Foundation Classes (MFC) 库的一部分&#xff0c;它是 Visual Studio 2015 的组件之一&#xff0c;主要服务于使用 C 编写的 Windows 应用程序。这个动态链接库文件包含了 MFC 14.0 Unicode 版本的实现代码&#xff0c;为应用程序提供运行时支持。当…...

Spring Security使用

文章目录 Spring Security的起点FilterChain重写重写登录验证逻辑增加CSRF Token增加方法权限校验 Spring Security的起点 在AbstractApplicationContext.refresh()方法时&#xff0c;子类ServletWebServerApplicationContext会创建一个ServletContextInitializerBeans这个Bea…...