<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://blog.atoery.cn/index.php/feed/rss/category/computer/">
<title>JRNitre&#039;s Blog - 计算机</title>
<link>https://blog.atoery.cn/index.php/category/computer/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://blog.atoery.cn/index.php/2026/04/07/174.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/11/26/171.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/06/29/156.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/06/10/155.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/06/05/153.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/05/25/149.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/05/12/142.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/05/12/141.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/04/17/93.html"/>
<rdf:li resource="https://blog.atoery.cn/index.php/2025/03/20/70.html"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://blog.atoery.cn/index.php/2026/04/07/174.html">
<title>[编程范式] DI 依赖管理与手动依赖注入</title>
<link>https://blog.atoery.cn/index.php/2026/04/07/174.html</link>
<dc:date>2026-04-07T11:28:18+08:00</dc:date>
<description>为什么会写这篇文章在刚接触 DI 时，最容易混淆的不是语法，而是对象到底在什么时候被创建、依赖到底由谁来传。很多人之前写项目时，已经在使用“依赖注入”，只是方式是手动的。例如：在 main 中先创建配置对象再创建 logger再创建 service最后通过 NewUserSrv(...)、NewUserAPI(...) 这类函数一层层传下去这当然也是依赖注入，但它是人工装配。而使用 DI 容器之后，依赖的装配关系不再由 main 手动维护，而是交给容器在运行时完成。本文会围绕这个核心差异展开说明。一句话区分两种方式手动依赖注入：你自己创建对象你自己把依赖传进去你自己维护对象之间的装配顺序DI 容器管理依赖：你声明对象需要什么依赖你把对象注册给容器容器在运行时创建对象并填充依赖你只需要从容器中取出装配完成的对象可以把它概括为一句话：以前你是自己搬运依赖；现在你是声明依赖，由容器配送。手动依赖注入是什么先看最熟悉的方式。假设有一个删除用户的功能，依赖：配置 Config日志 Logger鉴权模块 AuthHandler手动注入的常见写法如下：type AuthHandler struct {
    Config *modelconfig.Config
    Logger *slog.Logger
}

func NewAuthHandler(cfg *modelconfig.Config, logger *slog.Logger) *AuthHandler {
    return &amp;AuthHandler{
        Config: cfg,
        Logger: logger,
    }
}

type UserController struct {
    Config *modelconfig.Config
    Logger *slog.Logger
    Auth   *AuthHandler
}

func NewUserController(
    cfg *modelconfig.Config,
    logger *slog.Logger,
    auth *AuthHandler,
) *UserController {
    return &amp;UserController{
        Config: cfg,
        Logger: logger,
        Auth:   auth,
    }
}然后在 main 中手动组装：cfg := loadConfig()
logger := newLogger(cfg)
authHandler := NewAuthHandler(cfg, logger)
userController := NewUserController(cfg, logger, authHandler)

engine.DELETE(&quot;/api/user/delete&quot;, userController.Delete)这就是手动依赖注入。它的问题在哪里这种方式在小项目里完全可行，但随着模块变多，会有几个问题：main 必须知道所有对象的内部依赖关系创建顺序必须手动维护任何对象新增依赖，启动层都要跟着改NewXxx(...) 和参数传递链会越来越长这时启动层就不再只是“启动程序”，而变成了“手工装配整个系统”。DI 容器管理依赖是什么使用 DI 容器时，思路会变成：定义一个对象在对象上声明它需要哪些依赖把这个对象注册到容器容器在运行时创建并注入依赖使用方只负责获取这个对象例如：type UserController struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}

func (c *UserController) Delete(ctx *gin.Context) {
    c.Logger.Info(&quot;delete user request received&quot;)
}这里最关键的变化是：UserController 不再通过 NewUserController(...) 接收依赖它只声明自己需要什么具体依赖由容器负责填充然后在容器里注册：func registerControllerBeans() error {
    if _, err := di.RegisterBean(
        &quot;userController&quot;,
        reflect.TypeOf((*controller.UserController)(nil)),
    ); err != nil {
        return err
    }

    return nil
}如果 authHandler、appConfig、appLogger 也都已注册，那么当容器初始化时，UserController 就能被完整装配。最后在使用处获取：userControllerInstance, err := di.GetInstanceSafe(&quot;userController&quot;)
if err != nil {
    return err
}

userController := userControllerInstance.(*controller.UserController)
engine.DELETE(&quot;/api/user/delete&quot;, userController.Delete)这里没有手动创建 Config、Logger、Auth 并塞进 UserController。容器已经做完了这件事。最容易误解的地方很多人第一次接触 DI 时，会下意识地觉得：既然函数要用到依赖那么总得先创建一个 deps 实例然后把它作为参数传给下一层这个想法来自手动依赖注入的经验，本身没有错，但它不适用于容器式 DI 的核心模型。在 DI 中，你写下来的 struct 更像是一个“对象模板”或者“依赖声明模板”：type UserController struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}你在写代码时，并没有真的创建这个对象。真正的对象创建发生在程序运行时：容器读取注册信息容器创建 UserController 实例容器按 di.inject 标签填充字段最后把这个完整对象交给你所以真正的关键不是“我该怎么手动造一个 deps”，而是：userControllerInstance, err := di.GetInstanceSafe(&quot;userController&quot;)
userController := userControllerInstance.(*controller.UserController)这两行的意义是：从容器中取出已经装配完成的 userController将返回的 interface{} 断言成具体类型后续直接调用这个对象的方法这里不是类型断言在“智能填充依赖”，而是容器在此之前已经完成了填充；类型断言只是为了让 Go 知道这个对象的具体类型。为什么说依赖像对象的成员变量从 OOP 的角度看，DI 注入后的依赖非常像一个对象的成员变量。例如：type UserController struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}

func (c *UserController) Delete(ctx *gin.Context) {
    c.Logger.Info(&quot;start delete user&quot;)
}在方法内部，Config、Logger、Auth 的使用体验和普通成员变量完全一样。差别只在于：普通对象的成员变量通常由你手动赋值DI 对象的成员变量由容器在运行时自动填充因此可以这样理解：方法只依赖自己的成员变量DI 容器负责提前把这些成员变量填好你拿到对象后，直接调用它的方法即可main 中的 runtimeDeps 和控制器 Bean 有什么区别这是另一个很容易混淆的问题。在当前项目里，main 中有这样一层：type runtimeDeps struct {
    logger         *slog.Logger
    logHandler     *logx.Handler
    webHandler     *web.Handler
    testController *controller.TestController
}然后通过类似这样的函数集中获取：func loadRuntimeDeps() (*runtimeDeps, error) {
    loggerInstance, err := di.GetInstanceSafe(&quot;appLogger&quot;)
    if err != nil {
        return nil, err
    }

    webHandlerInstance, err := di.GetInstanceSafe(&quot;webHandler&quot;)
    if err != nil {
        return nil, err
    }

    return &amp;runtimeDeps{
        logger:     loggerInstance.(*slog.Logger),
        webHandler: webHandlerInstance.(*web.Handler),
    }, nil
}这和 userController := di.GetInstanceSafe(&quot;userController&quot;) 看起来很像，但角色不同。runtimeDeps 是启动层的手动聚合它的作用是：把 main 启动流程直接要用到的几个顶层 Bean 聚合起来让 run() 只面对一个统一的依赖入口这是一种启动层便利封装，不是 DI 自动注入 run()。换句话说：DI 负责把 logger、webHandler、testController 都准备好loadRuntimeDeps() 再把这些“已经注入完成的对象”聚合成一个 structuserController 是容器直接交付的业务对象而 userController 不一样。它是一个真正的消费者 Bean。容器会直接为它完成内部依赖注入。你拿到它时，它已经是完整对象：userControllerInstance, err := di.GetInstanceSafe(&quot;userController&quot;)
userController := userControllerInstance.(*controller.UserController)所以这两者的本质区别是：runtimeDeps：启动层手动聚合多个已注入对象userController：容器直接交付的已注入对象是否还需要单独定义 UserDeps很多人会想写成这样：type UserDeps struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}

func Delete(deps UserDeps) {
}这不是完全不行，但通常不推荐。更推荐的写法是：type UserController struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}

func (c *UserController) Delete(ctx *gin.Context) {
}原因很简单：路由最终绑定的是一个对象的方法对象本身就是依赖载体没必要再人为拆一个 UserDeps因此在容器式 DI 中，更自然的组织方式是：用对象表达业务角色，例如 UserController用对象字段表达它的依赖用对象方法表达它的行为一个完整的运作流程假设现在有接口 /api/user/delete，它需要：配置日志鉴权模块推荐的实现路径如下。第一步：定义依赖消费者文件：internal/controller/user.gopackage controller

import (
    &quot;log/slog&quot;

    &quot;github.com/gin-gonic/gin&quot;

    &quot;github.com/jrnitre/blackshores/internal/auth&quot;
    modelconfig &quot;github.com/jrnitre/blackshores/model/config&quot;
)

type UserController struct {
    Config *modelconfig.Config `di.inject:&quot;appConfig&quot;`
    Logger *slog.Logger        `di.inject:&quot;appLogger&quot;`
    Auth   *auth.Handler       `di.inject:&quot;authHandler&quot;`
}

func (c *UserController) Delete(ctx *gin.Context) {
    c.Logger.Info(&quot;delete user request received&quot;)
    ctx.JSON(200, gin.H{
        &quot;message&quot;: &quot;ok&quot;,
    })
}第二步：注册 Bean文件：internal/container/controller.gofunc registerControllerBeans() error {
    if _, err := di.RegisterBean(
        &quot;userController&quot;,
        reflect.TypeOf((*controller.UserController)(nil)),
    ); err != nil {
        return err
    }

    return nil
}文件：internal/container/auth.gofunc registerAuthBeans() error {
    if _, err := di.RegisterBean(
        &quot;authHandler&quot;,
        reflect.TypeOf((*auth.Handler)(nil)),
    ); err != nil {
        return err
    }

    return nil
}第三步：初始化容器文件：cmd/main.goif err := container.Init(); err != nil {
    return err
}第四步：获取顶层对象userControllerInstance, err := di.GetInstanceSafe(&quot;userController&quot;)
if err != nil {
    return err
}

userController := userControllerInstance.(*controller.UserController)第五步：绑定到 APIengine.DELETE(&quot;/api/user/delete&quot;, userController.Delete)到这里，依赖链就已经打通了。注意：main 不需要手动这样做：cfg := ...
logger := ...
authHandler := ...
userController := ...这些装配工作已经交给 DI 容器处理。应该由谁获取依赖这是实践中最重要的边界之一。main 应该获取什么main 只应该获取“启动流程要直接使用的顶层对象”，例如：webHandlertestControlleruserControllerappLoggermain 不应该获取什么main 不应该为了组装某个控制器，再单独去拿它的内部依赖，例如：appConfigauthHandlerdbService然后自己拼成一个 UserController。如果 main 开始知道 UserController 依赖什么、怎么创建，那就说明启动层又回到了手动装配模式。DI 与手动注入的关系DI 不是对手动注入的否定，而是对手动注入的进一步抽象。两者的关系可以这样理解：手动依赖注入也是依赖注入DI 容器管理依赖是“由容器执行的依赖注入”两者目标相同，都是让对象依赖外部提供，而不是自己在内部硬编码创建两者差别在于：由谁来负责装配所以如果非要用一句最严谨的话概括：DI 容器并没有消灭依赖注入，它只是把“注入动作”从开发者手里接管过去。最后总结如果只保留最核心的结论，那么有下面几条：手动依赖注入与 DI 容器管理依赖的本质差别，不在于“有没有注入”，而在于“谁来装配”。在 DI 模式中，你写下的 struct 更像是对象模板；真正的实例是在程序运行时由容器创建的。依赖字段可以理解为对象的成员变量，方法只依赖自己的成员变量工作。di.GetInstanceSafe(&quot;userController&quot;) 的意义是“取出已装配完成的对象”，不是“现场组装对象”。main 只获取顶层对象，不手动拼装它们的内部依赖。runtimeDeps 是启动层的手动聚合；userController 是容器直接交付的业务对象，这两者不要混淆。当你真正接受“我写的是依赖声明，容器负责运行时装配”这个前提之后，DI 的理解就会突然变得清晰很多。</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/11/26/171.html">
<title>软件工程中的显式依赖与隐式依赖</title>
<link>https://blog.atoery.cn/index.php/2025/11/26/171.html</link>
<dc:date>2025-11-26T16:58:18+08:00</dc:date>
<description>0.0 前言许多项目在开发初期，为了快速实现功能、看到效果从而在编码的过程中加入了大量的隐式依赖；如果不对其处理在编码工作进行到后期时可能遇到：难以测试、调试、代码架构升级等，从而造出一大坨屎山。大量隐式依赖的存在导致后期对代码结构进行修改时极其困难，比如你请了一位大厨来到家里为你做饭，来之前大厨用电话跟你沟通，说是需要你准备面粉，鸡蛋。。。等等一大堆“依赖”。结果厨师到你家开始做饭了，做一半突然告诉你：哎呀！我家祖传的大铁锅没拿啊！没这口锅我不会做饭了！没办法还得让人家跑回家拿锅；这时想到为什么你不打电话的时候就告诉我你有“铁锅”这一依赖啊？总不能明天我让你给我做炒菜的时候再告诉我祖传的锅铲没拿吧。所以隐式依赖对项目开发过程中可能导致没法编写单元测试，耦合度高等问题所在。1.0 隐式依赖我们给出一段 Go 的业务代码，显而易见的标出了隐式依赖的位置：// api/user.go
func UserRegister() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        var req types.UserRegisterReq
        if err := ctx.ShouldBindJSON(&amp;req); err != nil {
            // ...
            return
        }

        // ⚠️ 隐式依赖：直接调用全局单例
        svc := service.GetUserSrv()
        resp, err := svc.UserRegister(ctx.Request.Context(), &amp;req)
        // ...
    }
}这样的代码存在很多问题：不可测试单元测试时无法替换 GetUserSrv()，必须启动真实数据库，导致测试慢、不稳定。行为不可控测试无法模拟“用户已存在”“网络超时”等异常场景。耦合度高Handler 与具体 Service 实现强绑定，违反“依赖倒置原则”。文档缺失从函数签名无法得知其真实依赖，增加理解成本。这类代码常被称为“黑盒”——你知道输入，但不知道它背后偷偷用了什么。2.0 显式依赖现在我们对上述有问题的代码进行改进：// 定义接口契约
type UserRegisterService interface {
    UserRegister(context.Context, *types.UserRegisterReq) (*UserResponse, error)
}

// Handler 工厂函数：依赖通过参数注入
func MakeUserRegisterHandler(svc UserRegisterService) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        var req types.UserRegisterReq
        if err := ctx.ShouldBindJSON(&amp;req); err != nil {
            // ...
            return
        }

        // ✅ 显式使用注入的依赖
        resp, err := svc.UserRegister(ctx.Request.Context(), &amp;req)
        // ...
    }
}

// 生产环境注册
router.POST(&quot;/register&quot;, MakeUserRegisterHandler(service.NewUserSrv(db)))这样修改后，对其编写单元测试，不依赖后续 service 层提供的函数，不需要启动实际的数据库，这种设计不仅提升了代码的可测试性，更增强了系统的弹性。3.0 常见隐式依赖陷阱及规避策略隐式依赖形式风险显式变化方案全局变量(var db *sql.DB)测试污染、并发问题通过构造函数注入单例函数(service.GetInstance())无法 mock改为接口 + 工厂函数time.Now()时间不可控注入func() time.Timeos.Getenv(&quot;DB_URL&quot;)配置硬编码抽象为Config接口http.Get(...)网络不可控封装为 HTTPClient 接口黄金法则：任何外部依赖都应通过接口抽象，并由上层注入。</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/06/29/156.html">
<title>[Qt] Qt Quick QML 入门 / QML 与 C++ 进行交互 0x01</title>
<link>https://blog.atoery.cn/index.php/2025/06/29/156.html</link>
<dc:date>2025-06-29T15:42:00+08:00</dc:date>
<description>1.0 概述QML 是一种声明式脚本语言 (Qt Meta Language) 作为与 C++ 并列的 Qt 开发语言。示例：一个具有按钮的页面import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.5

Window {
    id: window
    visible: true
    width: 640
    height: 480
    title: qsTr(&quot;Button_Demo&quot;)

    Button{
        id: getBtn
        text: &quot;Button&quot;
        width: 120
        height: 40
        onClicked: {
            // TODO 点击回调
        }
    }

}2.0 QML 与 C++QML 与 C++ 的交互可以分为四种形式：注册 C++ 对象到 QML ，在 QML 中访问对象将 QML 暴露给 C++ 进行操作C++ 创建 QML 对象并进行操作C++ 对象与 QML 通过信号与槽进行交互本文章进对第一种方式进行介绍 (后续会完善别的方法并在上面进行引用)3.0 注册 C++ 对象到 QML 并使用首先创建一个类给 QML 调用：#include &lt;QObject&gt;

class qmlClass : public QObject
{
    Q_OBJECT
public:
    explicit qmlClass(QObject *parent = nullptr);

    Q_INVOKABLE void setValue(int value);
    Q_INVOKABLE int getValue(void);

signals:

private:
    int Value;
};创建一个类继承自 QObject 这里简单实现一个获取和设置 value 的方法。这里需要注意的是两个方法均用 Q_INVOKABLE 关键字进行修饰，这是类中的子方法能被 qml 访问的关键点，不能忽略。实现前者声明好的方法：qmlClass::qmlClass(QObject *parent) : QObject(parent){
    this-&gt;Value = 100;
}


int qmlClass::getValue(){
    return this-&gt;Value;
}

void qmlClass::setValue(int value){
    this-&gt;Value = value;
}现在来到 main.cpp 文件中，使用 qml 引擎将前者编写的类放在其中声明：qmlClass qclass;
engine.rootContext()-&gt;setContextProperty(&quot;qmlclass&quot;, &amp;qclass);#include &lt;QGuiApplication&gt;
#include &lt;QQmlApplicationEngine&gt;
#include &lt;QQmlContext&gt;

#include &quot;qmlclass.h&quot;

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    qmlClass qclass;
    engine.rootContext()-&gt;setContextProperty(&quot;qmlclass&quot;, &amp;qclass);


    const QUrl url(QStringLiteral(&quot;qrc:/main.qml&quot;));
    QObject::connect(&amp;engine, &amp;QQmlApplicationEngine::objectCreated,
                     &amp;app, [url](QObject *obj, const QUrl &amp;objUrl) {
        if (!obj &amp;&amp; url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}现在就可以在 qml 中调用前面编写好的代码了：声明一个 label 用于显示 valueLabel{
    id: label
    x: 298
    y: 118
    text: &quot;Value: &quot;
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: getBtn.top
    anchors.bottomMargin: 5
    anchors.topMargin: 10
}在按钮的 onClicked 方法中调用我们编写的方法来更新 label 中的文本：    Button{
        id: getBtn
        text: &quot;getValue&quot;
        anchors.verticalCenterOffset: 0
        anchors.horizontalCenterOffset: 0
        width: 120
        height: 40
        anchors.centerIn: parent

        onClicked: {
            label.text = &quot;Value &quot; + qmlclass.getValue()
        }
    }实现效果：</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/06/10/155.html">
<title>[C++] 迭代器(iterator)介绍与使用</title>
<link>https://blog.atoery.cn/index.php/2025/06/10/155.html</link>
<dc:date>2025-06-10T14:21:30+08:00</dc:date>
<description>1.0 什么是迭代器？摘要迭代器是一种检查容器内元素并遍历元素的数据类型，通常用于对 C++ 中各种容器内元素的访问。指向某个位置的对象可能指向可读内存地址/对象用于以与数据布局无关的方式迭代容器元素还用于指定容器中的位置和范围 （用于插入、删除等）2.0 从标准容器中获取元素 begin() 和 end()可以从标准容器中获取一些特定位置的数据 (第一个元素、最后一个元素) 获取方式有两种：成员函数container.begin()container.end()独立函数std::begin(container)std::end(container)2.1 通过 * 来访问指定位置的元素基于 2.0 中给出的两种通过迭代器访问特定位置元素的方法，编写示例代码：std::vector&lt;int&gt; vint;

// init container
for (int i = 0; i &lt; 10; ++i){
    vint.push_back(i);
}

auto vint_begin = vint.begin();
auto vint_end = vint.end();

//auto vint_begin = std::begin(vint);
//auto vint_end = std::end(vint);

std::cout &lt;&lt; &quot;vector begin - &quot; &lt;&lt; *vint_begin &lt;&lt; std::endl;

return 0;通过 *vint_begin 可以访问指定位置的元素：*vint_begin*(vint_begin + 1)值得注意的是 *vint_end 迭代器仅用于位置指示器不能用于访问元素。vector -&gt;   |1|2|3|4|5|6|7| |
            |i|           |e|还有一些操作++i 向终点移动 1 (--i 向起点移动 1)vector -&gt;   |1|2|3|4|5|6|7| |
              |i|         |e|i += x 前进 x (i -= x 倒退 x)i += 4 / 前进 4
vector -&gt;   |1|2|3|4|5|6|7| |
                    |i|   |e|3.0 基于迭代器的循环通过上面对迭代器的介绍显而易见的简单用法：for (auto i = vint.begin(); i != vint.end(); ++i){
    std::cout &lt;&lt; *i &lt;&lt; std::endl;
}4.0 迭代范围4.1 从容器中擦除 erase()vint.erase(std::begin(vint) + 3, std::begin(vint) + 6);END 参考与引用[参考文章]C++ 迭代器简介 - hacking C++C++ 迭代器(iterator)超详解+实例演练</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/06/05/153.html">
<title>[C++] cmdline 轻量的命令行解析库</title>
<link>https://blog.atoery.cn/index.php/2025/06/05/153.html</link>
<dc:date>2025-06-05T17:44:00+08:00</dc:date>
<description>1.0 简述在使用 C++ 写命令行工具的时候，需要解析输入的参数，该库就是为这一解析工作提供一个方便快捷的方案。2.0 使用该项目仅包含一个头文件 include &quot;cmdline.h&quot;以下是一个简单示例#include &lt;iostream&gt;
#include &quot;cmdline/cmdline.h&quot;

int main(int argc, char *argv[]){
    /* 创建一个命令解析器 */
    cmdline::parser options;

    /*  1op -&gt; 长名称
     *  2op -&gt; 短名称 / 如果没有短名写 &#039;\0&#039;
     *  3op -&gt; 解释
     *  4op -&gt; 是否必填
     *  5op -&gt; 默认值，仅在 4op 为 false 的时候生效
     */
    options.add&lt;std::string&gt;(
        &quot;host&quot;,
        &#039;h&#039;,
        &quot;host name&quot;,
        true,
        &quot;&quot;);

    // range -&gt; 可以用于限制输入值范围
    options.add&lt;int&gt;(
        &quot;port&quot;,
        &#039;p&#039;,
        &quot;port number&quot;,
        false,
        80,
        cmdline::range(1, 65535));

    // oneof 限制参数的可选值
    options.add&lt;std::string&gt;(
        &quot;type&quot;,
        &#039;t&#039;,
        &quot;protocol type&quot;,
        false,
        &quot;http&quot;,
        cmdline::oneof&lt;std::string&gt;(&quot;http&quot;, &quot;https&quot;, &quot;ssh&quot;, &quot;ftp&quot;));

    // 可以定义 bool 值
    options.add(&quot;gzip&quot;, &#039;\0&#039;, &quot;gzip when transfer&quot;);

    /* 执行解析器 */
    options.parse_check(argc, argv);

    std::cout &lt;&lt; &quot;input type -&gt; &quot; &lt;&lt; options.get&lt;std::string&gt;(&quot;type&quot;) &lt;&lt; std::endl;
    std::cout &lt;&lt; &quot;input host -&gt; &quot; &lt;&lt; options.get&lt;std::string&gt;(&quot;host&quot;) &lt;&lt; std::endl;
    std::cout &lt;&lt; &quot;input port -&gt; &quot; &lt;&lt; options.get&lt;int&gt;(&quot;port&quot;) &lt;&lt; std::endl;

    // bool 值仅能通过 exist() 方法判断
    if (options.exist(&quot;gzip&quot;)){
        std::cout &lt;&lt; &quot;gzip enable&quot; &lt;&lt; std::endl;
    }else{
        std::cout &lt;&lt; &quot;gzip disable&quot; &lt;&lt; std::endl;
    }


    return 0;
}传入定义好的参数即可看到效果可以输出帮助消息END 参考与引用[参考文章]【C++】cmdline —— 轻量级的C++命令行解析库 </description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/05/25/149.html">
<title>[C++] 函数高级用法</title>
<link>https://blog.atoery.cn/index.php/2025/05/25/149.html</link>
<dc:date>2025-05-25T18:56:03+08:00</dc:date>
<description>1.0 函数的默认参数函数的型参列表中，型参可以有默认值：返回值类型 函数名 (型参 = 默认值)int summation(int a = 10, int b = 20){
  return a + b;
}在使用函数的默认参数时有以下几点需要注意：如果函数中的某一个位置已经有了默认参数，则此后所有型参都必须有默认参数：int summation (int a, int b = 10, int c) 这样是不合法的。如果函数声明中有默认参数，那么函数实现中必须保持一致。2.0 函数的占位参数为函数提供占位参数，占位参数只有参数类型声明而没有参数名声明。int func(int a, int b, int){
  return a + b;
}一般情况下函数体内部无法使用占位函数，在 C++ 中使用占位参数可以方便后续程序拓展&兼容 C 中可能出现的不规范写法。3.0 函数重载函数重载使函数名可以相同，提高复用性。函数重载需要满足以下几个条件：同一个作用域下函数名相同函数参数类型、个数不同、顺序不同函数的返回值不可以成为函数重载的满足条件void func(int a){}

void func(const int a){}根据传入参数的数据类型的不同调用到的参数不同。</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/05/12/142.html">
<title>[Markdown] 简介及语法速查表</title>
<link>https://blog.atoery.cn/index.php/2025/05/12/142.html</link>
<dc:date>2025-05-12T16:03:00+08:00</dc:date>
<description>1.0 简介Markdown是一种轻量级的标记语言，可用于在纯文本中添加格式化元素。Markdown由John Gruber于2004年创建，如今是世界上最受欢迎的标记语言之一。2.0 专注于文字内容。纯文本，易于书写，可以方便的纳入版本控制。语法简单，学习成本低。Markdown与Word类编辑器不同。在Word之类的应用程序中，通过点击按钮设置相关的语法和格式，并且更改立即可见。而Markdown与此不同，创建了Markdown格式的文件，在文本中添加Markdown语法，用于指示哪些单词和短语看起来有所不同。3.0 Markdown 的优势Markdown无处不在，主流的代码托管平台：Github、GitLab、Gitee等均支持Markdown语法，很多开源项目的README、开发文档、帮助文档、Wiki等都是用Markdown写作。Markdown是纯文本，可移植的，几乎可以使用任何文本编辑器打开Markdown文件。Markdown是独立于平台的，任何操作系统和设备上均可以使用Markdown格式的文本。Markdown创建的文件读取，不会受到软件、系统更新的迭代影响，即使你无法读取Markdown的格式，依然可以通过阅读纯文本的方式获取Markdown中的信息。4.0 Markdown 的作用Markdown 是做笔记、为网站创建内容、生成可打印文档的快速，简便的方法。学习Markdown语法并不需要很长时间，一旦你学会了使用方法，就可以在几乎任何地方使用。5.0 语法速查表5.1 基本语法元素语法标题    Heading# H1  ## H2  ### H3粗体    Blod Blod text 斜体    Ltalic italicized text 引用块  Blockquote&gt; blockquote有序列表  Ordered List1. First item  2.Second item  3.Third item无序列表  Unordered List First item   Second item  *Third item代码  Codecode`分割线  Horizontal Rule---链接  Link[title](https://www.example.com)图片  Image![alt text](image.jpg)5.2 拓展语法以下的元素为 Markdown 的拓展语法，并非所有 Markdown 解释器都支持这些元素。元素语法表格  Table`HeadHead &lt;br&gt; :--::--: &lt;br&gt; BodyBody`代码块  Code Block `   void Function ();   ` 脚注  FootnoteHere's a sentence with a footnote.[^1]  [^1]: This is the footnote.标题编号  Heading ID### My Great Heading {#custom-id}定义列表  Definition Listterm  : definition删除线  Strikethrough~~The world is flat~~任务列表  Task List- [x] Write the press release  - [ ] Update the website  - [ ] Contact the mediaEND 参考与引用Markdown 官方教程</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/05/12/141.html">
<title>[RAID入门] 各类 RAID 阵列的优缺点及数据安全性分析</title>
<link>https://blog.atoery.cn/index.php/2025/05/12/141.html</link>
<dc:date>2025-05-12T15:58:54+08:00</dc:date>
<description>1.0 RAID 概述在介绍 RAID 的历史之前，先一句话描述一下什么是 RAID 存储：独立磁盘冗余阵列（RAID）是一种存储技术，通过将复数个存储设备，例如硬盘驱动器（HDD）或固态硬盘（SSD）合并成一个协调的存储单元或阵列，从而获得一些单块存储介质无法轻易达到的优势。1.1 RAID 的历史1988 年，美国加州大学伯克利分校的 D.A.Patterson 教授等首次在论文中提出了 RAID 的概念；廉价冗余磁盘阵列 (Redundant Array of Indexpensive Disks) 由于当时大容量存储设备价格比较昂贵，RAID 的思想就是用多个容量较小、相对廉价的存储设备进行组合，从而得到昂贵大容量存储设备相同的容量、性能等。随着时代的发展存储设备早已不再昂贵，因此 RIAD 也在发展中变为了用于为现代存储设备提供冗余的一种解决方案，即独立磁盘冗余阵列。根据存储设备组成方式和性能的不同，RAID 分为很多类别：RAID0、RAID1、RAID5 等。从实现角度来看 RAID 主要分为软 RAID 和 硬 RAID 以及软硬混合 RAID 三种；顾名思义，软 RAID 所有功能均有操作系统和 CPU 来完成，没有独立的 RAID 控制 / 处理芯片和 I/O 处理芯片，效率自然最低。硬 RAID 配备了专门的 RAID 控制 / 处理芯片和 I/O 处理芯片以及阵列缓冲，不占用 CPU 资源，但成本很高。软硬混合 RAID 具备 RAID 控制 / 处理芯片，但缺乏 I/O 处理芯片，需要 CPU 和驱动程序来完成，性能和成本 在软 RAID 和硬 RAID 之间。2.0 RAID 0[原理]RAID 0使用数据条带化（striping）的方式将数据分散存储在多个磁盘驱动器上，而不进行冗余备份。数据被分成固定大小的块，并依次存储在每个磁盘上。例如，如果有两个驱动器（驱动器A和驱动器B），一块数据的第一个部分存储在驱动器A上，第二个部分存储在驱动器B上，以此类推。这种条带化的方式可以同时从多个驱动器读取或写入数据，从而提高系统的性能。[适用场景&优缺点]RAID 0 具有高性能，即通过数据条带化和并行读写操作，使得 RAID0 可以提供更快的数据传输速度并且成本低廉，因为 RAID0 并不需要像其余 RAID 方式一样需要空间进行冗余备份，所有的空间均可以用于存储相同成本下可用存储空间相对较多；但是如上文所言由于数据条带化和没有冗余存储空间，导致了 RAID0 最为致命的问题就是一旦任意存储设备损坏则阵列上的所有数据都将受到影响。因此 RAID0 针对视频编辑&处理等对数据传输速度要求并且不关心数据冗余的场景中适合适用。3.0 RAID 1[原理][适用场景&优缺点]END 参考与引用完全图解RAID存储技术：RAID 0、1、5、6、10、50、60RAID技术全解图解-RAID0、RAID1、RAID5、RAID100</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/04/17/93.html">
<title>Linux 串口通讯工具 minicom 的基本使用</title>
<link>https://blog.atoery.cn/index.php/2025/04/17/93.html</link>
<dc:date>2025-04-17T20:49:29+08:00</dc:date>
<description>1.0 安装 minicom 工具本文使用环境为 Debian12&GNOM43.9 故可以使用 apt 安装。$ sudo apt install minicom2.0 快速使用通过 minicom -D &lt;port_device&gt; -b &lt;baud_rate&gt; 即可快速连接至串口。按下键盘会直接发送数据（默认不显示输入的内容）按下 ctrl + A 后按下 X 退出程序。使用 ls /dev/tty* 即可查询可用串口。3.0 常用命令&参数在使用过程中 ctrl + A Z 打开帮助。常用的命令如下 ctrl + A +X：退出程序。W：开启&关闭自动换行。E：开启&关闭输入现实。C：清屏。在调用 minicom 时可以传入一下参数调整程序运行效果。-D：指定串口设备-b：设置波特率，默认 115200-w：启用自动换行-H：使用 HEX 方式显示接收到的数据-s：设置 minicomEND 参考 & 声明[参考]CSDN:Linux笔记：串口通讯工具minicom基础使用</description>
</item>
<item rdf:about="https://blog.atoery.cn/index.php/2025/03/20/70.html">
<title>[编程规范] 代码中的特殊注释</title>
<link>https://blog.atoery.cn/index.php/2025/03/20/70.html</link>
<dc:date>2025-03-20T16:06:00+08:00</dc:date>
<description>TODO：待办，备忘；代表这里有待完成的工作。FIXME：fixme（修理我）代表这里有需要修改的代码，或存在错误。XXX：代表这里的代码功能正常但是实现方法需要修改。HACK：代表此处代码根据需求进行调整。</description>
</item>
</rdf:RDF>