C++项目 —— 基于多设计模式下的同步异步日志系统(3)(日志器类)

news2025/5/14 18:12:21

C++项目 —— 基于多设计模式下的同步&异步日志系统(3)(日志器类)

  • 整体思想设计
  • 日志消息的构造
    • C语言式的不定参
      • 函数的作用
      • 函数的具体实现逻辑
        • 1. 日志等级检查
        • 2. 初始化可变参数列表
        • 3. 格式化日志消息
        • 4. 释放参数列表
        • 5. 序列化和输出日志
      • 函数的关键点总结
  • vasprintf
    • `vasprintf` 函数详解
    • 函数原型
    • 参数说明
    • 返回值
    • 关键特性
    • 使用示例
    • 内存管理注意事项
    • 平台兼容性
    • 与相关函数的对比
    • C++ 中的替代方案
    • 总结
  • 同步日志器
  • 扩充
    • using的用法
      • 1. 类型别名(Type Aliases)
      • 2. 命名空间引入(Namespace Directives)
      • 3. 继承中的用法
      • 4. 类型转换(Type Traits)
      • 5. 模板编程中的依赖类型
      • 对比 `typedef` 与 `using`
      • 最佳实践建议

我们之前的两次博客,已经把日志器模块的一些基本组成要素已经搭建完成了,包括基本工具类的创建,格式化消息类,日志落地方向类的编写也已经完成了。如果还有小伙伴不熟悉这些,可以先看看我的前两次博客:

https://blog.csdn.net/qq_67693066/article/details/147190387?spm=1011.2415.3001.5331

https://blog.csdn.net/qq_67693066/article/details/147162921?spm=1011.2415.3001.5331

我们今天的任务主要是将前面的我们所编写的类组合起来,组合成一个实实在在的日志器,同时日志器也分两个方向,分为同步日志器和异步日志器。

整体思想设计

跟我们之前设计日志落地方向的时候一样,我们也是设计一个基类日志器,然后继承分化成两类不同的日志器,一个是同步日志器,另一个是异步日志器

#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "utils.hpp"
#include "message.hpp"
#include "utils.hpp"
#include "level.hpp"
#include "sink.hpp"
#include <atomic>
#include <mutex>
#include <iostream>
#include <memory>
#include <ctime>
#include <vector>
#include <cassert>
#include <sstream>

namespace logs
{
    class BaseLogger
    {
    public:
        BaseLogger(const std::string& logger_name,
        Loglevel::value level,
        Formetter::ptr &formetter,
        std::vector<BaseSink::ptr> &sinks)    
            :_logger_name(logger_name)
            ,_level(level)
            ,_formetter(formetter)
            ,_sinks(sinks.begin(), sinks.end())
        {

        }
        

    protected:
        std::mutex _mutex; //锁
        std::string logger_name; //日志器名称
        std::atomic<logs::Loglevel> _level; //日志等级
        Formetter::ptr _formetter; //格式化消息指针
        std::vector<BaseSink::ptr> _sink; //落地方向   
    };

    class SyncLogger : protected BaseLogger
    {

    };

    class AsyncLogger : protected BaseLogger
    {

    };
}



#endif

这样我们就把大概的架子搭好了,我们这时候先把转化日志消息的这个功能做好:

 void serialize(Loglevel::value level,const std::string& file_name,
 size_t line,const std::string& logger_name,char* str)
 {
     //1.构造msg对象
     logs::logMsg msg(level,file_name,line,logger_name,str);
     //2.利用Formetter进行消息格式化
     std::stringstream ss;
     _formetter->format(ss,str);
     //3.落地方向的输出
     log(ss.str().c_str(), ss.str().size());
 }

日志消息的构造

我们日志器最重要的一个部分就是对不同日志等级消息进行输出,所以我们要对不同的日志等级设计接口,使他们能够输出对应自身的日志消息:

我们拿debug来举例,我们要设计对应的接口,使得对应消息进入debug接口能够被格式化组织出来,按照我们想要的方向进行输出:

  /*完成构造日志消息对象过程并进行格式化,得到格式化后的日志消息字符串---然后进行输出*/
  void debug()
  {
      
  }

这里我们要考虑一个问题,就是我们传入的消息可能不是固定的,参数可能不是固定的,所以我们的接口参数个数就不能写死。这里我们我们要介绍一下C语言风格的不定参:

C语言式的不定参

在C语言中,函数可以接受不定数量的参数,这称为可变参数函数(variadic functions)。标准库中的printf()scanf()就是典型的例子。我们可以举一个简单的例子:

double average(int count, ...)
{
    va_list ap;  声明参数列表变量

    int j = 0;
    double sum = 0;

    va_start(ap,count); //count是最后一个参数

    for (j = 0; j < count; j++)
    {
        sum += va_arg(ap, int); //依次获得int类型的参数
    }

 	va_end(ap);

    return sum / count;
}

这个average函数可以接受若干参数,像这里,我就声明接受5个参数。

我们可以把这样的思想用到我们日志器不同等级日志打印上:

       void debug(const std::string& file,size_t line,const std::string fmt,...)
        {
            //如果限制输出的日志等级比debug高,则直接返回
            if(Loglevel::value::DEBUG < _limt_level)
            {
                return;
            }

            //声明参数列表变量
            va_list ap;
            va_start(ap,fmt); // 初始化fmt是最后一个固定的参数

            char* res; //声明缓冲区

            int ret = vasprintf(&res,fmt.c_str(),ap);

            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }

            va_end(ap); //释放参数列表变量
            serialize(Loglevel::value::DEBUG, file, line, res);
            free(res);
        }

这个函数是一个日志输出工具的一部分,用于生成和输出格式化的调试日志消息。以下是对其作用的详细解释:


函数的作用

这个 debug 函数的主要目的是:

  1. 判断是否需要输出日志:根据当前的日志等级限制 _limt_level,决定是否需要输出 DEBUG 级别的日志消息。
  2. 构造日志消息:通过接收一个格式化字符串(类似于 printf 的方式)和可变参数列表,生成最终的日志消息内容。
  3. 格式化日志消息:将传入的格式化字符串和参数组合成一个完整的日志消息字符串。
  4. 序列化和输出日志:将日志消息传递给另一个函数(如 serialize),进行进一步处理或输出。

函数的具体实现逻辑

1. 日志等级检查
if(Loglevel::value::DEBUG < _limt_level)
{
    return;
}
  • 这部分代码首先检查当前的日志等级限制 _limt_level 是否允许输出 DEBUG 级别的日志。
  • 如果 _limt_level 的值比 DEBUG 级别更高(例如设置为 INFOERROR),则直接返回,不执行后续的日志记录操作。这样可以避免不必要的日志生成和输出。
2. 初始化可变参数列表
va_list ap;
va_start(ap, fmt);
  • 使用 va_list 类型变量 ap 来存储可变参数列表。
  • va_start 初始化 ap,并指定 fmt 是最后一个固定的参数(即可变参数列表从 fmt 后面开始)。
3. 格式化日志消息
char* res;
int ret = vasprintf(&res, fmt.c_str(), ap);
  • 使用 vasprintf 函数将格式化字符串 fmt 和可变参数列表 ap 转换为一个动态分配的字符串 res
  • vasprintf 是 C 标准库中的一个扩展函数,它会根据格式化字符串和参数生成结果字符串,并自动分配足够的内存。
  • 如果 vasprintf 返回 -1,表示格式化失败,程序会输出错误信息并直接返回。
4. 释放参数列表
va_end(ap);
  • 在完成对可变参数列表的操作后,调用 va_end 释放 ap,以确保资源被正确清理。
5. 序列化和输出日志
serialize(Loglevel::value::DEBUG, file, line, res);
free(res);
  • 调用 serialize 函数,将日志等级(DEBUG)、文件名(file)、行号(line)以及格式化后的日志消息(res)传递给它。
  • serialize 函数可能会将这些信息进一步处理(例如添加时间戳、线程 ID 等),然后输出到文件、控制台或其他目标。
  • 最后,使用 free(res) 释放由 vasprintf 分配的内存,避免内存泄漏。

函数的关键点总结

  1. 日志等级过滤

    • 通过 _limt_level 控制日志输出的行为,避免输出不必要的日志消息,提高性能。
  2. 格式化日志消息

    • 使用 vasprintf 动态生成格式化的日志消息,支持类似 printf 的占位符语法(如 %s, %d 等)。
  3. 资源管理

    • 使用 va_startva_end 管理可变参数列表。
    • 使用 free 释放动态分配的内存,防止内存泄漏。
  4. 日志输出

    • 将日志消息传递给 serialize 函数进行进一步处理和输出。

vasprintf

vasprintf 函数详解

vasprintf 是一个非常有用的 C 库函数(GNU 扩展),用于安全地格式化字符串并自动分配内存。下面我将全面介绍这个函数。

函数原型

int vasprintf(char **strp, const char *format, va_list ap);

参数说明

  • strp:指向字符指针的指针,函数会将分配的缓冲区地址存储在这里
  • format:格式化字符串(与 printf 风格相同)
  • ap:可变参数列表(通过 va_start 初始化)

返回值

  • 成功时:返回写入的字符数(不包括结尾的 null 字符)
  • 失败时:返回 -1,并且不修改 *strp

关键特性

  1. 自动内存分配

    • 函数会根据需要自动分配足够大的内存
    • 调用者负责后续释放这块内存
  2. 安全性

    • 避免了缓冲区溢出风险
    • 不需要预先猜测缓冲区大小
  3. vsprintf 的关系

    • 类似于 vsprintf,但自动处理内存分配
    • 类似于 asprintf 的可变参数版本

使用示例

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

void log_message(const char *format, ...) {
    va_list args;
    va_start(args, format);
    
    char *buffer = NULL;
    int len = vasprintf(&buffer, format, args);
    va_end(args);
    
    if (len != -1) {
        printf("Formatted message: %s\n", buffer);
        free(buffer);  // 必须释放分配的内存
    } else {
        printf("Formatting failed\n");
    }
}

int main() {
    log_message("Current value: %d, name: %s", 42, "example");
    return 0;
}

内存管理注意事项

  1. 必须释放内存

    char *str = NULL;
    vasprintf(&str, ...);
    // 使用str...
    free(str);  // 必须调用free释放
    
  2. 错误处理

    • 总是检查返回值是否为-1
    • 失败时不要尝试使用或释放缓冲区

平台兼容性

  1. 支持平台

    • GNU/Linux 系统
    • 大多数 Unix-like 系统
  2. Windows 替代方案

    • _vasprintf(微软实现)
    • 或使用 _vscprintf + malloc + vsprintf 组合

与相关函数的对比

函数自动分配内存安全性需要缓冲区大小参数
vsprintf不安全 (可能溢出)
vsnprintf安全
vasprintf安全

C++ 中的替代方案

现代 C++ 可以使用以下替代方案:

  1. C++20 std::format

    #include <format>
    std::string msg = std::format("Value: {}", 42);
    
  2. fmt

    #include <fmt/core.h>
    std::string msg = fmt::format("Value: {}", 42);
    
  3. 字符串流

    #include <sstream>
    std::ostringstream oss;
    oss << "Value: " << 42;
    std::string msg = oss.str();
    

总结

vasprintf 是一个方便且安全的字符串格式化函数,特别适合需要动态构建字符串的场景。它的主要优点是自动内存管理和避免缓冲区溢出,但需要注意正确释放分配的内存和考虑跨平台兼容性。

以上的阐述足够让大家具体了解这个debug函数的具体用法和语法细节,其他等级的日志输出函数我们如法炮制就行了:

给大家贴上完整代码:

#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "utils.hpp"
#include "message.hpp"
#include "utils.hpp"
#include "level.hpp"
#include "sink.hpp"
#include <atomic>
#include <mutex>
#include <iostream>
#include <memory>
#include <ctime>
#include <vector>
#include <cassert>
#include <sstream>
#include<stdarg.h>

namespace logs
{
    class BaseLogger
    {
    public:
        using ptr = std::shared_ptr<BaseLogger>;
        BaseLogger(const std::string& logger_name,
        Loglevel::value limt_level,
        Formetter::ptr &formetter,
        std::vector<BaseSink::ptr> &sinks)    
            :_logger_name(logger_name)
            ,_limt_level(limt_level)
            ,_formetter(formetter)
            ,_sinks(sinks.begin(), sinks.end())
        {

        }

        const std::string& get_logger_name()
        {
            return _logger_name;
        }


        /*完成构造日志消息对象过程并进行格式化,得到格式化后的日志消息字符串---然后进行输出*/
        void debug(const std::string& file,size_t line,const std::string fmt,...)
        {
            //如果限制输出的日志等级比debug高,则直接返回
            if(Loglevel::value::DEBUG < _limt_level)
            {
                return;
            }

            //声明参数列表变量
            va_list ap;
            va_start(ap,fmt); // 初始化fmt是最后一个固定的参数

            char* res; //声明缓冲区

            int ret = vasprintf(&res,fmt.c_str(),ap);

            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }

            va_end(ap); //释放参数列表变量
            serialize(Loglevel::value::DEBUG, file, line, res);
            free(res);
        }

        void info(const std::string& file,size_t line,const std::string fmt,...)
        {
            //如果限制输出的日志等级比debug高,则直接返回
            if(Loglevel::value::INFO < _limt_level)
            {
                return;
            }

            //声明参数列表变量
            va_list ap;
            va_start(ap,fmt); // 初始化fmt是最后一个固定的参数

            char* res; //声明缓冲区

            int ret = vasprintf(&res,fmt.c_str(),ap);

            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }

            va_end(ap); //释放参数列表变量
            serialize(Loglevel::value::INFO, file, line, res);
            free(res);
        }

        void warn(const std::string& file,size_t line,const std::string fmt,...)
        {
            //如果限制输出的日志等级比debug高,则直接返回
            if(Loglevel::value::WARN < _limt_level)
            {
                return;
            }

            //声明参数列表变量
            va_list ap;
            va_start(ap,fmt); // 初始化fmt是最后一个固定的参数

            char* res; //声明缓冲区

            int ret = vasprintf(&res,fmt.c_str(),ap);

            if(ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }

            va_end(ap); //释放参数列表变量
            serialize(Loglevel::value::WARN, file, line, res);
            free(res);
        }

        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 1.通过传入的参数构造一个日志对象,进行日志的格式化,最终落地
            if (Loglevel::value::ERROR < _limt_level)
            {
                return;
            }
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);
            serialize(Loglevel::value::ERROR, file, line, res);

            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 1.通过传入的参数构造一个日志对象,进行日志的格式化,最终落地
            if (Loglevel::value::FATAL < _limt_level)
            {
                return;
            }
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!\n";
                return;
            }
            va_end(ap);
            serialize(Loglevel::value::FATAL, file, line, res);

            free(res);
        }


    
    protected:
        void serialize(Loglevel::value level,const std::string& file_name,
        size_t line,char* str)
        {
            //1.构造msg对象
            logs::logMsg msg(level,file_name,line,_logger_name,str);
            //2.利用Formetter进行消息格式化
            std::stringstream ss;
            _formetter->format(ss,msg);
            //3.落地方向的输出
            log(ss.str().c_str(), ss.str().size());
        }
    
        /*抽象接口完成实际的落地输出---不同的日志器会有不同的落地方式*/
        virtual void log(const char *data, size_t len) = 0;
        
    protected:
        std::mutex _mutex; //锁
        std::string _logger_name; //日志器名称
        std::atomic<Loglevel::value> _limt_level; //日志等级
        Formetter::ptr _formetter; //格式化消息指针
        std::vector<BaseSink::ptr> _sinks; //落地方向   
    };

    class SyncLogger : public BaseLogger
    {

    };

    class AsyncLogger : public BaseLogger
    {

    };
}



#endif

同步日志器

同步日志器比较简单,我们继承基础的日志器然后把接口log实现一下就可以了:

    class SyncLogger : public BaseLogger
    {
        public:
            SyncLogger(const std::string& logger_name,
                Loglevel::value limt_level,
                Formetter::ptr &formetter,
                std::vector<BaseSink::ptr> &sinks)
            :BaseLogger(logger_name, limt_level, formetter, sinks) {}
        
        protected:
            void log(const char *data, size_t len)
            {
                //1.上锁
                std::unique_lock<std::mutex> _lock(std::mutex);

                if (_sinks.empty())
                return;

                for (auto &sink : _sinks)
                {
                    sink->log(data, len);
                }
            }
    };

我们可以来测试一下:

#include "utils.hpp"
#include "level.hpp"
#include "message.hpp"
#include "fometter.hpp"
#include "sink.hpp"
#include "logger.hpp"

int main()
{
    // 1. 创建 Formatter 对象(假设构造函数接受格式字符串)
    logs::Formetter formatter("abc[%d{%H:%M:%S}][%c]%T%m%n");

    // 2. 创建智能指针
    logs::Formetter::ptr fmt_ptr = std::make_shared<logs::Formetter>(formatter);

    auto st1 = logs::SinkFactory::create<logs::StdoutSink>();
    std::vector<logs::BaseSink::ptr> sinks = {st1};

    std::string logger_name = "synclogger";
    logs::BaseLogger::ptr logger(new logs::SyncLogger(logger_name, logs::Loglevel::value::DEBUG, fmt_ptr, sinks));

    logger->debug("main.cc", 53, "%s","格式化功能测试....");
}

在这里插入图片描述

扩充

using的用法

在 C++ 中,using 是一个多功能关键字,主要有以下几种用法:

1. 类型别名(Type Aliases)

  • 替代 typedef,更直观地定义类型别名
using IntPtr = int*;          // 等价于 typedef int* IntPtr;
using StringVector = std::vector<std::string>;

模板别名typedef 无法实现):

template<typename T>
using Vec = std::vector<T>;   // Vec<int> 等价于 std::vector<int>

2. 命名空间引入(Namespace Directives)

  • 引入整个命名空间(谨慎使用):
using namespace std;  // 引入 std 命名空间
  • 引入特定成员:
using std::cout;      // 只引入 cout

3. 继承中的用法

  • 引入基类成员(解决名称隐藏问题):
class Base {
public:
    void func() {}
};

class Derived : private Base {
public:
    using Base::func;  // 将基类的 func 引入到 public 区域
};
  • 继承构造函数(C++11 起):
class Derived : public Base {
public:
    using Base::Base;  // 继承基类的所有构造函数
};

4. 类型转换(Type Traits)

decltype 配合定义复杂类型:

using ResultType = decltype(a + b);  // 根据表达式推断类型

5. 模板编程中的依赖类型

指定模板依赖的类型名:

template<typename T>
class Widget {
    using ValueType = typename T::value_type;  // 明确 value_type 是类型
};

对比 typedefusing

特性typedefusing
语法直观性较晦涩更清晰(类似赋值)
支持模板别名❌ 不支持✅ 支持
可读性类型名在末尾类型名在左侧
函数指针别名可支持但语法复杂更简洁

函数指针别名示例

// typedef 写法
typedef void (*FuncPtr)(int, int);

// using 写法
using FuncPtr = void (*)(int, int);

最佳实践建议

  1. 优先使用 using(现代 C++ 推荐)
  2. 避免全局 using namespace(易引发命名冲突)
  3. 模板编程中必须用 usingtypedef 无法替代)
  4. 合理使用继承中的 using(解决重载或访问控制问题)

通过灵活运用 using,可以显著提升代码的可读性和可维护性。

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

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

相关文章

【数学建模】随机森林算法详解:原理、优缺点及应用

随机森林算法详解&#xff1a;原理、优缺点及应用 文章目录 随机森林算法详解&#xff1a;原理、优缺点及应用引言随机森林的基本原理随机森林算法步骤随机森林的优点随机森林的缺点随机森林的应用场景Python实现示例超参数调优结论参考文献 引言 随机森林是机器学习领域中一种…

蓝桥杯 19.合根植物

合根植物 原题目链接 题目描述 W 星球的一个种植园被分成 m n 个小格子&#xff08;东西方向 m 行&#xff0c;南北方向 n 列&#xff09;。每个格子里种了一株合根植物。 这种植物有个特点&#xff0c;它的根可能会沿着南北或东西方向伸展&#xff0c;从而与另一个格子的…

Linux环境MySQL出现无法启动的问题解决 [InnoDB] InnoDB initialization has started.

目录 起因 强制启用恢复模式 备份数据 起因 服务器重启了&#xff0c;然后服务器启动完成之后我发现MySQL程序没有启动&#xff0c;错误信息如下&#xff1a; 2025-04-19T12:46:47.648559Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2025-04-1…

高性能服务器配置经验指南1——刚配置好服务器应该做哪些事

文章目录 安装ubuntu安装必要软件设置用户远程连接安全问题ClamAV安装教程步骤 1&#xff1a;更新系统软件源步骤 2&#xff1a;升级系统&#xff08;可选但推荐&#xff09;步骤 3&#xff1a;安装 ClamAV步骤 4&#xff1a;更新病毒库步骤 5&#xff1a;验证安装ClamAV 常用命…

Centos7安装Jenkins(图文教程)

本章教程,主要记录在centos7安装部署Jenkins 的详细过程。 [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 一、基础环境安装 内存大小要求:256 MB 内存以上 硬盘大小要求:10 GB 及以上 安装基础java环境:Java 17 ( JRE 或者 JDK 都可…

【JAVA】十三、基础知识“接口”精细讲解!(二)(新手友好版~)

哈喽大家好呀qvq&#xff0c;这里是乎里陈&#xff0c;接口这一知识点博主分为三篇博客为大家进行讲解&#xff0c;今天为大家讲解第二篇java中实现多个接口&#xff0c;接口间的继承&#xff0c;抽象类和接口的区别知识点&#xff0c;更适合新手宝宝们阅读~更多内容持续更新中…

边缘计算盒子是什么?

边缘计算盒子是一种小型的硬件设备&#xff0c;通常集成了处理器、存储器和网络接口等关键组件&#xff0c;具备一定的计算能力和存储资源&#xff0c;并能够连接到网络。它与传统的云计算不同&#xff0c;数据处理和分析直接在设备本地完成&#xff0c;而不是上传到云端&#…

大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署--完结

大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署 1. ClickHouse与MySQL的区别2. 在群集的所有机器上安装ClickHouse服务端2.1. 在线安装clickhouse2.2. 离线安装clickhouse 3. ClickHouse Keeper/Zookeeper集群安装4. 在配置文件中设置集群配置5. 在每…

19Linux自带按键驱动程序的使用_csdn

1、自带按键驱动程序源码简析 2、自带按键驱动程序的使用 设备节点信息&#xff1a; gpio-keys {compatible "gpio-keys";pinctrl-names "default";pinctrl-0 <&key_pins_a>;autorepeat;key0 {label "GPIO Key L";linux,code &l…

用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置

原文链接&#xff1a;用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在银河麒麟操作系统的 LiveCD 或系统试用镜像环境下&#xff0c;如何查看原系统中电脑的 IP 地址与网络打印机 IP 地址的实用教程。在系统损坏…

.net core 项目快速接入Coze智能体-开箱即用-第2节

目录 一、Coze智能体的核心价值 二、开箱即用-效果如下 三 流程与交互设计 本节内容调用自有或第三方的服务 实现语音转文字 四&#xff1a;代码实现----自行实现 STT 【语音转文字】 五&#xff1a;代码实现--调用字节API实现语音转文字 .net core 项目快速接入Coze智能…

win10中打开python的交互模式

不是输入python3&#xff0c;输入python&#xff0c;不知道和安装python版本有没有关系。做个简单记录&#xff0c;不想记笔记了

时序逻辑电路——序列检测器

文章目录 一、序列检测二、牛客真题1. 输入序列连续的序列检测&#xff08;输入连续、重叠、不含无关项、串行输入&#xff09;写法一&#xff1a;移位寄存器写法二&#xff1a;Moore状态机写法三&#xff1a;Mealy状态机 一、序列检测 序列检测器指的就是将一个指定的序列&…

TikTok X-Gnarly纯算分享

TK核心签名校验&#xff1a;X-Bougs 比较简单 X-Gnarly已经替代了_signature参数&#xff08;不好校验数据&#xff09; 主要围绕query body ua进行加密验证 伴随着时间戳 浏览器指纹 随机值 特征值 秘钥转换 自写算法 魔改base64编码 与X-bougs 长a-Bougs流程一致。 视频…

LPDDR5协议新增特性

文章目录 一、BL/n_min参数含义二、RDQS_t/RDQS_c引脚的功能三、DMI引脚的功能3.1、Write操作时的Data Mask数据掩码操作3.2、Write/Read操作时的Data Bus Inversion操作四、CAS命令针对WR/RD/Mask WR命令的低功耗组合配置4.1、Write/Read操作前的WCK2CK同步操作4.2、Write/Rea…

【深度学习】#8 循环神经网络

主要参考学习资料&#xff1a; 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李牧学AI 为了进一步提高长线学习的效率&#xff0c;该系列从本章开始将舍弃原始教材的代码部分&#xff0c;专注于理论和思维的提炼&#xff0c;系列名也改为“深度学习…

Linux学习——UDP

编程的整体框架 bind&#xff1a;绑定服务器&#xff1a;TCP地址和端口号 receivefrom()&#xff1a;阻塞等待客户端数据 sendto():指定服务器的IP地址和端口号&#xff0c;要发送的数据 无连接尽力传输&#xff0c;UDP:是不可靠传输 实时的音视频传输&#x…

leetcode205.同构字符串

两个哈希表存储字符的映射关系&#xff0c;如果前面字符的映射关系和后面的不一样则返回false class Solution {public boolean isIsomorphic(String s, String t) {if (s.length() ! t.length()) {return false;}int length s.length();Map<Character, Character> s2…

软考软件设计师考试情况与大纲概述

文章目录 **一、考试科目与形式****二、考试大纲与核心知识点****科目1&#xff1a;计算机与软件工程知识****科目2&#xff1a;软件设计** **三、备考建议****四、参考资料** 这是一个系列文章的开篇 本文对2025年软考软件设计师考试的大纲及核心内容进行了整理&#xff0c;并…

Redis—内存淘汰策略

记&#xff1a;全体LRU&#xff0c;ttl LRU&#xff0c;全体LFU&#xff0c;ttl LFU&#xff0c;全体随机&#xff0c;ttl随机&#xff0c;最快过期&#xff0c;不淘汰&#xff08;八种&#xff09; Redis 实现的是一种近似 LRU 算法&#xff0c;目的是为了更好的节约内存&…