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

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

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,可以显著提升代码的可读性和可维护性。

相关文章:

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

C项目 —— 基于多设计模式下的同步&异步日志系统&#xff08;3&#xff09;&#xff08;日志器类&#xff09; 整体思想设计日志消息的构造C语言式的不定参函数的作用函数的具体实现逻辑1. 日志等级检查2. 初始化可变参数列表3. 格式化日志消息4. 释放参数列表5. 序列化和…...

2025/4/19 数据库的流程控制函数

单行函数_流程函数 要点: 流程处理函数可以根据不同的条件 执行不同的处理流程 可以在SQL语句中实现不同的条件选择,MySQL中的流程处理函数主要包括if() ifnull() 和 case() 函数 多行函数_聚合函数 和单行函数的区别: 单行函数是作用在每一行 最终结果可能是多行结果 多行…...

代码随想录打家劫舍+树形DP入门

动态规划part07 198.打家劫舍 视频讲解&#xff1a;https://www.bilibili.com/video/BV1Te411N7SX https://programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html dp数组&#xff1a;进入房屋i能够偷得得最大金额dp[i]递推公式&#xff1a;根据不相邻原则…...

Http基础

目录 定义 一、请求部分&#xff08;Request&#xff09; 1. 请求行&#xff08;Request Line&#xff09; 常见请求方法&#xff1a; 2. 请求头&#xff08;Request Headers&#xff09; 3. 请求体&#xff08;Request Body&#xff09; 二、响应部分&#xff08;Respo…...

【Unity】bug记录——部分物体突然不受animator控制

博主烘焙完灯光后突然发现有的物体的动画失效了&#xff0c;不会动&#xff0c;测试发现是因为勾了static&#xff08;但是有些勾了static的物体就没事&#xff09;&#xff0c;修改static为Contribute GI Static&#xff08;只针对光照静态&#xff09;就行...

Zephyr、FreeRTOS、RT-Thread 邮箱(Mailbox)对比分析

一、核心特性对比 特性ZephyrFreeRTOSRT-Thread消息类型支持指针或4字节数据&#xff08;依赖架构&#xff09;仅支持指针传递支持任意数据类型&#xff08;需指定消息长度&#xff09;容量固定容量&#xff08;静态初始化配置&#xff09;动态容量&#xff08;基于队列长度&a…...

xilinx fpga中pll与mmcm的区别

Xilinx中的PLL&#xff08;锁相环&#xff09;和MMCM&#xff08;混合模式时钟管理器&#xff09;都是用于时钟管理的关键组件&#xff0c;但它们之间存在一些显著的区别。以下是对两者的详细比较&#xff1a; 1. 功能特性 PLL&#xff08;锁相环&#xff09;&#xff1a; 主…...

Python语法系列博客 · 第8期[特殊字符] Lambda函数与高阶函数:函数式编程初体验

上一期小练习解答&#xff08;第7期回顾&#xff09; ✅ 练习1&#xff1a;找出1~100中能被3或5整除的数 result [x for x in range(1, 101) if x % 3 0 or x % 5 0]✅ 练习2&#xff1a;生成字符串长度字典 words ["apple", "banana", "grape…...

黑马商城(五)微服务保护和分布式事务

一、雪崩问题 二、雪崩-解决方案&#xff08;服务保护方案&#xff09; 请求限流&#xff1a; 线程隔离&#xff1a; 服务熔断&#xff1a; 服务保护组件&#xff1a; 三、Sentinel 引入依赖&#xff1a; <!--sentinel--> <dependency><groupId>com.aliba…...

Java 编译与反编译深度解析

Java 编译与反编译深度解析 1. 编译过程详解 (1) 完整编译流程 .java 文件 → 词法分析 → 语法分析 → 语义分析 → 字节码生成 → .class 文件│ │ │ │↓ ↓ ↓ ↓识别关键字 生成抽象语法树 类型…...

Java集合框架中的List、Map、Set详解

在Java开发中&#xff0c;集合框架是处理数据时不可或缺的工具之一。今天&#xff0c;我们来深入了解一下Java集合框架中的List、Map和Set&#xff0c;并探讨它们的常见方法操作。 目录 一、List集合 1.1 List集合介绍 1.2 List集合的常见方法 添加元素 获取元素 修改元素…...

国产的 Java Solon v3.2.0 发布(央企信创的优选)

Solon 框架&#xff01; Solon 是新一代&#xff0c;Java 企业级应用开发框架。从零开始构建&#xff08;No Java-EE&#xff09;&#xff0c;有灵活的接口规范与开放生态。采用商用友好的 Apache 2.0 开源协议&#xff0c;是“杭州无耳科技有限公司”开源的根级项目&#xff…...

机器学习决策树

一、何为决策树 决策树&#xff08;Decision Tree&#xff09;是一种分类和回归方法&#xff0c;是基于各种情况发生的所需条件构成决策树&#xff0c;以实现期望最大化的一种图解法。由于这种决策分支画成图形很像一棵树的枝干&#xff0c;故称决策树。它的运行机制非常通俗易…...

Java集合及面试题学习

知识来源沉默王二、小林coding、javaguide 1、ArrayList list.add("66") list.get(2) list.remove(1) list.set(1,"55") List<String> listnew ArrayList<>(); 底层是动态数组 添加元素流程&#xff1a;判断是否扩容&#xf…...

【内置函数】84个Python内置函数全整理

Python 内置函数全集&#xff08;完整分类 参数详解 示例&#xff09; 文章目录 Python 内置函数全集&#xff08;完整分类 参数详解 示例&#xff09;一、数值与数学函数abs(x)divmod(a, b)pow(x, y, modNone)round(number[, ndigits])sum(iterable, /, start0)hash(obj) …...

【LeetCode 热题 100】双指针 系列

&#x1f4c1;283. 移动零 对于该题目&#xff0c;需要注意的是两个地方&#xff0c;一是保持非零元素的相对顺序&#xff0c;以及O(1)的空间复杂度。 采用双指针的思路&#xff0c;将数组划分成3个区间,。 [0 , left]&#xff1a;该区间内元素全是非零元素。 [left1 , right…...

实现批量图片文字识别(python+flask+EasyOCR)

话不多说,向上效果图 1)先说框架版本 为什么要先说框架版本呢,因为我在各种版本中尝试了两天,总算确定了如下版本适合我,至于其他的版本,各位自己去尝试 python 3.9.7 EasyOCR 1.7.2 flask 3.0.3 2)执行操作效果图 2.1)多选文件 2.2)图片预览 2.3)提取选中文件 2.4)提取所有文…...

[Swift]pod install成功后运行项目报错问题error: Sandbox: bash(84760) deny(1)

操作&#xff1a; platform :ios, 14.0target ZKMKAPP do# Comment the next line if you dont want to use dynamic frameworksuse_frameworks!# Pods for ZKMKAPPpod Moyaend pod install成功后运行报错 报错&#xff1a; error: Sandbox: bash(84760) deny(1) file-writ…...

文档内容提取以及合成

如何从10个左右的docx文档中抽取内容&#xff0c;生成新的文档&#xff0c;抽取内容包括源文档的文字内容、图片、表格、公式等&#xff0c;以及目标文档的样式排版、字体、格式&#xff0c;还有目标文档的语言风格、用词规范、文法习惯等等。这是一个相当复杂的需求&#xff0…...

[Windows] Wireshark 网络抓包工具 v4.4.6

[Windows] Wireshark 网络抓包工具 链接&#xff1a;https://pan.xunlei.com/s/VOODTZ7Lm2gsNLoFNcOIqflzA1?pwdf3ea# 软件说明Wireshark&#xff08;前称Ethereal&#xff09;是一款免费开源的网络嗅探抓包东西&#xff0c;世界上最流行的网络协议剖析器&#xff01;网络封…...

在Ubuntu中安装hadoop的详细过程

在Ubuntu中安装hadoop的详细过程 请自行安装Ubuntu系统&#xff08;可参考&#xff1a;在VMWare中安装Linux虚拟机Ubuntu&#xff09; 一、创建hadoop用户 如果在安装 Ubuntu 的时候不是用的 “hadoop” 用户&#xff0c;这时需要增加一个名为 hadoop 的用户。 首先打开终端…...

NOIP2017提高组.列队

目录 *数据结构模板题目算法标签: 模拟, 线段树, 线段树动态开点, 树状数组, 平衡树思路*前置代码完整注释代码精简注释代码 *数据结构模板 题目 530. 列队 算法标签: 模拟, 线段树, 线段树动态开点, 树状数组, 平衡树 思路 首先考虑简单情况, 如果只有一行, 删除一个位置…...

PSN港服跳过生日找回密码(需要英语对话,需要注册的id)

登陆这个网站 https://www.playstation.com/en-hk/support/contact-us/?categoryAcc&subCategorypw 随便输入点名字 firstname 跟lastname 勾选&#xff0c;然后打开机器人聊天 然后按照提示输入邮箱跟id&#xff0c;输入正确之后会分配真人客服 真人客服会要求提供第一次…...

服务治理-服务注册

一个服务在真实项目部署的时候&#xff0c;如果压力较大&#xff0c;会做多实例部署。 在IDEA里面做多实例部署的话&#xff0c;只需要配置多个启动项。...

Jinja2模板引擎SSTI漏洞

1. 引入 再研究大模型相关应用的漏洞CVE-2025-25362时&#xff08;参考1&#xff09;&#xff0c;看到作者给了比较详细的分析&#xff08;参考2&#xff09;。下面对这个漏洞做个介绍。 2. 漏洞类型 这个漏洞属于CWE-1336&#xff0c;它主要关注在使用模板引擎进行脚本化处…...

STM32单片机教程:从零开始打造智能天气时钟

STM32单片机教程&#xff1a;从零开始打造智能天气时钟 大家好&#xff01;今天我想为大家详细介绍一下我们的STM32课程&#xff0c;以及如何从零基础逐步掌握单片机开发技能&#xff0c;最终实现一个完整的智能天气时钟项目。 课程面向人群 本课程主要面向那些已经通过野火…...

c++_csp-j算法 (1)

DFS搜索(深度优先搜索) 讲解 第一部分&#xff1a;DFS搜索算法简介 深度优先搜索&#xff08;Depth-First Search&#xff0c;DFS&#xff09;是一种常用的图搜索算法&#xff0c;用于遍历或搜索图或树的所有节点。DFS算法的核心思想是尽可能深地搜索图的分支&#xff0c;直…...

word选中所有的表格——宏

Sub 选中所有表格()Dim aTable As TableApplication.ScreenUpdating FalseActiveDocument.DeleteAllEditableRanges wdEditorEveryoneFor Each aTable In ActiveDocument.TablesaTable.Range.Editors.Add wdEditorEveryoneNextActiveDocument.SelectAllEditableRanges wdEdito…...

16、堆基础知识点和priority_queue的模拟实现

一、priority_queue的使用方法 priority_queue的使用方法看这篇文章 二、堆 1、介绍 堆&#xff08;Heap&#xff09;是一种特殊的完全二叉树数据结构&#xff0c;满足以下性质&#xff1a; 堆序性质&#xff08;Heap Property&#xff09;&#xff1a; 大顶堆&#xff08…...

20250419将405的机芯由4LANE的LVDS OUT配置为8LANE的步骤

20250419将405的机芯由4LANE的LVDS OUT配置为8LANE的步骤 2025/4/19 15:38 查询格式YUV/RGB 81 09 04 24 60 FF 90 50 00 00 FF 查询辨率帧率 81 09 04 24 72 FF 90 50 01 03 FF 查询LVDS mode : Singel output/Dual output 81 09 04 24 74 FF 90 50 00 00 FF 配置405的机…...

【信息系统项目管理师】高分论文:论信息系统项目的采购管理(信息化办公系统)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 论文1、规划采购管理2、实施采购3、管理采购论文 随着信息化技术的发展,从企业到政府,传统的办公模式正在悄然消失,信息化办公模式正成为主流。特别是国务院印发的《关于加快推广“互联网+政务服务”工作的…...

国产GPU生态现状评估:从寒武纪到壁仞的编程适配挑战

近年来&#xff0c;国产GPU厂商在硬件性能上持续突破&#xff0c;但软件生态的构建仍面临严峻挑战。本文以寒武纪、壁仞等代表性企业为例&#xff0c;对比分析其与CUDA生态的兼容性差异&#xff0c;并探讨技术突围路径。 一、编程适配的核心挑战 ‌编程模型差异与开发成本‌ …...

Linux(autoDL云服务器)mamba-ssm环境安装——一次成功!

1.创建环境选择torch2.0&#xff0c; cuda11.8&#xff0c;python3.8 2.从GitHub官网下载cp38对应的&#xff0c;causl_conv1d&#xff0c;和mamba-ssm2.2.2。下载入下图所示。 3.直接用finalshell 或者xshell连接服务器上传&#xff0c;到根目录下面。 直接用pip install *…...

手搓LeNet-5(基础模型)实现交通标志识别

手搓LeNet-5&#xff08;基础模型&#xff09;实现交通标志识别 一、环境准备1. 安装Python环境2. 安装CUDA&#xff08;可选&#xff0c;仅需GPU加速时&#xff09;3. 配置虚拟环境4. 安装PyTorch核心库5. 安装辅助库6. 验证安装7. 准备数据集8.常见问题处理 二、 数据集处理三…...

TV主板的拆解学习

下面是小米的电视机主板&#xff0c;电源采用PFCLLC方案&#xff0c;主控采用电视盒子主控采用晶晨半导体T962-H&#xff0c;搭配2G南亚DDR3L内存和8G三星eMMC存储器。 本文用来加深对TV主板的认识&#xff0c;学习于充电头网&#xff0c;链接在文末。 两颗蓝色插件Y电容来自S…...

PH热榜 | 2025-04-19

1. Omakase.ai Voice 标语&#xff1a;你的语音驱动销售助手。一个链接。 介绍&#xff1a;Omakase.ai Voice将您的网站转变为一个语音驱动的销售助手&#xff0c;它可以在客户浏览时进行对话、倾听并给出推荐。聊天机器人往往效果不佳——它们无法实现销售&#xff0c;而这个…...

LeetCode(Hot.2)—— 49.字符异位词分组题解

Problem: 49. 字母异位词分组 字母异位词的定义是&#xff1a;两个单词的字母组成一样&#xff0c;但顺序可以不同&#xff0c;比如 eat、tea 和 ate 就是一个组的。 思路 将每个字符串按字母排序&#xff0c;把排序后的字符串作为 key&#xff0c;相同 key 的放在一个 list 中…...

UE学习记录part19

231 insect: insect enemy type 创建dead动画资源 往insect head上添加socket 创建攻击root motion动画。motion warping需要与root motion合作使用 为buff_blue创建物理资产 设置simulate physic使sinsect死亡后能落到地板上而不是漂浮在空中&#xff0c;要将die函数设置为 -…...

不连续数据区间天数累计sql

计算不连续数据区间天数并且剔除重复天数 create table loan_data(loan_no varchar(10),cust_no varchar(10),start_date date,end_date date )INSERT INTO loan_data VALUES (LN001, CUST001, 2025-01-04, 2025-01-08); INSERT INTO loan_data VALUES (LN002, CUST001, 2025-…...

django基于爬虫的网络新闻分析系统的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!

摘要 本网络新闻分析系统采用B/S架构&#xff0c;数据库是MySQL&#xff0c;网站的搭建与开发采用了先进的Python进行编写&#xff0c;使用了Django框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。前台主要功能包括&#xff1a;用户注册、登录、浏览…...

JAVA文件I/O

目录 一、三种路径的分类&#xff1a; 1、绝对路径&#xff1a; 2、相对路径&#xff1a; 3、基准目录&#xff1a; 二、文件的种类&#xff1a; 三、利用JAVA操作文件&#xff1a; 1、File类的构造方法&#xff1a; 2、File 类方法的使用&#xff1a; 使用例子&#…...

第七周作业

一、分别在前端和后端使用联合注入实现“库名-表名-字段名-数据”的注入过程&#xff0c;写清楚注入步骤 1、爆库 后端sql语句&#xff1a;select database(); 前端&#xff1a;1 order by 1#&#xff0c;1 order by 2#&#xff0c;1 order by 3# 判断显示位为两位1 union sel…...

Linux 进程信号详解

进程信号 信号是进程之间事件异步通知的一种方式&#xff0c;属于软中断。 kill -l //查看不同信号代表的事件 执行kill -l 可以看到共有62种信号&#xff0c;其中&#xff1a; 0-31号信号为非可靠信号&#xff08;这部分信号借鉴于UNIX系统的信号&#xff09;&#xff1b;…...

MCP 应用案例-网络设备批量管理

案例背景 需求痛点 企业需管理数百台跨地域网络设备&#xff08;交换机/路由器&#xff09;&#xff0c;传统方式存在&#xff1a; 人工SSH登录效率低脚本维护成本高&#xff08;不同厂商CLI语法差异&#xff09;状态监控依赖独立监控系统 解决方案 通过MCP协议构建智能网络…...

进程程序替换

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢&#xff1f;进程的程序 替换来完成这个功能&#xff01; 程序替换是通过特定的接⼝&#xff0c;加载磁盘上的⼀个全新的程序(代码和数据)&#xff0c;加载到调⽤进程的地址空间中&#xff01…...

6.7 ChatGPT自动生成定时任务脚本:Python与Cron双方案实战指南

ChatGPT自动生成定时任务脚本:Python与Cron双方案实战指南 关键词:定时任务调度, ChatGPT 代码生成, Cron 脚本开发, Python 调度器, 自动化更新系统 6.3 使用 ChatGPT 生成 Cron 调度脚本 在 GitHub Sentinel 的定期更新功能中,定时任务调度是核心模块。本节演示如何通过…...

废物九重境弱者学JS第十四天--构造函数以及常用的方法

目录 JavaScript 进阶 - 第2天 深入对象 构造函数 实例成员 静态成员 内置构造函数 Object Array 包装类型 String Number 案例 JavaScript 进阶 - 第2天 了解面向对象编程的基础概念及构造函数的作用&#xff0c;体会 JavaScript 一切皆对象的语言特征&#xff0c…...

机器学习+深度学习

文章目录 一、机器学习(一)机器学习概念(二)机器学习基本流程(三)机器学习应用场景二、机器学习的常见工具与相关库(一)Python 机器学习库(二)数据处理库(三)可视化库三、聚类算法思想与模型搭建过程(一)K - Means 聚类算法(二)DBSCAN 聚类算法四、分类算法思想…...

docker基本使用命令

一、镜像 1、拉取镜像 docker pull busybox docker pull nginx:1.26-alpine 2、查看本地镜像 [rootRocky-1 ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 4e1b6bae1e48 18 hours ago 192MB busybox lates…...

相机模型--CMOS和CCD的区别

1--CMOS和CCD的工作原理 CCD&#xff08;Charge Coupled Device&#xff0c;电荷耦合器件&#xff09;&#xff1a; 1. 图像通过光电效应在感光单元中转化为电荷&#xff1b; 2. 每个像素上的电荷被依次“耦合”并传输到芯片的角落&#xff0c;通过一个或几个模拟输出放大器输…...