CppCon 2014 学习:CHEAP, SIMPLE, AND SAFE LOGGING USING C++ EXPRESSION TEMPLATES

news2025/6/6 16:43:38

这段代码定义了一个简单的日志宏 LOG,用来在代码里方便地打印调试信息。

代码细节解析:

#define LOG(msg) \
  if (s_bLoggingEnabled) \
    std::cout << __FILE__ << "(" << __LINE__ << "): " << msg << std::endl
  • s_bLoggingEnabled 是一个全局开关,控制是否启用日志输出。
  • __FILE____LINE__ 是预定义宏,分别表示当前文件名和当前代码行号。
  • msg 是传入的日志内容,利用 C++ 的流式输出(<<)格式,类型安全且灵活
  • 宏展开后,相当于:
    if (s_bLoggingEnabled)
      std::cout << "当前文件名(行号): " << msg << std::endl;
    

使用示例:

void foo() {
  std::string file = "blah.txt";
  int error = 123;
  LOG("Read failed: " << file << " (" << error << ")");
}

打印结果示例:

test.cpp(5): Read failed: blah.txt (123)

总结:

  • 方便:只需写 LOG(...),无需重复写打印格式代码。
  • 类型安全:利用 C++ 流操作符,无需手动格式化字符串。
  • 带上下文:自动打印文件名和代码行号,方便定位问题。
  • 可控开关:通过 s_bLoggingEnabled 动态开启或关闭日志。

这段是对之前的 LOG 宏经过预处理(pre-processing)后的展开效果:

解释

  • 预处理器把 LOG("Read failed: " << file << " (" << error << ")"); 展开成了:
    if (s_bLoggingEnabled)
      std::cout << "foo.cpp" << "(" << 53 << "): " 
                << "Read failed: " << file << " (" << error << ")"
                << std::endl;
    
  • 其中 "foo.cpp" 是当前文件名(__FILE__),53 是代码行号(__LINE__)。
  • fileerror 是变量,仍保持流式输出,类型安全。

作用

  • 宏的作用就是把日志输出封装成一条语句,自动附加文件名和行号,方便调试定位。
  • 预处理后你看到的就是具体的 if 判断加标准输出语句。

你给的这段汇编代码是编译器对含有日志宏 LOG(...) 的 C++ 代码生成的机器指令示例,展示了日志语句的具体实现细节。

void foo() {
    string file = "blah.txt";
    int error = 123;
    ...
    movb  g_bLogging(%rip), %al
    testb %al, %al
    je  
    ..B2.14
    movl  $_ZSt4cout, %edi
    movl  $.L_2__STRING.3, %esi
    call  ostream& operator<<(ostream&, char const*)
    movq  %rax, %rdi
    movl  $.L_2__STRING.0, %esi
    call  ostream& operator<<(ostream&, char const*)
    movq  %rax, %rdi
    movl  $19, %esi
    call  ostream::operator<<(int)
    ...
    movq  %rax, %rdi
    movl  $ostream& endl(ostream&), %esi
    call  ostream::operator<<(ostream& (*)(ostream&))
}

解析:

  • movb g_bLogging(%rip), %al
    取日志开关变量的值到寄存器 %al
  • testb %al, %al
    测试 %al(即判断是否启用日志)。
  • je ..B2.14
    如果日志未启用,跳过日志输出代码。
  • 接下来是一系列调用 std::ostream 的输出操作:
    • operator<< 依次输出文件名、行号、消息内容等。
    • 每次调用后 %rax 保存返回的 ostream&,作为下次调用的第一个参数(链式调用)。
    • 最后调用输出流的 endl,刷新输出缓冲。
  • 编译参数包括 -O3(优化),说明生成的汇编是经过高优化的。
  • 总体代码约33条指令,10次函数调用,比较高效。

总结:

  • 通过汇编代码你可以看到,日志开关检查是条件跳转,性能代价低
  • 每条日志输出通过调用标准流操作符实现,保持类型安全和灵活性。
  • 编译器对日志宏生成的代码做了优化,但调用开销仍在(函数调用多)。
  • 汇编代码体现了日志宏背后的底层实现细节。

你指出了日志代码带来的性能和优化问题,总结得很准确:

问题:

  • 日志相关的指令多,尤其是流操作符和条件判断,导致代码臃肿。
  • 这些指令会阻碍编译器的优化,因为它们可能引入不可预测的控制流和函数调用。
  • 影响指令缓存(icache)性能,代码体积变大,缓存命中率降低。
  • 但我们又想保持日志的速度、类型安全和使用方便

目标:

  • 减少调用日志时的指令数量,即代码体积更小。
  • 仍然保持日志输出速度快,不会显著拖慢程序。
  • 保持C++流操作符的类型安全
  • 使用上仍然简洁方便
    这就是日志设计中的经典权衡点。接下来,可以考虑的优化方案包括:
  • 使用**零开销日志(zero-overhead logging)**技巧,编译期剔除无用日志。
  • 设计更轻量的日志API,避免不必要的函数调用。
  • 利用宏和模板元编程在编译期计算是否启用日志。
  • 结合缓冲区或异步日志减少运行时开销。

是一种通过“表达式模板”(Expression Templates)技术解决日志调用开销的方案,核心思路如下:

核心问题

  • 保持 流式(operator<<)接口 的优雅和类型安全
  • 但避免 每次日志调用都产生多次函数调用operator<< 的开销)

解决方案:表达式模板(Expression Templates)

  • 利用 C++ 运算符重载,在编译期把日志表达式 封装成一个类型(表达式树)
  • 日志表达式如 "Read failed: " << file << " (" << error << ")"
    不在运行时一步步调用 operator<<,而是先变成一个编译期的表达式对象
  • 这样可以在运行时 一次性处理整个表达式,减少函数调用次数和指令数量

类比示例

  • 矩阵计算:
    Matrix D = A + B * C;
    编译器用表达式模板避免生成临时矩阵,多做合并优化。
  • 条件查询:
    polygons.Find(VERTICES == 4 && WIDTH >= 20);
    编译时构建查询条件表达式。
  • 日志表达式:
    LOG("Read failed: " << file << " (" << error << ")");
    通过表达式模板,编译时构造表达式树,运行时统一输出。

总结

  • 通过表达式模板,可以实现 零运行时开销的流式日志
  • 保持 类型安全方便易用的接口
  • 大幅减少日志调用时的指令数量,提高性能和优化空间

这是用表达式模板实现日志系统的一部分代码,解析如下:

代码结构

#define LOG(msg) \
  if (s_bLoggingEnabled) \
    (Log(__FILE__, __LINE__, LogData<None>() << msg))
template<typename List>
struct LogData {
  typedef List type;
  List list;
};
struct None { };

说明

  • LOG(msg) 宏:
    • 检查日志开关 s_bLoggingEnabled
    • 创建一个空的 LogData<None>() 对象(表示空的日志数据列表)
    • 利用重载的 operator<<msg 添加进这个 LogData,构建日志表达式
    • 把文件名、行号和构造的日志数据传给 Log() 函数
  • LogData<List> 模板结构体:
    • 作为表达式模板的核心,保存“日志消息链”(这里用 List 代表消息列表或表达式树)
    • 通过模板递归展开,实现链式拼接日志内容
  • None
    • 表示初始的空日志数据类型

整体作用

  • 利用模板和运算符重载,将日志消息“拼接”成一个类型安全的表达式模板结构
  • 日志内容在运行时才调用 Log() 输出,之前只构建表达式类型,减少多次函数调用开销
  • 保持了流式接口的使用习惯,同时允许编译期优化

这段代码是实现 LogData 表达式模板的关键 operator<<,它实现了日志数据的链式拼接。具体分析如下:

代码内容

template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin, Value&& v) noexcept {
  return {{ std::forward<Begin>(begin.list), std::forward<Value>(v) }};
}

解释

  • 模板参数
    • Begin:表示已有的日志数据类型(表达式模板中的“前半部分”)
    • Value:本次要加入的新日志值类型(右操作数)
  • 参数
    • begin:右值引用,表示已有的 LogData<Begin> 对象(即当前已有的日志链)
    • v:要追加的值(如字符串、变量等)
  • 返回类型
    • 返回一个新的 LogData,其模板参数是一个 std::pair,组合了之前的 begin.list 和新值 v
    • 这样就形成了一个链表结构,每个节点都包含前面的表达式和当前新值
  • 实现
    • 使用 std::forward 完美转发参数,保证传递值的引用性质(左值/右值)
    • 通过花括号初始化 std::pairLogData 对象

作用

  • 每次执行 operator<< 都是把一个新的值追加到已有的 LogData 链表中,形成嵌套的 std::pair 类型链
  • 这个链条在编译时展开,运行时可以一次性遍历输出全部日志内容
  • 保持类型安全无额外函数调用开销

举例

调用示例:

auto data = LogData<None>() << "Error: " << errorCode;
  • 第一次 << "Error:",把 "Error:" 包装进 LogData<std::pair<None, const char*>>
  • 第二次 << errorCode,把 errorCode 和之前链表继续包成新的 LogData<std::pair<std::pair<None, const char*>, int>>
    #结构解析:
LOG("Read failed: " << file << " (" << error << ")");

在宏展开、运算符重载作用下,会构建出如下嵌套类型结构:

LogData<
  pair<
    pair<
      pair<
        pair<
          pair<
            None,
            char const*>,         // "Read failed: "
          string const&>,         // file
        char const*>,             // " ("
      int const&>,                // error
    char const*>                  // ")"
>

这是怎么产生的?

每次你使用 <<,都会走这段代码:

template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin, Value&& v);

这就把“前一个表达式”和“当前要加入的值”合并为一个新的 std::pair
所以会形成链式嵌套结构,每个新 LogData 都把前一个表达式 Begin 作为链表的头节点。

为什么这样做?

这是表达式模板的经典技巧,优点如下:

优点描述
编译期结构构建表达式在编译期以类型嵌套的形式构建完成,无运行时拼接开销
类型安全所有内容在编译期就明确了类型(string, int 等),无字符串格式化出错问题
无需临时变量不会像 stringstream 那样创建临时对象,减小运行时开销
高性能日志输出编译器可以优化掉未启用日志的所有代码路径(零开销)

总结并详细解释一下你提供的 LOG() 实现和其递归原理:

整体结构

你写的是一个 表达式模板日志系统的运行期输出逻辑,它会:

  1. 在编译期 使用模板构建日志消息的链式结构(嵌套 std::pair
  2. 在运行期 递归展开这个链并输出每一部分

代码详解

Log()

template<typename TLogData>
void Log(const char* file, int line, TLogData&& data) noexcept __attribute__((__noinline__)) {
    std::cout << file << "(" << line << "): ";
    Log_Recursive(std::cout, std::forward<typename TLogData::type>(data.list));
    std::cout << std::endl;
}
  • TLogData 是一个 LogData<...> 类型
  • TLogData::type 是嵌套的 std::pair 类型(日志表达式链)
  • data.list 是实际数据链
  • 使用 Log_Recursive 递归遍历整个链并输出
  • __noinline__:避免编译器内联该函数(保持调试时清晰)

Log_Recursive():递归版本

template<typename TLogDataPair>
void Log_Recursive(std::ostream& os, TLogDataPair&& data) noexcept {
    Log_Recursive(os, std::forward<typename TLogDataPair::first_type>(data.first));
    os << std::forward<typename TLogDataPair::second_type>(data.second);
}
  • 遍历 pair<前面内容, 当前值>
  • 先递归打印 .first(链表的前面)
  • 然后输出 .second(当前这一层的值)

Log_Recursive():终止版本

inline void Log_Recursive(std::ostream& os, None) noexcept
{ }
  • 终止条件,当到达最前端 None 类型时不再输出

打印流程图

对于:

LOG("Read failed: " << file << " (" << error << ")");

最终输出类似:

main.cpp(42): Read failed: blah.txt (123)

内部运行时递归调用顺序大致如下:

Log_Recursive(..., pair<..., ")">)
  └── Log_Recursive(..., pair<..., error>)
       └── Log_Recursive(..., pair<..., " (">)
            └── ...
                 └── Log_Recursive(..., None)

每层输出一个值,直到链表遍历完成。

优点总结

优点描述
零运行时开销(禁用时)宏判断 + 模板链式结构避免执行
编译时类型检查所有拼接内容都必须合法 << 运算符
可扩展性强(可加入日志策略)如 mock、profile、日志级别
性能优越优化后无多余函数调用

详细解释一下这段代码的目的和原理,尤其是 处理 std::endl 这样的流操作符(stream manipulators)

问题背景:为什么要特别处理 manipulators?

当你这样写:

LOG("Count: " << count << std::endl);

其中 std::endl 不是普通的值,而是一个 函数指针,它的签名是:

std::ostream& endl(std::ostream&);

这叫做 stream manipulator,像 std::endlstd::flushstd::hex 都是这样。
如果不专门处理它,表达式模板机制就会在编译时出错(类型不匹配)。

解决方案:函数指针偏特化模板

类型定义:

typedef std::ostream& (*PfnManipulator)(std::ostream&);

这就是 manipulator 的类型,本质上是一个函数指针,指向返回 ostream& 并接受一个 ostream& 参数的函数。

operator<< 重载:

template<typename Begin>
LogData<std::pair<Begin&&, PfnManipulator>> operator<<(LogData<Begin>&& begin, PfnManipulator pfn) noexcept {
    return {{ std::forward<Begin>(begin.list), pfn }};
}
  • Begin 是前面链式结构的日志数据类型
  • pfnstd::endl 这类操作符的函数指针
  • 继续构建嵌套 std::pair

最终效果

这段代码允许如下语法合法且能被正确处理:

LOG("Result is: " << result << std::endl);

Log_Recursive() 函数中,这个 pfn 也能被正确递归处理为:

os << pfn;  // 等效于 os << std::endl;

总结

项目内容
解决的问题支持 std::endl 等流操作符
处理方式ostream& (*)(ostream&) 类型专门提供 operator<< 重载
兼容性完整支持任意流拼接表达式
类型安全编译期确保所有内容都能插入 ostream

这一段代码处理的是 字符串字面量优化(String Literal Optimization)。我们来一步一步拆解并说明这段代码的意图和作用。

问题背景

当你写:

LOG("Error: " << error);

其中的 "Error: " 是一个 字符串字面量(string literal),它的类型是:

const char[8]  // 对于 "Error: " 来说是8(含 null terminator)

这个类型和 const char* 不一样,所以没有合适的 operator<< 重载时会导致模板匹配失败。

解决方案:模板重载接受 const char (&sz)[n]

template<typename Begin, size_t n>
LogData<std::pair<Begin&&, const char*>> operator<<(LogData<Begin>&& begin, const char (&sz)[n]) noexcept {
    return {{ std::forward<Begin>(begin.list), sz }};
}

参数解释:

  • Begin:前面构建好的链式日志数据结构
  • sz引用类型的字符数组,也就是 string literal,如 "Error"const char (&)[6]
  • n:模板参数,用来匹配任意长度的字符串字面量

返回值:

  • 返回一个新的 LogData,在已有链条的基础上添加一个 const char* 类型
    这就将 "Error" 类型从 const char[6] 转换成了 const char* 存储,更加轻量,也统一了类型。

优势

优点描述
支持字符串字面量不支持会导致编译错误
避免每次都拷贝字符串字面量的内容只存指针,效率高
统一类型为 const char*简化后续递归处理逻辑
零开销类型转换编译期完成,不增加运行时负担

示例用法

LOG("Error at line: " << lineNum << " in file: " << filename);

对于上面这段,模板重载会自动识别 "Error at line: "" in file: " 为 string literal,匹配这个特化模板,转成 const char*

总结

你看到的这一段代码实现的是:

专门支持 "字符串字面量" 这种特殊的数组类型,并将其优化为 const char* 来存储,提高日志系统的灵活性和效率。

你这段汇编和其后的模板展开反映的是 高性能、低指令开销的日志系统实现方式

场景回顾

你在使用这样的日志语句:

LOG("Read failed: " << file << " (" << error << ")");

其中 LOG 是一个宏,最终展开为对 Log(...) 的一次调用,使用了 表达式模板(expression templates) 技术来延迟表达式求值、减少运行时指令。

汇编代码解释

movb g_bLogging(%rip), %al      ; 加载全局布尔变量 s_bLoggingEnabled
testb %al, %al                  ; 测试它是否为真
je    ..B6.7                    ; 如果为假,跳转(不执行日志)
movb $0, (%rsp)                 ; 临时栈处理(非关键)
movl $.L_2__STRING.4, %ecx      ; 加载字符串字面量指针(如 " (" )
movl $.L_2__STRING.3, %edi      ; 加载另一个字符串(如 "Read failed: ")
movl $40, %esi                  ; 加载行号
lea   128(%rsp), %r9            ; 设置临时地址作为参数
call Log<...>                   ; 调用唯一的 Log 模板函数实例

模板展开(推导出的 LogData)

Log<pair<
    pair<
        pair<
            pair<
                pair<
                    None,
                    char const*       // "Read failed: "
                >,

                string const&         // file
            >,

            char const*               // " ("
        >,

        int const&                   // error
    >,

    char const*                     // ")"
>>

这是你通过 << 运算符链式拼接出来的日志数据表达式,模板在编译期就构建好了这些类型。
最终传给唯一的 Log(...) 函数,运行时只需一次函数调用(pimp’d function call)即可完成整条日志输出。

优点

优点说明
编译期表达式组合<< 构建表达式的结构体,不执行实际操作
运行期延迟调用只有一次 Log 函数调用(模板实例化)
少量汇编指令这里只用了 9 条汇编指令来构造参数和调用
保留语义完整性LOG(...) 保持流式语法,且类型安全
优化友好编译器能轻松做内联或省略,性能极高

理解重点

你看到的这一切说明:表达式模板技术允许你写出非常干净的代码,而编译器又能生成非常高效的汇编。 它兼顾了:

  • 语法简洁
  • 类型安全
  • 运行时性能
  • 最小的指令和函数调用开销

总结提到的是:使用表达式模板的日志系统,在性能和编译优化方面的巨大优势。下面逐点解释:

SUMMARY 理解

• Expression templates solution

表达式模板方案
使用表达式模板技术(即 << 拼接被延迟到编译期生成类型结构),实现日志消息的构建。这种方式不在日志调用处做字符串拼接或格式化,而是传递一个类型安全的结构(LogData<...>)到一个统一的日志处理函数。

• Reduced instructions at call site by 73% (33 → 9)

调用处的汇编指令减少了 73%(从 33 条降到 9 条)
传统的日志实现使用大量的 operator<<,每次 << 都是函数调用。表达式模板把这些函数调用“挪”到编译期,只留下最终调用 Log(...) 的一次函数调用。
这极大地提升了性能,特别是在嵌入式或高频调用场景下。

• Mo’ args, mo’ savings

参数越多,节省越大
传统日志系统每多一个参数,就多一到两个函数调用。而表达式模板只构建更复杂的类型结构,运行时开销不变。你写:

LOG("Read failed: " << file << " (" << error << ") at offset " << offset << ", reason: " << reason);

无论参数多少,运行时也就一两次函数调用。结果是:
参数越多,节省越多!

总结一句话

表达式模板让你保留了优雅的写法,却几乎不付出运行时代价,是一种兼顾语义表达极致性能的高级技巧。

可变参数模板(Variadic Template)日志系统 的完整代码版本,并附带了详细注释,帮助你理解其结构与作用:

完整代码:Variadic Template Logging

#include <iostream>
#include <string>
bool s_bLoggingEnabled = true;  // 控制是否启用日志
// 提前声明递归函数模板
template <typename T, typename... Args>
void Log_Recursive(const char* file, int line, std::ostream& os, T first, const Args&... rest);
inline void Log_Recursive(const char* file, int line, std::ostream& os);  // 终止版本声明
// 定义 LOG 宏,用于自动捕获 __FILE__ 和 __LINE__,并转发可变参数到 Log_Variadic
#define LOG(...) Log_Variadic(__FILE__, __LINE__, __VA_ARGS__)
// 主日志函数模板,接受任意数量和类型的参数
template <typename... Args>
void Log_Variadic(const char* file, int line, const Args&... args) {
    if (!s_bLoggingEnabled) return;                 // 如果日志未启用,直接返回
    std::cout << file << "(" << line << "): ";      // 打印文件名和行号
    Log_Recursive(file, line, std::cout, args...);  // 展开参数并输出
    std::cout << std::endl;                         // 换行
}
// 递归模板函数:处理一个参数,然后递归处理剩余的参数
template <typename T, typename... Args>
void Log_Recursive(const char* file, int line, std::ostream& os, T first, const Args&... rest) {
    os << first;                             // 打印当前参数
    Log_Recursive(file, line, os, rest...);  // 递归处理剩余参数
}
// 递归终止函数:不做任何事(没有参数时)
inline void Log_Recursive(const char* file, int line, std::ostream& os) {
    // 什么都不做,递归终止
}

使用示例

// 示例
int main() {
    std::string file = "config.txt";
    int errorCode = 404;
    LOG("Failed to read file: ", file, ", error code: ", errorCode);
}

输出示例:

/home/xiaqiu/test/CppCon/day46/code/main.cpp(40): Failed to read file: config.txt, error code: 404

特点总结

特性说明
类型安全使用模板展开参数,支持任意类型(只要可输出到 std::ostream
语法简洁printf 相比更现代、更安全,且无需格式字符串
可扩展可添加如时间戳、线程 ID、颜色等功能
条件编译可以结合宏或 s_bLoggingEnabled 控制开关

下面这段代码(专门用于优化字符串字面量处理)的完整可运行示例,包含基础 LogData 类型、递归输出逻辑、宏定义等内容。这个版本模拟了使用 表达式模板风格的日志系统,特别是你提到的 operator<< 针对 const char[N] 的特化处理。

完整示例代码:支持字符串字面量优化的日志系统

#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
// 控制是否启用日志
bool s_bLoggingEnabled = true;
// 基础类型 None,用于终止递归链表结构
struct None { };
// 表达式模板用的包装类型 LogData
template<typename List>
struct LogData {
    using type = List;
    List list;
};
// 重载 operator<<:通用版本,将任意类型“连接”到 LogData 链中
template<typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>>
operator<<(LogData<Begin>&& begin, Value&& v) noexcept {
    return {{ std::forward<Begin>(begin.list), std::forward<Value>(v) }};
}
// 特化版本:优化 const char[N] 字符串字面量的拼接(避免构造 std::string)
template<typename Begin, size_t n>
LogData<std::pair<Begin&&, const char*>>
operator<<(LogData<Begin>&& begin, const char (&sz)[n]) noexcept {
    return {{ std::forward<Begin>(begin.list), sz }};
}
// 操作流控制符(如 std::endl)的版本
using PfnManipulator = std::ostream& (*)(std::ostream&);
template<typename Begin>
LogData<std::pair<Begin&&, PfnManipulator>>
operator<<(LogData<Begin>&& begin, PfnManipulator pfn) noexcept {
    return {{ std::forward<Begin>(begin.list), pfn }};
}
// 宏:包装日志调用,自动注入文件名和行号
#define LOG(msg) \
    if (s_bLoggingEnabled) \
        Log(__FILE__, __LINE__, LogData<None>() << msg)
// 主日志函数(展开表达式链)
template<typename TLogData>
void Log(const char* file, int line, TLogData&& data) noexcept {
    std::cout << file << "(" << line << "): ";
    Log_Recursive(std::cout, std::forward<typename TLogData::type>(data.list));
    std::cout << std::endl;
}
// 递归打印表达式链
template<typename TLogDataPair>
void Log_Recursive(std::ostream& os, TLogDataPair&& data) noexcept {
    Log_Recursive(os, std::forward<typename TLogDataPair::first_type>(data.first));
    os << std::forward<typename TLogDataPair::second_type>(data.second);
}
// 递归终止函数
inline void Log_Recursive(std::ostream& os, None) noexcept {
    // Do nothing
}
// 示例
int main() {
    std::string filename = "data.txt";
    int errCode = 42;
    LOG("Error opening " << filename << ": code " << errCode << std::endl);
}

输出示例

如果编译并运行,会输出类似:

main.cpp(87): Error opening data.txt: code 42

特点说明

  • 表达式模板 LogData + operator<< 允许将多个元素拼接成“延迟求值”的链表结构;
  • 字符串字面量版本的 operator<< 避免将其视为模板推导中需要额外构造的 std::string,提升效率;
  • 支持任意类型拼接、流操作符(如 std::endl);
  • 所有输出逻辑最终只在 Log() 函数中统一处理,便于拦截、替换输出流(比如写入文件)或增加 profiling。

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

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

相关文章

专业级PDF转CAD解决方案

PDF 文件因其出色的便携性和稳定性&#xff0c;已成为许多用户的首选格式。但在涉及图像编辑或精细调整时&#xff0c;CAD 文件显然更具优势。 这款 CAD 图纸转换工具&#xff0c;界面清爽、操作直观&#xff0c;是处理图纸文件的理想助手。 它不仅支持不同版本 CAD 文件之间…

STM32 智能小车项目 两路红外循迹模块原理与实战应用详解

在嵌入式系统、机器人、智能设备等场景中&#xff0c;红外反射型光电传感器 被广泛应用于黑白识别、障碍检测、物体计数、位置判断等任务。其中&#xff0c;RPR220 是一款性能稳定、体积小巧的红外光电收发管&#xff0c;本文将详细介绍其工作原理、引脚参数、接线说明以及典型…

SSL安全证书怎么安装?

SSI并非一个标准的、广为人知的安全证书类型&#xff0c;通常网站安装的是SSL/TLS证书&#xff0c;用于加密网站和用户浏览器之间的通信&#xff0c;保障数据传输安全。以下以安装SSL/TLS证书为例&#xff0c;介绍网站安装证书的步骤&#xff1a; 一、证书申请与获取 选择证书…

电子电器架构 --- OTA测试用例分析(上)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

抑郁症患者数据分析

导入数据 import pandas as pd from pyecharts.charts import * from pyecharts import options as optsdfpd.read_csv(YiYuZheng.csv) df.head(1)Patient_nameLabelDateTitleCommunicationsDoctorHospitalFaculty0患者&#xff1a;女 43岁压抑05.28压抑 个人情况&#xff1a;…

Rust 学习笔记:关于智能指针的练习题

Rust 学习笔记&#xff1a;关于智能指针的练习题 Rust 学习笔记&#xff1a;关于智能指针的练习题问题一问题二问题三问题四问题五问题六问题七问题八问题九问题十 Rust 学习笔记&#xff1a;关于智能指针的练习题 参考视频&#xff1a; https://www.bilibili.com/video/BV1S…

6.RV1126-OPENCV 形态学基础膨胀及腐蚀

一.膨胀 1.膨胀原理 膨胀的本质就是通过微积分的转换&#xff0c;将图像A和图形B进行卷积操作合并成一个AB图像。核就是指任意的形状或者大小的图形B。例如下图&#xff0c;将核(也就是图形B)通过微积分卷积&#xff0c;和图像A合并成一个图像AB。 2.特点 图像就会更加明亮 …

筑牢企业网管域安全防线,守护数字核心——联软网管域安全建设解决方案

在当今数字化浪潮中&#xff0c;企业网管域作为数据中心的核心&#xff0c;其安全防护至关重要。一旦网管域遭受攻击&#xff0c;整个网络系统可能陷入瘫痪&#xff0c;给企业带来巨大损失。联软科技凭借其创新的网管域安全建设解决方案&#xff0c;为企业提供了全方位的安全保…

【目标检测】backbone究竟有何关键作用?

backbone的核心在于能为检测提供若干种感受野大小和中心步长的组合&#xff0c;以满足对不同尺度和类别的目标检测。

一个小小的 flask app, 几个小工具,拼凑一下

1. 起因&#xff0c; 目的: 自己的工具&#xff0c;为自己服务。给大家做参考。项目地址&#xff1a; https://github.com/buxuele/flask_utils 2. 先看效果 3. 过程: 一个有趣的 Flask 工具集&#xff1a;从无到有的开发历程 缘起&#xff1a;为什么要做这个项目&#xff…

对抗性提示:大型语言模型的安全性测试

随着大语言模型&#xff08;LLM&#xff09;在虚拟助手、企业平台等现实场景中的深度应用&#xff0c;其智能化与响应速度不断提升。然而能力增长的同时&#xff0c;风险也在加剧。对抗性提示已成为AI安全领域的核心挑战&#xff0c;它揭示了即使最先进的模型也可能被操纵生成有…

好得睐:以品质守味、以科技筑基,传递便捷与品质

据相关数据显示&#xff0c;超市半成品菜是冻品区增长最快品类&#xff0c;再加上商超渠道作为消费者日常高频接触场景&#xff0c;是促进半成品菜成为冻品生鲜消费领域的关键一环。好得睐作为半成品菜领军品牌&#xff0c;其商超渠道布局是连接消费者与品质生活的重要桥梁。商…

docker-部署Nginx以及Tomcat

一、docker 部署Nginx 1、搜索镜像&#xff08;nginx&#xff09; [rootlocalhost /]# docker search nginx Error response from daemon: Get "https://index.docker.io/v1/search?qnginx&n25": dial tcp 192.133.77.133:443: connect: connection refused 简…

蒙特卡罗模拟: 高级应用的思路和实例

蒙特卡罗模拟不仅仅是一种理论练习&#xff0c;它还是一种强大的工具&#xff0c;在金融、医疗保健、物流等领域都有实际应用。本篇文章将探讨高级和复杂的现实生活场景&#xff0c;深入探讨它们的细微差别&#xff0c;并通过详细的解释在 Python 中实现它们。 什么是蒙特卡罗…

数据分析Agent构建

数据分析agent构建 代码资料来源于 Streamline-Analyst&#xff0c;旨在通过该仓库上的代码了解如何使用大语言模型构建数据分析工具&#xff1b; 个人仓库&#xff1a;Data-Analysis-Agent-Tutorial 不同的在于 Data-Analysis-Agent-Tutorial 是在 Streamline-Analyst 基础…

vscode配置lua

官网下载lua得到如下 打开vscode的扩展下载如下三个 打开vscode的此处设置 搜索 executorMap&#xff0c;并添加如下内容

【笔记】MSYS2 的 MINGW64 环境 全面工具链

#工作记录 MSYS2 的 MINGW64 环境&#xff08;mingw64.exe&#xff09;&#xff0c;下面是为该环境准备的最全工具链安装命令&#xff08;包括 C/C、Python、pip/wheel、GTK3/GTK4、PyGObject、Cairo、SDL2 等&#xff09;。 这一环境适用于构建原生 64 位 Windows 应用程序。…

国内头部的UWB企业介绍之品铂科技

一、核心优势与技术实力‌ ‌厘米级定位精度‌ 自主研发的ABELL无线实时定位系统&#xff0c;在复杂工业环境中实现静态与动态场景下‌10-30厘米‌高精度定位&#xff0c;尤其擅长金属设备密集的化工、电力等场景&#xff0c;抗干扰能力行业领先。‌多技术融合能力‌ 支持卫星…

Prj10--8088单板机C语言8259中断测试(2)

1.测试结果 2.全部代码 #include "tiny_stdarg.h" // 使用自定义可变参数实现#define ADR_273 0x0200 #define ADR_244 0x0400 #define LED_PORT 0x800 #define PC16550_THR 0x1f0 #define PC16550_LSR 0x1f5 / //基本的IO操作函数 / char str[]"Hel…

35.x64汇编写法(二)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;34.x64汇编写法&#xff08;一&#xff09; 上一个内容写了&#xff0c;汇编调…