文章目录

    • 概述
    • 为什么要迁移到 C++,以及 C++ 的陷阱
      • 目标与挑战
      • 为什么不能直接使用 `std::function`?
    • 解决方案:POD 回调与模板 Trampoline
      • 核心设计
      • 模板 trampoline 实现
    • 两种成员函数绑定策略
      • 1. **Per-Transition Context(每个状态转移绑定一个对象)**
      • 2. **`sm->user_data`(通过状态机获取对象)**
    • 性能对比
    • 附完整代码

概述

本文将探讨如何将一个 C 风格的 HSM 迁移至 C++14/17,并解决在这一过程中遇到的核心挑战:如何在不引入堆分配、不牺牲实时性的前提下,优雅地将成员函数作为回调?

为什么要迁移到 C++,以及 C++ 的陷阱

目标与挑战

我们希望用 C++ 重构一个 C 风格的 HSM,以实现以下目标:

  • 更好的接口与可读性:利用面向对象特性,将状态机逻辑与业务对象紧密结合。
  • 零堆分配与确定性:保留在嵌入式/RTOS(如 RT-Thread)上无堆分配、可静态初始化和确定性行为的优势。
  • 简化成员函数绑定:方便地将成员函数作为状态机的回调,而无需为每个方法手动编写静态封装函数。

为什么不能直接使用 std::function

在通用编程中,std::function 提供了极大的便利,它能以统一的方式存储各种可调用对象。然而,在受限的嵌入式环境中,它可能带来致命的缺点:

  • 潜在的堆分配std::function 通常利用小对象优化(Small Object Optimization, SOO)来避免小尺寸可调用对象的堆分配。但当可调用对象超过其内部缓冲区大小时,它会回退到堆分配
  • 不可预测性:堆分配操作(new/malloc)会引入不确定的延迟抖动,这在实时系统中是不可接受的。
  • 无法静态初始化std::function 不能在编译时被定义为 constexpr,这意味着你无法将其直接放入 ROM 表(如 .rodata 段),从而增加了 RAM 占用。
  • 更大的代码体积std::function 的类型擦除机制和复杂的实现路径会显著增加二进制文件的大小。

因此,在状态机的热路径(如 dispatchactionguard 回调)中,应坚决避免 std::function

解决方案:POD 回调与模板 Trampoline

为了解决 std::function 的问题,我们提出一种基于**(Plain Old Data, POD)**的回调方案。

核心设计

我们定义一个非常简单且轻量级的回调结构体:

// 核心数据结构:精简且为 POD
struct ActionCallback {using Fn = void(*)(void* ctx, StateMachine* sm, const Event* ev);void* ctx;Fn fn;
};struct GuardCallback {using Fn = bool(*)(void* ctx, StateMachine* sm, const Event* ev);void* ctx;Fn fn;
};// 状态转移表保持 POD
struct Transition {uint32_t event_id;const State* target;GuardCallback guard;ActionCallback action;TransitionType type;
};

该方案的核心优势在于:

  • POD 类型ActionCallbackGuardCallback 都是 POD 类型,可以被 static const 定义并存储在 ROM 中,实现零运行时分配。
  • 简洁的调用路径:调用仅包含一次上下文指针读取和一次函数指针的间接调用,性能开销低且可预测。
  • 灵活的绑定:我们可以使用 C++ 模板来生成“trampoline”函数,将任意成员函数绑定到这个通用的 (void*, ...) 签名上。

模板 trampoline 实现

Trampoline(意为“跳板”)是一个内联的模板函数,它负责将通用的 (void*, ...) 参数转换为特定成员函数所需的 (this*, ...) 参数。

template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline void action_trampoline(void* ctx, StateMachine* sm, const Event* ev) {// 关键步骤:static_cast 将 void* 转换回目标对象指针T* obj = static_cast<T*>(ctx);// 成员函数调用(obj->*MF)(sm, ev);
}

通过这个 trampoline,我们可以方便地创建绑定,让状态机能够调用某个对象实例的特定成员函数:

template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline ActionCallback make_action(T* obj) {return { static_cast<void*>(obj), &action_trampoline<T, MF> };
}

static_cast 的开销误区:
很多人担心 static_cast<void*> -> T* 会引入额外的运行时开销。事实上,在绝大多数主流的 ABI(如 ARM、x86)和优化级别下,static_cast 是一个纯编译时指示,不会产生任何运行时代码。真正的开销来自于随后的间接调用。因此,不必担心 static_cast 会影响性能。

两种成员函数绑定策略

这套方案提供了两种实用的绑定策略,以适应不同的设计需求。

1. Per-Transition Context(每个状态转移绑定一个对象)

这种策略将上下文(ctx)指针直接存储在 Transition 结构中。

  • 优点:非常灵活,同一张状态转移表可以在运行时被多个对象实例复用,你只需在初始化时设置好每个回调的 ctx 指针。
  • 适用场景:当一个状态表被多个不同实例共享,但每个实例的回调逻辑(即成员函数)是其自身状态的一部分时。

2. sm->user_data(通过状态机获取对象)

该策略将 Transition 表设计为完全静态且无上下文指针(ctx = nullptr)。在 trampoline 函数中,我们从 StateMachine 实例中预设的 user_data 字段获取目标对象。

  • 优点:状态转移表可以被定义为 static constexpr,完全存储在 ROM 中,不占用任何 RAM。这是性能和资源占用的最优解。
  • 适用场景:每个状态机实例都唯一对应一个业务对象(例如在 Active Object 或 Actor 模式中)。你只需在初始化时通过 sm.set_user_data(this) 绑定一次即可。

性能对比

我们将几种常见的回调方案进行性能与开销的维度对比。

方案内存开销调用开销静态初始化动态绑定
直接调用极低(可内联)N/A不支持
POD 回调极低(POD)1次加载 + 1次间接调用
pointer-to-member低(与ABI相关)1次加载 + 1次间接调用
virtual低(vptr)1次间接调用N/A
std::function可变(有堆分配)可变(复杂)
  • POD 回调:在可静态化、无堆分配与低开销之间取得了最佳平衡,是嵌入式场景下的首选。
  • pointer-to-member:如果所有回调都属于同一类型(例如,所有 action 回调都来自同一个 MyFSM 类),pointer-to-member 可以进一步优化,提供与 POD 回调相似甚至更低的开销。但其在多继承或多类型混用时会变得复杂。
  • std::function:仅在初始化、非关键后台任务或非实时逻辑中使用。严禁在中断服务例程(ISR)或任何高频热路径中使用。

附完整代码

/*state_machine.hppModern C++14/17 hierarchical state machine (HSM) implementation.
*/
#ifndef STATE_MACHINE_HPP
#define STATE_MACHINE_HPP#include <cstdint>
#include <cstddef>
#include <cassert>
#include <type_traits>#ifndef HSM_ASSERT
#define HSM_ASSERT(expr) assert(expr)
#endifnamespace hsm
{using uint32_t = std::uint32_t;
using uint8_t  = std::uint8_t;
using size_t   = std::size_t;
using bool_t   = bool;// Forward
struct State;
struct Transition;
struct Event;
class StateMachine;/* Event */
struct Event
{uint32_t id;void *context;
};/* TransitionType */
enum class TransitionType : uint8_t
{External = 0,Internal = 1
};/* POD callback descriptors (no allocations, can be static) */
struct ActionCallback {using Fn = void (*)(void* ctx, StateMachine* sm, const Event* ev);void* ctx;Fn fn;
};struct GuardCallback {using Fn = bool (*)(void* ctx, const StateMachine* sm, const Event* ev);void* ctx;Fn fn;
};/* No-op constants */
inline void action_noop(void* /*ctx*/, StateMachine* /*sm*/, const Event* /*ev*/) {}
inline bool guard_always_true(void* /*ctx*/, const StateMachine* /*sm*/, const Event* /*ev*/) { return true; }namespace detail {/* Member function trampoline for actions */
template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline void action_trampoline(void* ctx, StateMachine* sm, const Event* ev) {T* obj = static_cast<T*>(ctx);(obj->*MF)(sm, ev);
}/* Member function trampoline for guards */
template<typename T, bool (T::*MG)(const StateMachine*, const Event*)>
inline bool guard_trampoline(void* ctx, const StateMachine* sm, const Event* ev) {T* obj = static_cast<T*>(ctx);return (obj->*MG)(sm, ev);
}/* Free/static function trampolines */
template<void (*F)(StateMachine*, const Event*)>
inline void action_fn_trampoline(void* /*ctx*/, StateMachine* sm, const Event* ev) {F(sm, ev);
}template<bool (*G)(const StateMachine*, const Event*)>
inline bool guard_fn_trampoline(void* /*ctx*/, const StateMachine* sm, const Event* ev) {return G(sm, ev);
}/* Forward declarations for trampolines needing StateMachine::user_data() */
template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline void action_from_sm_user_data(void* /*ctx*/, StateMachine* sm, const Event* ev);template<typename T, bool (T::*MG)(const StateMachine*, const Event*)>
inline bool guard_from_sm_user_data(void* /*ctx*/, const StateMachine* sm, const Event* ev);} // namespace detail/* Helper factories *//* Bind member function with explicit ctx pointer (per-transition ctx) */
template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline ActionCallback make_action(T* obj) {return ActionCallback{ static_cast<void*>(obj), &detail::action_trampoline<T, MF> };
}template<typename T, bool (T::*MG)(const StateMachine*, const Event*)>
inline GuardCallback make_guard(T* obj) {return GuardCallback{ static_cast<void*>(obj), &detail::guard_trampoline<T, MG> };
}template<void (*F)(StateMachine*, const Event*)>
inline ActionCallback make_action_fn() {return ActionCallback{ nullptr, &detail::action_fn_trampoline<F> };
}template<bool (*G)(const StateMachine*, const Event*)>
inline GuardCallback make_guard_fn() {return GuardCallback{ nullptr, &detail::guard_fn_trampoline<G> };
}template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline ActionCallback make_action_using_sm_user_data() {return ActionCallback{ nullptr, &detail::action_from_sm_user_data<T, MF> };
}template<typename T, bool (T::*MG)(const StateMachine*, const Event*)>
inline GuardCallback make_guard_using_sm_user_data() {return GuardCallback{ nullptr, &detail::guard_from_sm_user_data<T, MG> };
}/* Transition and State structures (POD-friendly) */
struct Transition
{uint32_t        event_id;const State*    target;   // ignored for internal transitionsGuardCallback   guard;ActionCallback  action;TransitionType  type;
};struct State
{const State*        parent;ActionCallback      entry_action;ActionCallback      exit_action;const Transition*   transitions;size_t              num_transitions;const char*         name;
};/* StateMachine class */
class StateMachine
{
public:StateMachine() noexcept;~StateMachine() noexcept = default;StateMachine(const StateMachine&) = delete;StateMachine& operator=(const StateMachine&) = delete;void init(const State* initial_state,const State** entry_path_buffer,uint8_t buffer_size,void* user_data = nullptr,ActionCallback unhandled_hook = ActionCallback{nullptr, nullptr}) noexcept;void deinit() noexcept;void reset() noexcept;bool dispatch(const Event* event) noexcept;bool is_in_state(const State* state) const noexcept;const char* get_current_state_name() const noexcept;void* user_data() const noexcept { return _user_data; }void set_user_data(void* p) noexcept { _user_data = p; }private:uint8_t _get_state_depth(const State* state) const noexcept;const State* _find_lca(const State* s1, const State* s2) const noexcept;void _perform_transition(const State* target_state, const Event* event) noexcept;const Transition* _find_matching_transition(const State* state, const Event* event, bool* guard_passed) const noexcept;bool _execute_transition(const Transition* transition, const Event* event) noexcept;bool _process_state_transitions(const State* state, const Event* event) noexcept;void _execute_exit_actions(const State* source_state, const State* lca, const Event* event) noexcept;int _build_entry_path(const State* target_state, const State* lca) noexcept;void _execute_entry_actions(uint8_t path_length, const Event* event) noexcept;private:const State*    _current_state = nullptr;const State*    _initial_state = nullptr;void*           _user_data = nullptr;ActionCallback  _unhandled_hook = ActionCallback{nullptr, nullptr};const State**   _entry_path_buffer = nullptr;uint8_t         _buffer_size = 0;
};/* ---------------- Implementation ---------------- */inline StateMachine::StateMachine() noexcept = default;inline void StateMachine::init(const State* initial_state,const State** entry_path_buffer,uint8_t buffer_size,void* user_data,ActionCallback unhandled_hook) noexcept
{HSM_ASSERT(initial_state != nullptr);HSM_ASSERT(entry_path_buffer != nullptr);HSM_ASSERT(buffer_size > 0);bool valid = (initial_state != nullptr) && (entry_path_buffer != nullptr) && (buffer_size > 0);if (!valid) return;_user_data = user_data;_unhandled_hook = unhandled_hook;_initial_state = initial_state;_entry_path_buffer = entry_path_buffer;_buffer_size = buffer_size;_current_state = nullptr;_perform_transition(initial_state, nullptr);
}inline void StateMachine::deinit() noexcept
{_current_state = nullptr;_initial_state = nullptr;_user_data = nullptr;_unhandled_hook = ActionCallback{nullptr, nullptr};_entry_path_buffer = nullptr;_buffer_size = 0;
}inline void StateMachine::reset() noexcept
{if (_initial_state != nullptr) _perform_transition(_initial_state, nullptr);
}inline bool StateMachine::dispatch(const Event* event) noexcept
{HSM_ASSERT(event != nullptr);HSM_ASSERT(_current_state != nullptr);if ((_unhandled_hook.fn != nullptr) && (event != nullptr)){_unhandled_hook.fn(_unhandled_hook.ctx, this, event);}const State* state_iter = _current_state;bool handled = false;while (state_iter != nullptr){if (_process_state_transitions(state_iter, event)){handled = true;break;}state_iter = state_iter->parent;}if ((!handled) && (_unhandled_hook.fn != nullptr)){_unhandled_hook.fn(_unhandled_hook.ctx, this, event);}return handled;
}inline bool StateMachine::is_in_state(const State* state) const noexcept
{HSM_ASSERT(state != nullptr);HSM_ASSERT(_current_state != nullptr);const State* iter = _current_state;while (iter != nullptr){if (iter == state) return true;iter = iter->parent;}return false;
}inline const char* StateMachine::get_current_state_name() const noexcept
{HSM_ASSERT(_current_state != nullptr);if (_current_state->name != nullptr) return _current_state->name;return "Unknown";
}/* Private helpers */inline uint8_t StateMachine::_get_state_depth(const State* state) const noexcept
{HSM_ASSERT(state != nullptr);uint8_t depth = 0;const State* cur = state;while (cur != nullptr){++depth;cur = cur->parent;}return depth;
}inline const State* StateMachine::_find_lca(const State* s1, const State* s2) const noexcept
{if (s1 == nullptr) return s2;if (s2 == nullptr) return s1;const State* p1 = s1;const State* p2 = s2;uint8_t d1 = _get_state_depth(p1);uint8_t d2 = _get_state_depth(p2);while (d1 > d2) { p1 = p1->parent; --d1; }while (d2 > d1) { p2 = p2->parent; --d2; }while (p1 != p2) { p1 = p1->parent; p2 = p2->parent; }return p1;
}inline void StateMachine::_perform_transition(const State* target_state, const Event* event) noexcept
{HSM_ASSERT(target_state != nullptr);const State* source_state = _current_state;bool same_state = (source_state == target_state);if (same_state){if ((source_state != nullptr) && (source_state->exit_action.fn != nullptr))source_state->exit_action.fn(source_state->exit_action.ctx, this, event);if (target_state->entry_action.fn != nullptr)target_state->entry_action.fn(target_state->entry_action.ctx, this, event);}else{const State* lca = _find_lca(source_state, target_state);_execute_exit_actions(source_state, lca, event);int path_length = _build_entry_path(target_state, lca);if (path_length < 0){HSM_ASSERT(0);return;}_current_state = target_state;_execute_entry_actions(static_cast<uint8_t>(path_length), event);}
}inline const Transition* StateMachine::_find_matching_transition(const State* state, const Event* event, bool* guard_passed) const noexcept
{HSM_ASSERT(state != nullptr);HSM_ASSERT(event != nullptr);HSM_ASSERT(state->transitions != nullptr);HSM_ASSERT(state->num_transitions != 0);if (guard_passed) *guard_passed = false;for (size_t i = 0; i < state->num_transitions; ++i){const Transition* t = &state->transitions[i];if (t->event_id == event->id){bool g = true;if (t->guard.fn != nullptr){g = t->guard.fn(t->guard.ctx, this, event);}if (g){if (guard_passed) *guard_passed = true;return t;}}}return nullptr;
}inline bool StateMachine::_execute_transition(const Transition* transition, const Event* event) noexcept
{HSM_ASSERT(transition != nullptr);HSM_ASSERT(event != nullptr);if (transition->type == TransitionType::Internal){if (transition->action.fn != nullptr)transition->action.fn(transition->action.ctx, this, event);}else{if (transition->action.fn != nullptr)transition->action.fn(transition->action.ctx, this, event);_perform_transition(transition->target, event);}return true;
}inline bool StateMachine::_process_state_transitions(const State* state, const Event* event) noexcept
{HSM_ASSERT(state != nullptr);HSM_ASSERT(event != nullptr);bool guard_passed = false;const Transition* matching = _find_matching_transition(state, event, &guard_passed);if ((matching != nullptr) && guard_passed){return _execute_transition(matching, event);}return false;
}inline void StateMachine::_execute_exit_actions(const State* source_state, const State* lca, const Event* event) noexcept
{const State* iter = source_state;while ((iter != nullptr) && (iter != lca)){if (iter->exit_action.fn != nullptr)iter->exit_action.fn(iter->exit_action.ctx, this, event);iter = iter->parent;}
}inline int StateMachine::_build_entry_path(const State* target_state, const State* lca) noexcept
{HSM_ASSERT(target_state != nullptr);HSM_ASSERT(_entry_path_buffer != nullptr);HSM_ASSERT(_buffer_size > 0);const State* iter = target_state;uint8_t idx = 0;while ((iter != nullptr) && (iter != lca)){if (idx < _buffer_size){_entry_path_buffer[idx] = iter;++idx;iter = iter->parent;}else{return -1; // buffer insufficient}}return static_cast<int>(idx);
}inline void StateMachine::_execute_entry_actions(uint8_t path_length, const Event* event) noexcept
{HSM_ASSERT(_entry_path_buffer != nullptr);int idx = static_cast<int>(path_length) - 1;for (; idx >= 0; --idx){const State* s = _entry_path_buffer[idx];if ((s != nullptr) && (s->entry_action.fn != nullptr))s->entry_action.fn(s->entry_action.ctx, this, event);}
}/* ---------------- Definitions that require complete StateMachine type ---------------- */
namespace detail {template<typename T, void (T::*MF)(StateMachine*, const Event*)>
inline void action_from_sm_user_data(void* /*ctx*/, StateMachine* sm, const Event* ev) {T* obj = static_cast<T*>(sm->user_data());(obj->*MF)(sm, ev);
}template<typename T, bool (T::*MG)(const StateMachine*, const Event*)>
inline bool guard_from_sm_user_data(void* /*ctx*/, const StateMachine* sm, const Event* ev) {T* obj = static_cast<T*>(sm->user_data());return (obj->*MG)(sm, ev);
}} // namespace detail} // namespace hsm#endif // STATE_MACHINE_HPP
// example.cpp
// Updated to match state_machine.hpp changes:
// - Guard member functions now accept `const StateMachine*`
// - use make_guard_using_sm_user_data accordingly#include "state_machine.hpp"
#include <iostream>// Simple IDs
enum : uint32_t { EVT_START = 1, EVT_STOP = 2, EVT_TICK = 3 };struct Controller
{// Actions still take non-const StateMachine*void on_entry(hsm::StateMachine* /*sm*/, const hsm::Event* /*e*/) {std::cout << "Controller::on_entry\n";}void on_exit(hsm::StateMachine* /*sm*/, const hsm::Event* /*e*/) {std::cout << "Controller::on_exit\n";}// Guard now receives a const StateMachine*bool guard_allow(const hsm::StateMachine* /*sm*/, const hsm::Event* /*e*/) {std::cout << "Controller::guard_allow -> true\n";return true;}void handle_start(hsm::StateMachine* /*sm*/, const hsm::Event* /*e*/) {std::cout << "Controller::handle_start\n";}void handle_stop(hsm::StateMachine* /*sm*/, const hsm::Event* /*e*/) {std::cout << "Controller::handle_stop\n";}
};// Forward declare states
extern const hsm::State S_idle;
extern const hsm::State S_running;// Transitions for idle
static const hsm::Transition T_idle[] = {// EVT_START -> S_running, guard using sm->user_data, action using sm->user_data{ EVT_START, &S_running,hsm::make_guard_using_sm_user_data<Controller, &Controller::guard_allow>(),hsm::make_action_using_sm_user_data<Controller, &Controller::handle_start>(),hsm::TransitionType::External}
};// Transitions for running
static const hsm::Transition T_running[] = {{ EVT_STOP, &S_idle,hsm::make_guard_using_sm_user_data<Controller, &Controller::guard_allow>(),hsm::make_action_using_sm_user_data<Controller, &Controller::handle_stop>(),hsm::TransitionType::External},{ EVT_TICK, &S_running,hsm::GuardCallback{nullptr, nullptr}, // no guardhsm::ActionCallback{nullptr, nullptr}, // no actionhsm::TransitionType::Internal}
};// State definitions
const hsm::State S_idle = {nullptr,hsm::make_action_using_sm_user_data<Controller, &Controller::on_entry>(),hsm::make_action_using_sm_user_data<Controller, &Controller::on_exit>(),T_idle, sizeof(T_idle)/sizeof(T_idle[0]),"Idle"
};const hsm::State S_running = {nullptr,hsm::make_action_using_sm_user_data<Controller, &Controller::on_entry>(),hsm::make_action_using_sm_user_data<Controller, &Controller::on_exit>(),T_running, sizeof(T_running)/sizeof(T_running[0]),"Running"
};int main()
{Controller ctrl;// entry path buffer sized for max hierarchy depth (2 here)const hsm::State* buffer[4];hsm::StateMachine sm;sm.init(&S_idle, buffer, 4, &ctrl, hsm::ActionCallback{nullptr, nullptr});std::cout << "Current state: " << sm.get_current_state_name() << "\n";hsm::Event ev_start{EVT_START, nullptr};sm.dispatch(&ev_start); // should transition to Running and call handle_startstd::cout << "After START state: " << sm.get_current_state_name() << "\n";hsm::Event ev_stop{EVT_STOP, nullptr};sm.dispatch(&ev_stop); // back to Idlestd::cout << "After STOP state: " << sm.get_current_state_name() << "\n";return 0;
}
/*
$ ./example_bin                                                                                                                                                                                                   
Current state: Idle
Controller::guard_allow -> true
Controller::handle_start
Controller::on_exit
Controller::on_entry
After START state: Running
Controller::guard_allow -> true
Controller::handle_stop
Controller::on_exit
Controller::on_entry
After STOP state: Idle
*/

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

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

相关文章

【unity】Obfuz加固混淆日志还原解析方案ObfuzResolver

Github | Gitee ObfuzResolver是基于obfuz-tools针对Obfuz的一项辅助工具&#xff0c;方便开发者在unity编辑器中或者运行时快捷将使用Obfuz混淆加固后的日志信息还原为原始信息&#xff0c;以辅助开发者快速定位Bug。 特性 支持unity编辑器模式下还原被加固混淆的日志信息&a…

2025DevOps平台趋势解读:哪些DevOps工具正在引领行业变革?

DevOps平台已成为企业提升研发效能、实现敏捷交付的核心支柱。2025年DevOps领域正经历深刻变革&#xff0c;平台能力正从“工具链整合”向“价值流智能中枢”跃升。01. 2025Devops平台趋势解读“全栈式”与“模块化/可组合”的平衡&#xff1a;企业既需要能覆盖开发、测试、部署…

第二阶段Winform-4:MDI窗口,布局控件,分页

1_MDI窗口 &#xff08;1&#xff09;MDI是指将多控件窗体在同一窗体中打开,可以设置重叠打开&#xff0c;平捕打开等&#xff0c;MDI窗体&#xff08;Multiple-Document Interface&#xff0c;多文档界面&#xff09;用于同时显示多个文档。在项目中使用MDI窗体时&#xff0c…

实用R语言机器学习指南:从数据预处理到模型实战(附配套学习资源)

一、为什么需要掌握机器学习建模&#xff1f;在科研与项目实践中&#xff0c;机器学习已成为数据挖掘的核心工具。本文手把手带你在R语言中实现7大常用模型&#xff1a;逻辑回归/正则化回归决策树/随机森林SVM支持向量机XGBoost梯度提升神经网络全程包含数据标准化→模型训练→…

go.uber.org/zap 日志库高性能写入

使用 go.uber.org/zap 实现日志分割功能 实现按照单个文件最大MB自动分割,最多保留多少天的文件,是否启用压缩,按天自动分割日志 核心依赖 go.uber.org/zap:核心日志库 lumberjack.v2:日志轮转工具(实现按大小/时间分割) 时间处理依赖标准库 time 实现步骤 1. 初始化…

电脑端完全免费的动态壁纸和屏保软件(真正免费、无广告、无会员)

✅ 1. Lively Wallpaper&#xff08;强烈推荐&#xff09; 特点&#xff1a;完全免费、开源、无广告&#xff0c;支持本地视频/GIF/网页作为动态壁纸内置资源&#xff1a;12个高质量动态壁纸&#xff08;可自定义&#xff09;屏保功能&#xff1a;支持将动态壁纸一键设为屏保系…

C#_组合优于继承的实际应用

2.2 Composition over Inheritance&#xff1a;组合优于继承的实际应用 继承&#xff08;Inheritance&#xff09;是面向对象编程中最容易被过度使用和误用的特性之一。传统的教学往往让人们优先选择继承来实现代码复用和建立“是一个&#xff08;is-a&#xff09;”的关系。然…

Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失 broker在故障后的消息丢失 消费者在消费过程中的消息丢失ACK机制 ack有3个可选值&#xff0c;分别是1&#xff0c;0&#xff0c;-1。 ack0&#xff1a;生产者在生产过程中的消息丢失 简单来说就是&#xff0c;producer发送一次就不再发送了&#…

盼之代售 231滑块 csessionid 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 部分python代码 url "…

STL关联式容器解析:map与set详解

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.2 set的使用 3.2 map 3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.3.2 multiset的使用 3.4 multimap 3.4.1 multimap的介绍 3.4.2 multimap的使用 4.红黑树模拟实现…

贪吃蛇--C++实战项目(零基础)

视频地址&#xff1a;C语言必学项目&#xff1a;贪吃蛇&#xff01; 贪吃蛇游戏框架 ├── 基础框架 │ ├── 头文件引入 │ ├── 常量和宏定义 │ └── 窗口初始化 │ ├── 数据结构系统 │ ├── Pos结构体(位置和颜色) │ ├── Snake结构体(蛇的属性) │ ├──…

unity资源领取反作弊工具加密器

https://assetstore.unity.com/packages/tools/utilities/anti-cheat-pro-2025-3006260元购码GUARDINGPEARSOFTWARE

FPGA设计中的信号完整性量化与优化:探索高速数字系统的关键路径

在高速FPGA设计中&#xff0c;信号完整性&#xff08;Signal Integrity, SI&#xff09;已成为确保系统稳定、可靠运行的核心要素之一。随着数据传输速率的不断提升和电路规模的日益复杂&#xff0c;信号在传输过程中受到的干扰和畸变问题日益凸显。因此&#xff0c;如何有效量…

`strncasecmp` 字符串比较函数

1) 函数的概念与用途 strncasecmp 是 C 语言中一个非常实用的字符串处理函数&#xff0c;它执行不区分大小写的字符串比较&#xff0c;但只比较前 n 个字符。这个函数的名字来源于"string n case-compare"&#xff08;字符串前n个字符不区分大小写比较&#xff09;。…

软件安装教程(一):Visual Studio Code安装与配置(Windows)

文章目录前言一、Visual Studio Code下载二、安装步骤&#xff08;Windows&#xff09;1. GUI安装2. 命令行安装三、首次启动后建议的快速配置&#xff08;几分钟完成&#xff09;四、常见问题 & 小贴士总结前言 Visual Studio Code&#xff08;VS Code&#xff09;是一款…

JavaSSM框架从入门到精通!第三天(MyBatis(二))!

四、Mapper 的动态代理1. 引入 在上面的 CURD 例子中&#xff0c;我们发现&#xff1a;Dao 层的实现类的每一个方法仅仅是通过 SqlSession 对象的相关 API 定位到映射文件 mapper 中的 SQL 语句&#xff0c;真正对数据库操作的工作实际上是有 Mybatis 框架通过 mapper 中的 SQL…

大模型应用发展与Agent前沿技术趋势(下)

Agent技术的行业应用与实践案例 金融领域的Agent应用 金融行业是大模型Agent技术应用最为广泛的领域之一&#xff0c;涵盖了风险评估、投资决策、客户服务等多个方面。在金融风控领域&#xff0c;Agent系统通过结合大模型的语义理解能力和强化学习的决策优化能力&#xff0c;能…

94. 城市间货物运输 I, Bellman_ford 算法, Bellman_ford 队列优化算法

94. 城市间货物运输 IBellman_ford 算法Bellman_ford 算法 与 dijkstra算法 相比通用性更强。dijkstra算法解决不了负权边的问题&#xff0c;因为Dijkstra基于贪心策略&#xff0c;一旦一个节点被从队列中取出&#xff08;标记为已解决&#xff09;&#xff0c;它就假定已经找到…

如何使用Prometheus + Grafana + Loki构建一个现代化的云原生监控系统

如何使用 Prometheus + Grafana + Loki 构建一个现代化的云原生监控系统。这套组合被誉为监控领域的“瑞士军刀”,功能强大且生态极佳。 一、核心组件概念介绍 在搭建之前,深刻理解每个组件的角色和职责至关重要。 1. Prometheus(指标监控与时序数据库) 角色:系统的“核…

JavaScript Object 操作方法及 API

一、对象创建方式1.字面量创建&#xff08;最常用&#xff09;const obj { name: "张三", age: 25 };2.构造函数创建const obj new Object(); obj.name "李四";3.Object.create()&#xff08;指定原型&#xff09;const proto { greet: () > "…