CppCon 2017 学习:folly::Function A Non-copyable Alternative to std::function

你说的内容是关于 C++ 中 可调用类型(Callable Types) 的基础知识,我帮你理清并补充理解。

Callable Types(可调用类型)简介

C++ 中任何可以用 () 括号操作符“调用”的对象,都叫做 可调用类型。典型包括:

1. 函数指针和函数引用

int (*functionPointer)(std::string);   // 指向函数的指针
int (&functionReference)(std::string); // 函数的引用

它们都指向一个可被调用的函数,调用方式如:

int result = functionPointer("hello");
int result2 = functionReference("world");

2. Lambda 表达式

匿名函数对象,语法:

auto lambda = [](std::string s) -> int { return s.size(); };
int result = lambda("test");

lambda 实际上是编译器生成的匿名类对象,重载了 operator()

3. 函数对象(Functor)

你可以自定义一个类或结构体,实现 operator(),使其实例可以像函数一样被调用:

class ComplexObject {
public:int operator()(std::string s) {return s.size() * 2;  // 举例}
};
ComplexObject obj;
int result = obj("hello");

4. 补充:static operator()

注意,你提到的:

class ComplexObject {static int operator()(std::string);
};

这个声明是不合法的。因为 operator() 必须是实例成员函数(non-static),它的意义就是让对象可以被调用。
如果你写 static,就不能通过对象调用了:

ComplexObject obj;
obj("hello");  // 不行,static operator() 无法用obj调用

你可以写普通静态成员函数,但那就不是函数调用运算符重载了。

小结

类型示例说明
函数指针int (*fp)(std::string);指向普通函数
函数引用int (&fr)(std::string);绑定到函数
Lambda 表达式auto lambda = [](std::string) { ... };匿名函数对象,重载了 operator()
函数对象(重载 operator()class Foo { int operator()(std::string); };自定义可调用类型

带状态的可调用对象(Stateful Callables),让我帮你详细解释和梳理下:

Stateful Callables(有状态的可调用对象)

“有状态”指的是这个可调用对象内部保存了数据(状态),每次调用可能用到这些状态或改变它们。

1. 捕获变量的 Lambda(Stateful Lambda)

int x = 5;
auto lambda = [x](std::string s) { return s.size() + x; };
  • 这里的 lambda 捕获了外部变量 x,把它存到 lambda 对象里。
  • 每次调用 lambda,都会用这个 x 参与计算。
  • 这个 lambda 对象内部有状态(x),所以是 stateful callable

2. 自定义类或结构体,重载非静态 operator()

class ComplexObject {int x;
public:ComplexObject(int x_) : x(x_) {}int operator()(std::string s) const {return s.size() + x;}
};
  • 这里 ComplexObject 有成员变量 x(状态)
  • 重载了 operator(),通过对象调用时可以用到内部状态。
  • 也是一个 stateful callable

3. 为什么 operator() 要是非静态?

  • operator() 是调用对象的核心,非静态才能访问对象的成员变量(状态)。
  • 静态 operator() 没法访问对象成员,因此不算真正的“stateful callable”。

4. 总结

类型例子特点
无状态 Lambda[](int x){ return x*x; }没捕获外部变量,纯函数
有状态 Lambda[x](std::string s){ return s.size() + x; }捕获外部变量,内部有状态
函数对象(类/结构体)有成员变量的类重载非静态 operator()内部保存状态,调用时可用

这部分讲的是 会修改自身状态的可调用对象(State-Mutating Callables),我帮你详细说明:

State-Mutating Callables(会改变状态的可调用对象)

这类 callable 不仅“有状态”,而且在调用时会修改它们内部的状态

1. mutable lambda

默认情况下,捕获外部变量的 lambda 是 const 调用的,不能修改捕获的变量副本。

int x = 0;
auto lambda = [x](std::string s) mutable {x += s.size();  // 修改了 lambda 内部捕获的 x 副本return x;
};
  • mutable 关键字让 lambda 的 operator() 变成非 const,允许修改捕获的变量副本。
  • 注意,捕获的是值拷贝,修改的是 lambda 内部的那个副本,不影响外部 x

2. 非 const 的非静态 operator()

class ComplexObject {int x = 0;
public:int operator()(std::string s) {x += s.size();  // 修改对象的状态return x;}
};
  • 这里 operator() 不是 const,说明调用会改变对象状态。
  • 你可以记录调用次数、累计某些值等。

3. 区别于前面 const operator() 的地方

  • const operator() 不允许修改成员变量(除非用 mutable 修饰成员变量)
  • 非 const operator() 可以修改成员变量,实现状态变化

4. 总结表

类型示例说明
mutable lambda[x](std::string s) mutable { x += s.size(); return x; }允许修改捕获变量的副本
类的非 const operator()int operator()(std::string s) { x += s.size(); return x; }允许修改对象成员变量,状态改变

你提到的是传递可调用对象(callables)给函数的方式,特别是用 函数指针 传递的限制,我帮你详细解释:

传递可调用对象(Passing Callables)

1. 函数指针只能传递无状态的可调用对象

举例:

std::string work(int x);
void workAsynchronously(int x, void (*processResult)(std::string));
  • processResult 是一个 函数指针,指向一个 void(std::string) 的函数。
  • 这意味着传入的回调只能是无状态的普通函数,不能是有状态的 lambda 或函数对象。

2. 为什么函数指针只能指向无状态函数?

  • 函数指针实际指向具体的函数地址
  • 有状态 lambda 和函数对象是对象实例,它们保存状态,需要调用其成员函数(operator()),而不是普通函数地址。
  • 因此,函数指针无法表示带状态的可调用对象。

3. 如果想传递有状态 callable,该怎么办?

  • std::function(类型擦除),支持任何可调用对象,包括有状态的 lambda 和函数对象:
#include <functional>
void workAsynchronously(int x, std::function<void(std::string)> processResult);
  • 这样,传入的 processResult 可以是:
auto lambda = [capturedData](std::string s) { /*...*/ };
workAsynchronously(5, lambda);

4. 总结

传递方式支持的 callable 类型备注
函数指针(void(*)(T)只能无状态的普通函数不能传递捕获变量的 lambda
std::function<T>支持所有 callable,包括带状态的 lambda 和函数对象灵活但有一定运行时开销

std::function 其实是个 函数包装器(Function Wrapper),用来封装各种可调用对象,提供统一调用接口。下面帮你拆解理解:

std::function 简化结构解析

template<typename R, typename... Args>
class function<R(Args...)> {void* state;                        // 存储可调用对象的状态(捕获的数据)void (*func)(void*, Args...);      // 指向“调用”函数的指针,传入state和参数void (*destroy)(void*);            // 指向销毁函数,用于清理state指针内存~function();                       // 析构时调用destroy清理stateR operator()(Args... args);         // 调用包装的函数对象template<typename F>function(F&& f);                   // 构造时,把任意可调用对象包装进来template<typename F>function& operator=(F&& f);        // 赋值操作符,重新包装
};

关键点

  • void state*
    这里保存了任意类型的可调用对象的状态,比如捕获的变量、类实例等等。
    void* 实现类型擦除,隐藏了具体类型。
  • func 指针
    指向一个统一的调用接口,实现了调用时把 state 传进去,再调用对应可调用对象的 operator()
  • destroy 指针
    用于析构 state 指向的对象,防止内存泄漏。
  • 构造函数模板
    允许用任意类型的可调用对象(函数指针、lambda、函数对象)来初始化 std::function
  • 调用运算符
    std::function 实例看起来像普通函数一样,可以直接用 () 调用。

为什么需要这么设计?

  • 统一接口:不管是普通函数、带状态的 lambda,还是自定义的函数对象,都能用同一种类型变量来保存并调用。
  • 类型擦除:隐藏不同可调用对象类型的差异,简化代码调用。
  • 灵活性和便利性:把多态调用变得简单。

你提到的是 std::function 的实现细节和其运行时开销,我来帮你详细解析这些点,帮助你更好理解:

std::function 的结构(以 libstdc++ 的实现为例)

一个 std::function<R(Args...)> 实例,大约占 48 字节(在 x86_64 上),这些字节大致分配如下:

组成部分说明
函数调用指针指向调用封装对象的函数:R (*invoke)(void*, Args...)
管理函数指针指向处理 copy、destroy、move 的函数:void (*manager)(...)
内部 buffer(32B)小对象优化区,用于直接放入小对象(例如小 lambda)
或者指针指向堆内对象如果封装对象较大,就会动态分配在堆上,buffer 存储的是指针地址

特性解析

小对象优化(SBO:Small Buffer Optimization)

  • 如果你的 lambda 或函数对象小于 32 字节,就会直接放入内部 buffer,避免堆分配,提高性能。
  • 如果太大,就会在堆上创建对象,然后内部 buffer 存放指针。

可复制(copyable)

  • std::function 是可复制的。
  • 拷贝会拷贝封装的对象(调用对应的 copy 管理函数)。
std::function<void()> f1 = [] { std::puts("hi"); };
std::function<void()> f2 = f1;  // 拷贝了 lambda 封装体

不一定 noexcept 可移动

  • 移动操作不是 noexcept,意味着在某些容器(如 std::vector)中移动 std::function 可能触发重新分配或异常传播。
  • 原因:封装的对象类型可能自带抛异常的移动构造函数。

示例:大小对比

#include <iostream>
#include <functional>
int main() {std::function<void()> small = [] { std::cout << "small\n"; };auto bigLambda = [buffer = std::array<int, 100>{}] { std::cout << "big\n"; };std::function<void()> big = bigLambda;std::cout << "sizeof(std::function): " << sizeof(std::function<void()>) << "\n";
}

输出(在 x86_64 上):

sizeof(std::function): 48

总结

特性说明
大小通常 48 字节(libstdc++)
SBO32 字节内的对象放栈上,否则放堆上
可复制会复制被封装对象
移动非 noexcept使用时注意异常安全性
功能封装任意可调用对象,延迟调用,多态调用

你说的这段是在说明 std::function典型使用场景,尤其是它的任务封装功能。我来帮你逐句解释:

典型用途解释

「passing a task to libraries for execution at a later time」

将某个任务(比如 lambda)传给一个库,让它**“以后”执行**,不是现在马上执行。

例子:传一个回调到异步网络库中。

void startAsync(std::function<void(std::string)> callback);
「or in a different thread」

把任务传到另一个线程去执行,比如线程池。

例子:

std::thread t([] { doWork(); });
「storing those tasks in the library implementations」

这些任务(lambda、函数等)通常会被**“保存”在库内部的数据结构里**,等时间合适再执行。

例子:任务队列

std::queue<std::function<void()>> taskQueue;
「in either case, those tasks are never executed more than once」

这些任务通常只执行一次,执行完就丢弃,没有重用的需求。

所以不需要支持多次调用,比如:

auto task = [] { std::puts("run once"); };
task(); // 
task(); //  通常不会有这个需求
「and there is never a need to copy them」

这些任务传进去之后,不会被复制。只需要“移动”进库中,然后库调用它就结束了。

总结成一句话:

这些“任务式”的可调用对象通常:

  • 只执行一次
  • 被移动而非复制
  • 用于延迟/异步执行
  • 常用于线程池、事件循环、任务调度器中

延伸建议:使用 std::function 还是 std::movestd::unique_function

如果任务只用一次、无需复制,std::function 有点重。C++23 起可以用 std::move_only_function(或者第三方的 unique_function),更轻更高效:

std::move_only_function<void()> task = std::move(lambda);

你这段是对 Facebook 的开源代码库(如 Folly)中,std::function典型使用场景的描述。我来帮你逐条解释:

内容逐句解析

folly::Executor* executor;

这是一种抽象接口指针,用来表示一个“任务执行器”。

executor->add(callable);

往这个执行器里“添加”一个任务(可调用对象)。callable 可以是 lambda、函数、绑定对象等。

folly::Executor is an interface (abstract base class) for passing tasks that need to be executed

folly::Executor 是一个接口类(抽象基类),它的职责是:接收并执行异步任务

相当于你设计了一个标准协议,任何执行器(线程池、事件循环等)都可以实现它。

implementations include a thread pool which executes tasks in parallel

这个接口的具体实现包括:

  • 线程池(并行执行)
  • 单线程事件循环(串行调度)
  • IO 线程(和 Reactor 模式结合)
std::function<void()> was used to pass tasks to the executor

std::function<void()> 是传给 executor->add() 的参数类型,表示:
“一个可以执行、不带参数、不返回值的任务”。

这种使用方式让 folly::Executor 成为一个高层、通用的任务调度器接口。

总结

Facebook/Folly 的实践中

元素作用
folly::Executor任务执行接口(类似抽象线程池)
executor->add(task)添加一个延迟执行的任务
std::function<void()>用来封装传入的任务

设计上的好处

  • 任务的来源不受限制(lambda、函数、类)
  • 执行方式可以灵活替换(线程池、主线程调度器)
  • 接口通用、便于解耦模块

这段讲的是 Facebook Folly 库中 Future 的常见使用场景,以及它与 std::function 的关系。我来帮你逐句解析 + 总结背后原理

示例代码含义

folly::Future<std::string> result = someRpcCall(1, 2, 3);

表示调用了一个异步 RPC 函数,它返回一个 Future<std::string>,未来会获得一个 std::string

result.then([&foo](std::string r) {return foo.extractNumber(r);
})

给这个 Future 添加一个 .then() 回调:

  • result 可用了,就调用 lambda。
  • lambda 从字符串里提取出数字(int)。
.then([obj = std::move(obj)](int x) {obj.setNumber(x);
});

接着链式 .then() 调用,把前一个结果 x(一个 int)交给另一个 lambda。

  • 这里用了 C++ 的“带 move capture 的 lambda
  • obj 是只在 lambda 中使用的一个局部状态对象

概念解释:Future + then 回调机制

Future<T>:代表未来会得到一个 T

这是一个异步结果占位符

.then(func)

当结果可用时,调用你传进去的 func(回调)

就像 JavaScript 的 .then(),但类型安全,支持 C++ 特性。

回调的存储方式:使用 std::function

你写的这句:

“the implementation used to use std::function to store the callback”

表示早期实现是这样写的:

std::function<void(T)> callback;

也就是用 std::function 来存储 then() 传入的 lambda。这有几个好处:

优点缺点
可以存储任意可调用对象比较重(拷贝 / 类型擦除)
简化了接口不支持 move-only 类型

后续优化:不再使用 std::function

因为 std::function 不支持:

  • move-only lambda(捕获 unique_ptr 或 std::move(obj))
  • noexcept move
  • 精确类型推导(性能)
    所以 Folly 后来换成了 手写的轻量 type-erased function wrapper,支持 move-only 语义。类似于:
template<typename T>
struct MoveOnlyCallback {virtual void operator()(T) = 0;virtual ~MoveOnlyCallback() = default;
};

总结

这段代码展示了:

内容意义
Future<T>管理异步结果
.then(callback)注册异步回调
回调传 lambda(可带状态)支持链式操作、异步数据流
早期用 std::function 储存回调简单但不支持 move-only,后来被优化掉

你这段是讲 C++ 中使用 std::function限制,尤其是它不支持捕获 move-only 类型的问题,以及 Facebook Folly 提供的一些 解决方法。下面是逐点讲解与深入理解:

问题:std::function 不支持 move-only 捕获

示例问题代码:

MoveOnlyType x;
executor.add([x = std::move(x)]() mutable { x.doStuff(); });

这在某些实现中无法编译,原因是:

std::function 只能封装可复制(copyable)对象。而这个 lambda 捕获了 MoveOnlyType,它不可复制。

常见 Workaround 1:用 std::shared_ptr

auto x = std::make_shared<MoveOnlyType>();
executor.add([x]() { x->doStuff(); });

优点:

  • lambda 可复制了,shared_ptr 也是可复制的

缺点:

  • 每次调用都涉及堆分配
  • 需要原子操作维护引用计数(性能差)
  • 会让你为避免 std::function 限制而牺牲所有权语义

Workaround 2:用 folly::MoveWrapper<T>

folly::MoveWrapper<MoveOnlyType> x;
executor.add([x]() mutable { x->doStuff(); });

优点:

  • MoveWrapper<T> 实际上是一个**“伪复制对象”**,在复制时会自动移动内部对象
  • 这让 lambda 看起来是可复制的,但实际上把资源从左值转成了右值传进来

缺点:

  • 违反了 C++ 的复制语义(拷贝其实是 move)
  • 很像已经弃用的 std::auto_ptr —— 危险、易错
  • 一不小心就可能在拷贝时丢失数据

本质问题总结:

项目问题
std::function不能 wrap move-only lambda
lambda 捕获 move-only会使 lambda 本身不可复制
std::function 要求 copyable所以编译报错
workaround(shared_ptr / MoveWrapper)都是权衡性能或语义的方案

Folly 的真正解决方案(后续)

Folly 的后续优化是引入了一个可以支持 move-only lambda 的轻量函数包装器(非 std::function),其特性包括:

  • 支持 unique_ptrPromise 等 move-only 类型
  • 支持 noexcept move
  • 避免拷贝构造限制
  • 可以零堆分配(small buffer optimization)

小结

方法优点缺点
std::function通用、简洁不支持 move-only 捕获
shared_ptr兼容 std::function堆分配、性能差、共享所有权
folly::MoveWrapper可变通使用破坏复制语义、易出错
自定义轻量函数包装器(如 Folly)真正解决 move-only 问题实现复杂,不是标准库

为什么需要一种不同的 Function Wrapper

std::function 的核心问题:

要求所有可调用对象(callables)是可拷贝的(copyable)

这对很多实际用例来说,是一种不必要的限制,尤其是:

我们并不需要拷贝这些可调用对象

  • 比如在线程池中提交任务时:
    • 只会执行一次(one-shot)
    • 不需要拷贝(只需要 move 进去,然后调用)
    • 可调用对象中经常有 unique_ptrPromise 这类 move-only 类型

结果是:

不能直接使用 lambda 捕获 move-only 对象

MoveOnlyType x;
executor.add([x = std::move(x)]() mutable { x.doStuff(); }); //  std::function 不接受
  • 捕获了 MoveOnlyType,lambda 本身就变成 move-only
  • std::function<void()> 要求构造函数参数是 copyable

所以你真正想要的是:

一个轻量级的 function wrapper,可以 wrap:

  • move-only 的 lambda
  • 只 move、不 copy
  • 小对象无需堆分配
  • 只执行一次(one-shot callable)也没问题

Facebook 的 Folly 库就因此创造了:

  • folly::Function:是 move-only 的 function wrapper
  • 用于线程池、异步任务、promise 等现代用例
  • 避免了不必要的性能开销

小结:

标准库 std::function实际开发常见需求
需要可复制 callable只需要 move、执行一次
可能导致堆分配想要 small buffer 或 zero allocation
不支持 move-only 捕获现代 C++ 任务常用 move-only 对象
如果你要构建一个支持 MoveOnly 的任务系统,那使用 std::function 是不合适的。你应该考虑:
  • 自己实现一个简易的 MoveFunction
  • 或者使用 Folly 的 folly::Function

关于 const 正确性(Const Correctness)在 std::function 中的一个重要细节

你可能会以为:

std::function<void()> f;
void someFunction(const std::function<void()>& f) {f(); // f 是 const,调用 f() 应该不会修改内部状态
}

表面上看,f()const 的成员函数,所以你以为

  • 包装的 lambda 或函数对象也应该是 const 调用
  • 内部状态不会被修改

实际上:

R operator()(Args...) const;

这个 operator()const 没错,但是

  • 它内部调用的是 (*callable)(args...),这个 callable 是 void* 转型来的
  • 没有检查实际 wrapped 对象的 constness!

所以问题来了:

std::function<void()> f = [x = 0]() mutable { /* 修改 x */ };
f(); //  可以正常调用,虽然是 const 对象

即使 fconst,只要里面包装的是 mutable lambda 或非 const operator() 的对象:

仍然可以修改状态!

总结就是:

看起来实际上
operator()const但 wrapped callable 可能是非-const 的
你以为不改状态实际上可以修改状态(比如 mutable lambda)

所以这段话的重点理解是:

std::function::operator()const,但它没有真正地保证 const-correctness。

你不能依赖它来保证你的代码不会修改内部状态。

你这段内容是讲解 folly::Function 的设计目的和内部实现机制,下面是逐点的理解与总结

folly::Function 的设计动机与特点

1. Non-copyable(不可拷贝)

  • 为什么?
    要支持捕获 unique_ptrPromisemove-only 的类型
  • std::function 的问题: 只支持可拷贝类型
  • folly::Function 的解决方案:
    自己就是不可拷贝的(copy constructor & copy assignment 被删除)
    folly::Function 使用 值语义(value semantics),但通过 move 实现转移所有权

2. noexcept-Movable

  • 如果你定义了一个类型含有 folly::Function 成员变量,
    STL 要求你这个类型如果想 noexcept move,那成员也必须是 noexcept move
  • 所以 folly::Function 本身实现了 noexcept move,以便:
    支持 std::move_if_noexcept
    与 STL 容器、线程池等类型协作良好

3. Const Correct

  • folly::Function 有两个版本:
    folly::Function<void()>         // 非 const operator()
    folly::Function<void() const>   // const operator()
    
  • 保证 const-correctness,解决 std::function 的历史问题
  • 如果你用的是:
    const folly::Function<void()>& f;
    f(); // 调用的是 const operator()
    
    那里面包的 callable 也必须支持 const 调用!

4. 实现细节(Implementation Details)

特性说明
大小64 bytes(x86_64 架构)
调用指针1 个指针:真正的调用函数
管理函数指针用于 move、destroy 等操作
小对象优化提供 48 字节的 inline 存储空间
大对象策略不满足 noexcept-movable 的对象将 heap 分配

哪些对象不能 inline 存储?

  • noexcept movable 的对象
    会退化为堆分配(使用指针 + heap 管理)

设计目标小结:

目标实现方式
支持 move-only 对象类型本身不可拷贝
高性能小对象优化,避免堆分配
STL 容器兼容性noexcept move
保证 const 正确性两个签名:void() vs void() const

这部分内容总结了 folly::Function 相比 std::function 的优势、迁移实践、适用场景以及性能表现。下面是对这些内容的逐条解释与理解

Trivia:互操作性

  • std::function 可以转换为 folly::Function(拷贝构造)
  • folly::Function 不能转换为 std::function,因为 std::function 需要拷贝构造,而 folly::Function 是不可拷贝的

迁移到 folly::Function

大多数情况下可作为 drop-in 替代品

但也有例外:

  1. 如果代码 依赖拷贝语义(很少见)
  2. 如果代码 依赖不正确的 const 使用(那是个 bug,folly::Function 会强制你修)

使用方式差异:

  • std::function 常常以 const& 传参
    folly::Function 不可拷贝,必须以 &&& 传参

Facebook 内部采用

  • folly::Future 中替换了 std::function
    带来最大受益:可使用 move-only 的回调
  • folly::Executor 中替换 std::function
    需要修改很多子类,但修改过程常揭示原有代码问题

何时使用 std::function vs folly::Function

使用场景推荐
需要复制 callable 的 APIstd::function
一般回调/异步任务/只需移动语义folly::Function
使用 MoveWrapper 等拷贝模拟方式不要这样做,使用 folly::Function 替代更安全清晰

Benchmark(性能)

调用方式时间(越小越好)每秒调用次数(越高越好)
函数指针调用 (fn_ptr)~1.3 ns~761M 次
std::function 调用~2.28 ns~437M 次
folly::Function 调用~1.96 ns~510M 次
std::function 创建 + 调用~3.04 ns~329M 次
folly::Function 创建 + 调用~2.79 ns~359M 次
folly::Function 通常快于 std::function

总结

内容
目标替代 std::function,支持 move-only 类型
不支持拷贝(故不能做 const& 参数)
迁移建议改成 &&& 传参
效果避免 ugly workarounds(如 MoveWrappershared_ptr 等)
性能std::function 持平或更快
应用在 Facebook 内部已广泛部署

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

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

相关文章

PyTorch 中Tensor常用数据结构(int, list, numpy array等)互相转换和实战示例

在 PyTorch 中&#xff0c;tensor 是一种强大且灵活的数据结构&#xff0c;可以与多种 Python 常用数据结构&#xff08;如 int, list, numpy array 等&#xff09;互相转换。下面是详细解释和代码示例&#xff1a; 1. Tensor ↔ int / float 转为 int / float&#xff08;前提…

计算机网络与数据通信基础

第一章 计算机网络概述 1. 计算机网络的核心概念 1.1 定义 将 地理分散 的、具有 独立处理能力 的计算机系统&#xff08;主机/Host&#xff09;&#xff0c;通过 传输介质 与 网络设备 互连&#xff0c;在 网络协议 和 软件 支持下实现 资源共享 与 数据通信 的系统。 关键术…

【统计术语】

文章目录 基础概念术语基期与现期增长量与增长率环比与同比 比重术语平均数术语特殊增长术语其他常用术语 基础概念术语 基期与现期 基期&#xff1a;作为基础参照的时期&#xff0c;一般指过去的时间 现期&#xff1a;与基期对比的时期&#xff0c;一般指现在的时间 示例&am…

XXE(XML外部实体注入)详解

目录 一、XXE漏洞简介 二、XML详解 (一) XML文档结构 1. 文档声明 2. XML文档类型定义&#xff08;DTD&#xff09; 3. XML文档元素 4. XML文档示例 三、XXE漏洞类型 四、XXE漏洞挖掘技巧 五、XXE漏洞危害 (一) 文件读取 (二) 内网探测 1. 端口探测 2. 主机存活探…

深入解析JVM字节码执行引擎

JVM 字节码执行引擎。它是 JVM 核心组件之一&#xff0c;负责实际执行加载到内存中的字节码指令。你可以将它想象成 JVM 的“CPU”。 核心职责&#xff1a; 加载待执行的字节码&#xff1a; 从方法区&#xff08;元空间&#xff09;获取已加载类的方法字节码。创建和管理栈帧…

华为OD机试-MELON的难题-DFS(JAVA 2025A卷)

题意是从N快雨花石中找出最少拿出雨花石的块数&#xff0c;使得雨花石可以均分&#xff0c;直接使用dfs解决此类组合问题 package com.example.demo.bean;import java.util.Arrays; import java.util.LinkedList; import java.util.Scanner;public class YuHuaStone {public s…

鸿蒙数据库操作

一、使用关系型数据库实现数据持久化&#xff0c;需要获取一个RdbStore&#xff0c;其中包括建库、建表、升降级等操作。 const STORE_CONFIG: relationalStore.StoreConfig {name: AnyOffice.db, // 数据库文件名securityLevel: relationalStore.SecurityLevel.S1, // 数据库…

基于ARM SoC的半导体测试

ARM SoC&#xff08;System on Chip&#xff09; 是一种集成了多个关键计算组件的单片系统芯片&#xff0c;广泛应用于移动设备、嵌入式系统、物联网&#xff08;IoT&#xff09;和半导体测试设备等领域。它的核心设计理念是“高度集成”&#xff0c;将处理器、内存、外设接口等…

JavaEE->多线程2

目录 一、线程安全&#xff08;重点&#xff09; 1.线程安全演示 2.线程不安全的原因 1.线程是抢占式执行的&#xff08;执行顺序是随机的&#xff09; 2.多个线程同时修改了同一个变量 3.原子性 4.内存可见性 5.指令重排序&#xff08;有序性&#xff09; 二、解决线…

Flutter TCP通信

启动TCP服务 Future<void> startServer() async {final server await ServerSocket.bind(InternetAddress.anyIPv4, 12345);print(Server listening on ${server.address}:${server.port});server.listen((Socket socket) {print(Client connected: ${socket.remoteAddr…

flask拆分计划

两个启动链接&#xff0c;看日志提示是因为2次启动&#xff0c;一次是database&#xff0c;一次是xmind2&#xff0c;去掉一次就可以&#xff0c;如何去掉一次&#xff1f; 这里启动也调用了一次&#xff0c;所以测试环境注释掉&#xff0c;如下图&#xff0c;也就调用了一次

【生活】ECMO原理、作用、费用及使用方法

博客目录 一、ECMO 是什么&#xff1f;二、ECMO 的作用1. 替代肺功能&#xff08;氧合与二氧化碳清除&#xff09;2. 替代心脏功能&#xff08;循环支持&#xff09;3. 为其他治疗争取时间4. 用于心肺复苏&#xff08;ECPR&#xff09; 三、ECMO 的费用1. 设备使用费2. 耗材费用…

Profinet转EtherCAT网关模块怎么用:案例分享

在某制造工厂西门子S7-1200 PLC中&#xff0c;存在一个技术难题&#xff0c;即伺服驱动器与可编程逻辑控制器&#xff08;PLC&#xff09;之间的通讯不兼容问题。具体而言&#xff0c;PLC采用的是PROFINET通讯协议&#xff0c;而伺服EtherCAT协议驱动器则需要EtherCAT协议进行数…

什么是 NLP-NLP基础知识体系的系统认知

NLP基础知识体系的系统认知 一、引言 今天的学习内容集中于自然语言处理&#xff08;NLP&#xff09;的基本概念、发展历程、核心任务及文本表示技术。通过这一学习过程&#xff0c;我对NLP这门学科有了更加系统和深入的认识&#xff0c;并且理解了NLP技术的广泛应用及其复杂…

数据结构 学习 链表 2025年6月14日08点01分

单向链表: 线性数据结构 由一系列节点组成 每个节点包含: 数据部分:存储实际数据 指针部分:储存指向下一个节点的引用 特点1,每个节点只有一个指向下一个节点的指针 特点2,只能从头到尾 单向遍历 特点3,不需要连续的内存空间 特点4,插入和删除效率高 特点5,随机访问 效率低 …

使用 Kubernetes 部署 PHP 留言板应用(含 Redis 架构)

使用 Kubernetes 部署 PHP 留言板应用&#xff08;含 Redis 架构&#xff09; 文章目录 使用 Kubernetes 部署 PHP 留言板应用&#xff08;含 Redis 架构&#xff09;教程概述技术架构特点 准备工作环境要求 Redis 数据库部署Redis 主从架构原理创建 Redis 领导者 Deployment部…

MATLAB提供的两种画误差矩阵的函数

MATLAB在统计学和机器学习工具包中提供了两种画误差矩阵&#xff08;Confusion matrix&#xff09;的函数。 figure; plotconfusion(YValidation,YPred)figure; cm confusionchart(YValidation,YPred) cm.Title Confusion Matrix for Validation Data; cm.RowSummary row-n…

【Java学习笔记】泛型

泛型 一、泛型的引出 代码示例 public class pra {public static void main(String[] args) {ArrayList arrayList new ArrayList();arrayList.add("java");arrayList.add("jack");arrayList.add("jom");arrayList.add(new a());for (Object…

SpringMVC系列(一)(介绍,简单应用以及路径位置通配符)

0 引言 作者正在学习SpringMVC相关内容&#xff0c;学到了一些知识&#xff0c;希望分享给需要短时间想要了解SpringMVC的读者朋友们&#xff0c;想用通俗的语言讲述其中的知识&#xff0c;希望与诸位共勉&#xff0c;共同进步&#xff01; 1 SpringMVC介绍 SpringMVC本质上…

Java中如何使用lambda表达式分类groupby

Java中如何使用lambda表达式分类groupby Java中如何使用lambda表达式分类groupby分类问题场景传统手写方式lambda使用groupBy()方法一行结束&#xff01;&#xff01;&#xff01;完整代码 Java中如何使用lambda表达式分类groupby 分类问题场景 比如一群学生根据性别和年龄排…