C语言-预处理详解

news2024/12/27 13:40:54

文章目录

  • 🎯引言
  • 👓预处理详解
    • 1.预定义符号
      • 1.1 `__FILE__`
      • 1.2 `__LINE__`
      • 1.3 `__DATE__`
      • 1.4 `__TIME__`
      • 1.5 `__STDC__`
    • 2.#define定义常量
      • 2.1 定义数值常量
      • 2.2 定义字符串常量
    • 3.#define中使用参数
      • 3.1**使用示例**
      • 3.2注意事项
    • 4.宏替换的规则
    • 5.宏函数和函数的对比
      • 5.1宏函数
      • 5.2普通函数
    • 6.#和##
      • 6.1`#` 操作符(字符串化)
      • 6.2**`##` 操作符(令牌粘合)**
    • 7.#undef
      • 7.1`#undef` 的基本用法
    • 8.条件编译
      • 8.1 `#ifdef` 和 `#ifndef` 的基本用法
      • 8.2 `#if`、`#elif`、`#else` 和 `#endif` 的基本用法
    • 9.头文件的包含
      • 9.1**包含头文件的方式**
      • 9.2头文件重复包含的问题
  • 🥇结语

在这里插入图片描述

🎯引言

在C语言编程中,预处理是一个重要且常被忽视的步骤。它在编译之前对源代码进行处理,执行诸如宏替换、文件包含和条件编译等任务。通过预处理,程序员能够提升代码的可读性、可维护性和可移植性,使得编程更加高效和灵活。在本文中,我们将详细探讨C语言预处理的各种机制和用法,帮助读者深入理解预处理的功能和作用。

👓预处理详解

1.预定义符号

预定义符号是由C语言标准或编译器自动定义的宏,它们在预处理阶段被替换成特定的值或信息。以下是一些常见的预定义符号及其用途:

1.1 __FILE__

__FILE__表示当前编译的源文件名。它通常用于调试信息和日志记录,以帮助开发人员追踪代码的位置。

#include <stdio.h>

int main() {
    printf("This code is in file: %s\n", __FILE__);
    return 0;
}

1.2 __LINE__

__LINE__表示当前源代码中的行号。与__FILE__一起使用,可以准确定位代码中的特定行。

#include <stdio.h>

int main() {
    printf("This code is at line: %d\n", __LINE__);
    return 0;
}

1.3 __DATE__

__DATE__表示当前编译的日期,格式为 “MMM DD YYYY”(如 “Jul 9 2024”)。

#include <stdio.h>

int main() {
    printf("This code was compiled on: %s\n", __DATE__);
    return 0;
}

1.4 __TIME__

__TIME__表示当前编译的时间,格式为 “HH:MM”。

#include <stdio.h>

int main() {
    printf("This code was compiled at: %s\n", __TIME__);
    return 0;
}

1.5 __STDC__

__STDC__指示编译器是否遵循ANSI C标准。如果定义了__STDC__,则其值为1。

#include <stdio.h>
//下面的代码会在条件编译里学到
int main() {
#ifdef __STDC__
    printf("This compiler conforms to the ANSI C standard.\n");
#else
    printf("This compiler does not conform to the ANSI C standard.\n");
#endif
    return 0;
}

输出示例:

This compiler conforms to the ANSI C standard.

2.#define定义常量

在C语言中,#define指令用于定义符号常量和宏。通过使用#define指令,程序员可以为数值、字符串或表达式指定一个符号名称,以提高代码的可读性和可维护性。以下是一些常见的用法示例:

2.1 定义数值常量

使用#define可以为一个数值定义一个符号常量,这在需要重复使用某个固定值的情况下特别有用。

include <stdio.h>

#define PI 3.14159

int main() {
    double radius = 5.0;
    double area = PI * radius * radius;
    printf("Area of the circle: %f\n", area);
    return 0;
}

在这个例子中,PI被定义为3.14159,然后在计算圆的面积时使用。

2.2 定义字符串常量

#define也可以用来定义字符串常量。这对于需要在多个地方使用相同字符串的情况非常有用。

#include <stdio.h>

#define GREETING "Hello, World!"

int main() {
    printf("%s\n", GREETING);
    return 0;
}

在这个例子中,GREETING被定义为字符串"Hello, World!",然后在printf中使用。

注意事项

  • #define指令不带有分号,因为它们不是语句。
  • 使用大写字母命名常量和宏是一个好的编程习惯,这样可以区分于变量名。

3.#define中使用参数

基本语法

#define 宏名称(参数列表) 替换文本

3.1使用示例

  1. 定义简单的参数化宏

    参数化宏可以接受一个或多个参数,并在替换文本中使用这些参数。

    define SQUARE(x) ((x) * (x))
    
    #include <stdio.h>
    
    int main() {
        int num = 4;
        printf("Square of %d: %d\n", num, SQUARE(num));
        return 0;
    }
    

    在这个例子中,SQUARE宏接受一个参数x,并返回x的平方。

  2. 多参数宏

    参数化宏可以接受多个参数,用逗号分隔。

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
    #include <stdio.h>
    
    int main() {
        int x = 5, y = 10;
        printf("Max of %d and %d is %d\n", x, y, MAX(x, y));
        return 0;
    }
    

    在这个例子中,MAX宏接受两个参数ab,并返回其中的较大值。

  3. 带有复杂表达式的宏

    参数化宏可以包含复杂的表达式,以实现更复杂的逻辑。

    #define ABS(x) ((x) < 0 ? -(x) : (x))
    
    #include <stdio.h>
    
    int main() {
        int n = -5;
        printf("Absolute value of %d is %d\n", n, ABS(n));
        return 0;
    }
    

    在这个例子中,ABS宏计算并返回x的绝对值。

3.2注意事项

  1. 括号使用

    在定义参数化宏时,应使用括号包裹参数和整个宏表达式,以避免运算优先级问题。例如:

    #define ADD(x, y) ((x) + (y))
    

    这样可以确保在使用宏时,宏参数的表达式被正确计算。

  2. 避免副作用

    宏中的参数可能会被多次计算,导致副作用。因此,避免在宏参数中使用可能产生副作用的表达式,如递增或递减操作。

    #define INCREMENT(x) ((x) + 1)
    
    int a = 5;
    int b = INCREMENT(a++);
    // b 的值可能不是预期的6,因为a++会被多次计算
    

    在这个例子中,a++会被多次计算,导致未定义行为。

  3. 宏与函数的区别

    • 无类型检查:宏不会进行类型检查,所有的替换在预处理阶段完成。
    • 无参数数量检查:宏不会检查参数的数量,传递错误数量的参数不会产生编译错误。
    • 代码膨胀:宏会在每次使用时展开,可能导致代码膨胀,而函数只在调用时执行。
  4. 宏定义中的空格

    在宏定义中,宏名和参数列表之间不要有空格,否则会导致编译错误。

    #define SQUARE (x) ((x) * (x)) // 错误的定义
    #define SQUARE(x) ((x) * (x)) // 正确的定义
    
  5. 多行宏

    使用反斜杠(\)可以将宏定义扩展到多行。

    #define PRINT_VALUES(a, b) do { \
        printf("a: %d\n", a);       \
        printf("b: %d\n", b);       \
    } while (0)
    
    int main() {
        int x = 3, y = 4;
        PRINT_VALUES(x, y);
        return 0;
    }
    

通过合理使用参数化宏,可以实现代码的简洁和复用,但在使用过程中需注意避免潜在的问题,确保代码的可维护性和正确性。

4.宏替换的规则

识别宏名

  • 编译器会识别代码中出现的宏名,即以 #define 指令定义的标识符。

文本替换

  • 当编译器在代码中遇到宏名时,会用宏定义中的替换文本来替换宏名。这是一个简单的文本替换过程,不进行任何语法检查或计算。

参数替换

  • 如果宏是一个参数化宏,那么在宏定义中可以指定一个或多个形式参数。这些形式参数在宏调用时会被实际参数替换。在宏替换过程中,编译器将实际参数直接插入到宏定义中形式参数的位置。

5.宏函数和函数的对比

5.1宏函数

宏函数是使用预处理器指令 #define 定义的宏,它可以接受参数,并在预处理阶段进行文本替换。宏函数没有类型检查,也不执行参数求值,只是简单的文本替换。

优点:

  1. 无函数调用开销:宏在预处理阶段进行替换,没有函数调用的开销。
  2. 灵活:宏可以实现一些在普通函数中无法实现的操作,如代码片段插入等。
  3. 内联展开:宏在每次使用时都会展开,避免了函数调用的开销。

缺点:

  1. 无类型检查:宏没有类型检查,容易出现隐蔽的错误。
  2. 调试困难:宏展开后代码会变得复杂,调试时难以追踪。
  3. 代码膨胀:宏展开会导致代码量增加,特别是宏被频繁使用时。

5.2普通函数

普通函数是在C语言中定义的函数,它有明确的参数和返回类型,编译器会进行类型检查和参数求值。

优点:

  1. 类型安全:函数有明确的类型检查,避免了许多编译时错误。
  2. 可调试性:函数调用可以很容易地通过调试器进行跟踪和分析。
  3. 代码复用:函数体只定义一次,可以在多个地方调用,减少代码重复。

缺点:

  1. 函数调用开销:函数调用涉及参数传递和栈操作,有一定的性能开销。
  2. 局部变量的生命周期:函数的局部变量在函数调用时创建,调用结束后销毁。

6.#和##

6.1# 操作符(字符串化)

# 操作符用于将宏参数转换为字符串。这个过程称为字符串化。当在宏定义中使用 # 操作符时,宏参数会被转换为一个字符串字面量。

示例:

#include <stdio.h>

#define STRINGIFY(x) #x

int main() {
    int value = 10;
    printf("%s\n", STRINGIFY(value)); // 输出 "value"
    printf("%s\n", STRINGIFY(Hello World)); // 输出 "Hello World"
    return 0;
}

在这个示例中,STRINGIFY 宏将参数转换为字符串,因此 STRINGIFY(value) 被替换为 "value",而 STRINGIFY(Hello World) 被替换为 "Hello World"

6.2**## 操作符(令牌粘合)**

## 操作符用于连接两个宏参数,或者连接宏参数和其他文本。这个过程称为令牌粘合。通过 ## 操作符,可以生成新的标识符或代码片段。

示例:

#include <stdio.h>

#define CONCAT(a, b) a##b

int main() {
    int xy = 100;
    printf("%d\n", CONCAT(x, y)); // 输出 100
    return 0;
}

在这个示例中,CONCAT 宏将参数 ab 连接起来,因此 CONCAT(x, y) 被替换为 xy,这与定义的变量 int xy = 100; 相匹配。

注意事项

  1. 字符串化

    • # 操作符只能用于宏定义中的参数,并且只能将参数转换为字符串。
    • 如果需要在宏外部将一个值转换为字符串,需要手动添加引号。
  2. 令牌粘合

    • ## 操作符用于连接两个宏参数或连接宏参数和其他文本,但需要确保连接后的结果是一个有效的标识符或代码片段。
    • 使用 ## 操作符时,需要注意生成的代码是否符合语法要求。
  3. 嵌套宏

    • 如果在一个宏中使用另一个宏,预处理器会先展开内层的宏,然后再展开外层的宏。
    • 当使用 ## 操作符时,需要注意展开顺序,以避免生成无效的代码。

    嵌套宏示例:

    嵌套宏的展开顺序

    1. 内层宏先展开:预处理器首先会展开最内层的宏。
    2. 外层宏后展开:在内层宏展开之后,外层宏才会展开。

    使用 ## 操作符的嵌套宏示例

    下面的例子演示了嵌套宏的展开顺序,并展示了 ## 操作符在嵌套宏中的使用情况。

    示例:

    #include <stdio.h>
    
    #define CREATE_VAR(name, num) name##num
    #define VAR_PREFIX(name) create_##name
    #define CREATE_FULL_VAR(name, num) CREATE_VAR(VAR_PREFIX(name), num)
    
    int main() {
        int create_var1 = 10;
        printf("%d\n", CREATE_FULL_VAR(var, 1)); // 预期输出:10
        return 0;
    }
    

    在这个示例中,有三个宏定义:

    • CREATE_VAR(name, num):将 namenum 连接起来。
    • VAR_PREFIX(name):在 name 前添加前缀 create_
    • CREATE_FULL_VAR(name, num):先调用 VAR_PREFIX(name),再调用 CREATE_VAR

    展开过程解析:

    1. 最内层宏展开
      • VAR_PREFIX(var) 展开为 create_var
    2. 外层宏展开
      • CREATE_VAR(create_var, 1) 展开为 create_var1

    所以,CREATE_FULL_VAR(var, 1) 展开为 create_var1,这与定义的变量 int create_var1 = 10; 相匹配。因此,printf("%d\n", CREATE_FULL_VAR(var, 1)); 会输出 10

7.#undef

7.1#undef 的基本用法

语法

#undef MACRO_NAME

MACRO_NAME 是要取消定义的宏名称。使用 #undef 后,这个宏在预处理阶段将不再被识别为定义的宏。

示例

基本示例

#include <stdio.h>

#define MAX 100

int main() {
    printf("MAX: %d\n", MAX); // 输出 "MAX: 100"
    
    #undef MAX

    #ifdef MAX
        printf("MAX is defined.\n");
    #else
        printf("MAX is not defined.\n"); // 输出 "MAX is not defined."
    #endif

    return 0;
}

在这个示例中:

  1. MAX 被定义为 100。
  2. 使用 #undef MAX 取消 MAX 的定义。
  3. 使用 #ifdef MAX 检查 MAX 是否定义,结果显示 MAX 未定义。

避免命名冲突

#include <stdio.h>

#define VALUE 10

void first_function() {
    printf("VALUE in first_function: %d\n", VALUE); // 输出 "VALUE in first_function: 10"
    #undef VALUE
}

void second_function() {
    #define VALUE 20
    printf("VALUE in second_function: %d\n", VALUE); // 输出 "VALUE in second_function: 20"
    #undef VALUE
}

int main() {
    first_function();
    second_function();

    #ifdef VALUE
        printf("VALUE in main: %d\n", VALUE);
    #else
        printf("VALUE is not defined in main.\n"); // 输出 "VALUE is not defined in main."
    #endif

    return 0;
}

在这个示例中:

  1. first_function 中定义并使用 VALUE,然后使用 #undef 取消定义。
  2. second_function 中重新定义 VALUE 并使用,然后使用 #undef 取消定义。
  3. main 中检查 VALUE 是否定义,结果显示 VALUE 未定义。

8.条件编译

8.1 #ifdef#ifndef 的基本用法

  • #ifdef MACRO:如果宏 MACRO 已经被定义,则编译后面的代码块。
  • #ifndef MACRO:如果宏 MACRO 没有被定义,则编译后面的代码块。

示例说明

使用 #ifdef

#include <stdio.h>

#define DEBUG_MODE // 定义调试模式宏

int main() {
    // 如果 DEBUG_MODE 被定义,则输出调试信息
    #ifdef DEBUG_MODE
        printf("Debug mode is enabled.\n");
    #else
        printf("Debug mode is disabled.\n");
    #endif

    return 0;
}
  • 解释:在这个示例中,我们定义了 DEBUG_MODE 宏。在 main 函数中,使用 #ifdef DEBUG_MODE 检查 DEBUG_MODE 是否已经定义。如果已经定义,则编译输出 “Debug mode is enabled.”,否则输出 “Debug mode is disabled.”。

使用 #ifndef

#include <stdio.h>

// 如果 RELEASE_MODE 宏未被定义,则输出消息
#ifndef RELEASE_MODE
    #define RELEASE_MODE
#endif

int main() {
    printf("This message will always be displayed.\n");

    // 如果 RELEASE_MODE 宏未被定义,则输出消息
    #ifndef RELEASE_MODE
        printf("Not in release mode.\n");
    #endif

    return 0;
}
  • 解释:在这个示例中,我们首先通过 #ifndef RELEASE_MODE 检查 RELEASE_MODE 是否未被定义。在这种情况下,我们定义了 RELEASE_MODE 宏,并且在 main 函数中,如果 RELEASE_MODE 宏未被定义,则输出 “Not in release mode.”。

区别和注意事项

  • #ifdef#ifndef 是用来检查宏是否已经定义或未定义的指令。
  • 使用 #ifdef 可以直接判断宏是否已经定义,而 #ifndef 则相反。
  • 这些指令在预处理阶段进行处理,因此在编译时会根据宏的定义情况选择性地编译代码段。

8.2 #if#elif#else#endif 的基本用法

  • #if:基于常量表达式进行条件编译。
  • #elif:在之前的 #if#elif 条件不满足时继续检查其他条件。
  • #else:如果之前的 #if#elif 条件都不满足,则执行 #else 后面的代码块。
  • #endif:结束条件编译块。

示例说明

使用 #if#elif#else 和 `#endif

#include <stdio.h>

#define DEBUG_MODE
#define DEBUG_LEVEL 2

int main() {
    // 根据 DEBUG_MODE 和 DEBUG_LEVEL 的定义输出不同级别的调试信息
    #if defined(DEBUG_MODE) && DEBUG_LEVEL > 2
        printf("Detailed debug information.\n");
    #elif defined(DEBUG_MODE) && DEBUG_LEVEL > 0
        printf("Basic debug information.\n");
    #else
        printf("No debug information.\n");
    #endif

    return 0;
}
  • 解释:在这个示例中,我们使用了 #if#elif#else 来根据不同的条件输出不同级别的调试信息。

    • 如果 DEBUG_MODE 宏被定义且 DEBUG_LEVEL > 2,则输出详细的调试信息。
    • 如果 DEBUG_MODE 宏被定义且 DEBUG_LEVEL > 0,则输出基本的调试信息。
    • 如果上述条件都不满足,则输出 “No debug information.”。

    在预处理阶段,编译器会先处理条件编译指令,其中的 defined(DEBUG_MODE) 表达式会被解析为:

    • 如果 DEBUG_MODE 宏已经被定义,则整个表达式的值为 1(true)。
    • 如果 DEBUG_MODE 宏未定义,则整个表达式的值为 0(false)。

使用 #else

#include <stdio.h>

#define RELEASE_MODE // 定义发布模式宏

int main() {
    // 如果 RELEASE_MODE 宏被定义,则输出 "Release mode is enabled."
    #ifdef RELEASE_MODE
        printf("Release mode is enabled.\n");
    #else
        printf("Debug mode is enabled.\n");
    #endif

    return 0;
}
  • 解释:在这个示例中,我们使用了 #ifdef#else 来根据宏的定义情况输出不同的信息。
    • 如果 RELEASE_MODE 宏被定义,则输出 “Release mode is enabled.”。
    • 否则(即 RELEASE_MODE 宏未定义),输出 “Debug mode is enabled.”。

注意事项

  • 预处理阶段#if#elif#else#endif 指令在预处理阶段进行处理,根据条件选择性地编译代码。
  • 嵌套使用:可以嵌套使用多个 #if#elif 来处理复杂的条件逻辑。
  • 可读性:合理使用条件编译可以提高代码的可读性和灵活性,但过度使用可能会导致代码维护困难和可移植性降低。

9.头文件的包含

9.1包含头文件的方式

  1. 尖括号 < > 包含系统头文件

    #include <stdio.h>
    
    • 作用:用于包含系统提供的标准库头文件或者编译器环境提供的头文件。
    • 查找策略:编译器会根据预定义的路径来查找头文件。这些路径通常包括标准系统路径,例如 /usr/include 或者编译器环境指定的路径。
  2. 双引号 " 包含用户自定义头文件

    #include "myheader.h"
    
    • 作用:用于包含用户自己编写的头文件或者项目内部的头文件。
    • 查找策略:编译器首先会在当前源文件所在的目录下查找指定的头文件,如果找不到,则会在其他系统路径下查找(类似于尖括号方式的查找策略)。

区别和注意事项

  • 系统头文件 vs 用户自定义头文件:尖括号 < > 用于标准和系统提供的头文件,双引号 " 用于用户自定义的头文件或项目内部的头文件。
  • 查找路径:尖括号方式会直接使用编译器预定义的系统路径进行查找,而双引号方式会先在当前源文件目录下查找,如果找不到才会进入系统路径查找。
  • 编译器特定的路径:具体的查找路径可能会因编译器而异,可以通过编译器文档或相关设置了解系统头文件的查找路径。

9.2头文件重复包含的问题

在大型项目中,一个头文件可能会被多个源文件包含,如果没有头文件保护,可能会导致以下问题:

  1. 重复定义:同一个符号在多个源文件中被定义多次。
  2. 编译错误:由于重复定义,编译器会报错,例如符号重定义等。
  3. 效率问题:重复包含可能导致编译时间增加,尤其是当头文件包含链较长时。

头文件保护的原理

头文件保护的原理是利用预处理器的条件编译指令,在第一次包含头文件时定义一个宏,在后续的包含过程中检查这个宏是否已经定义,如果已经定义,则跳过头文件的内容。

头文件保护的典型形式

头文件保护通常使用如下的形式:

#ifndef HEADER_FILE_NAME_H  // 如果未定义 HEADER_FILE_NAME_H 宏,则定义以下内容
#define HEADER_FILE_NAME_H  // 定义 HEADER_FILE_NAME_H 宏,防止下次重复包含

// 头文件内容

#endif // 结束头文件保护

详细解释

  1. #ifndef 指令:如果 HEADER_FILE_NAME_H 宏未定义,则编译器会继续处理 #ifndef#endif 之间的内容。
  2. #define 指令:在 #ifndef 的条件下,定义 HEADER_FILE_NAME_H 宏,防止下次重复包含。
  3. 头文件内容:在 #ifndef#endif 之间放置头文件的实际内容,包括函数声明、宏定义、结构体定义等。
  4. #endif 指令:结束条件编译块。

示例

假设有一个头文件 myheader.h,内容如下:

#ifndef MYHEADER_H
#define MYHEADER_H

// 在这里放置头文件内容
#include <stdio.h>

void printMessage() {
    printf("Hello, this is a message from myheader.h\n");
}

#endif // MYHEADER_H

在上面的示例中:

  • 第一次包含 myheader.h 时,MYHEADER_H 宏未定义,因此会定义 MYHEADER_H 宏并包含 stdio.h 头文件以及定义 printMessage() 函数。
  • 后续再次包含 myheader.h 时,MYHEADER_H 宏已经定义,预处理器会跳过 #ifndef#endif 之间的内容,避免重复定义和编译错误。

注意事项

  • 宏命名:通常使用头文件名大写并在末尾加 _H 或者类似的后缀来命名头文件保护宏,以确保唯一性。
  • 位置:头文件保护宏应该放在头文件的开头部分,并且保证每个头文件都有头文件保护。

#pragma once 的作用

#pragma once 是另一种防止头文件被多次包含的预处理器指令,它与传统的头文件保护 #ifndef#define#endif 的机制类似,但具有更简洁和直观的语法。#pragma once 指令告诉编译器确保当前文件只被包含一次。它的工作原理类似于传统的头文件保护,但更加简洁,并且由编译器直接支持,不依赖于预定义宏的命名约定。

使用方法和示例

使用 #pragma once 非常简单,只需在头文件的开头加上这条指令即可:

#pragma once

// 头文件内容
#include <stdio.h>

void printMessage() {
    printf("Hello, this is a message from myheader.h\n");
}

优点和注意事项

  1. 简洁性:相比传统的 #ifndef#define#endif 形式,#pragma once 更加简洁清晰,不需要额外定义宏和写多行代码。
  2. 可移植性#pragma once 是标准的 C 和 C++ 编译器特性,几乎所有主流的编译器都支持,因此具有良好的跨平台兼容性。
  3. 性能:虽然传统的头文件保护在性能上没有明显问题,但 #pragma once 有时可能比传统方法稍微快一些,因为编译器可以利用更高效的内部数据结构来处理。

注意事项

  • 编译器支持:虽然大多数现代编译器都支持 #pragma once,但在某些特定情况或旧版本编译器中可能不支持,这时候需要考虑使用传统的头文件保护方式。
  • 语法正确性:使用 #pragma once 时,确保它在文件的最顶部,并且没有任何其他代码或注释在它的前面,以保证编译器能正确识别。

🥇结语

通过对C语言预处理的深入探讨,我们可以看到预处理在代码开发中的重要性和广泛应用。熟练掌握预处理的使用,不仅能提高代码的可维护性和重用性,还能简化复杂项目的管理。希望本文的介绍能帮助读者更好地理解和应用C语言的预处理技术,在实际编程中发挥其最大效力。如果你有任何疑问或想法,欢迎在评论区与我们交流。

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

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

相关文章

基于Netty的自研流系统缓存实现挑战: 内存碎片与OOM困境

01 前言 Kafka 作为流处理平台&#xff0c;在实时流计算和在线业务场景&#xff0c;追尾读追求端到端低延迟。在离线批处理和削峰填谷场景&#xff0c;数据冷读追求高吞吐。两个场景都需要很好的数据缓存设计来支撑&#xff0c;Apache Kafka 的数据存储在本地文件&#xff0c…

从零开始学习嵌入式----C语言框架梳理与后期规划

目录 一、环境搭建. 二、见解 三、C语言框架梳理 四、嵌入式学习规划流程图&#xff08;学习顺序可能有变&#xff09; 一、环境搭建. C语言是一门编程语言&#xff0c;在学习的时候要准备好环境。我个人比较喜欢用VS,具体怎么安装请百度。学习C语言的时候&#xff0c;切忌…

Qt:12.输入类控件(QSpinBox-整数值输入的小部件、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件)

目录 一、QSpinBox-整数值输入的小部件&#xff1a; 1.1QSpinBox介绍&#xff1a; 1.2属性介绍&#xff1a; 1.3通用属性介绍&#xff1a; 1.4信号介绍&#xff1a; 二、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件&#xff1a; 2.1QDateEdit、QTimeEdit…

一、YOLO V10安装、使用、训练大全

YOLO V10安装、使用、训练大全 一、下载官方源码二、配置conda环境三、安装YOLOV10依赖四、使用官方YOLO V10模型1.下载模型2.使用模型2.1 图片案例 五、制作数据集1.数据集目录结构2.标注工具2.1 安装标注工具2.2 运行标注工具2.3 设置自动保存2.4 切换yolo模式2.5 开始标注2.…

【C++ | 继承】C++的继承详解 及 例子源码演示

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 本文未经允许…

C++视觉开发 七.模板匹配

模板匹配是一种基于图像处理的技术&#xff0c;用于在目标图像中寻找与给定模板图像最相似的部分。通过设定的模板&#xff0c;将目标图像与模板图像比较&#xff0c;计算其相似度&#xff0c;实现对目标图像的判断。 目录 一.手写数字识别 重要函数&#xff1a; 1.cv::glob…

Mac平台虚拟机 Parallels Desktop v19.4.1,支持M1/M2/M3芯片组

Parallels Desktop for Mac是功能强大灵活度高的虚拟化方案&#xff0c;无需重启即可在同一台电脑上随时访问Windows和Mac两个系统上的众多应用程序。从仅限于PC的游戏到生产力软件&#xff0c;Parallels Desktop都能帮您实现便捷使用。Parallels Desktop 是一款专业的Mac虚拟机…

虚拟机因断电进入./#状态解决办法

现象&#xff1a; 解决&#xff1a;先查看错误日志&#xff1a;journalctl -p err -b查看自己虚拟机中标黄部分的名字 之后运行&#xff1a;xfs_repair -v -L /dev/sda #这里sda用你自己标黄的 最后重启 reboot 即可。

ArcGIS的智慧与情怀

初识ArcGIS 在这个信息化的时代&#xff0c;ArcGIS如同一位智者&#xff0c;静静地伫立在地理信息系统的巅峰。初识它时&#xff0c;我仿佛走进了一片未知的领域&#xff0c;心中充满了好奇与期待。ArcGIS&#xff0c;这款专业的地理信息系统软件&#xff0c;凭借其强大的功能…

【k8s中安装rabbitmq】k8s中安装rabbitmq并搭建镜像集群-hostpath版

文章目录 简介一.条件及环境说明二.需求说明三.实现原理及说明四.详细步骤4.1.规划节点标签4.2.创建configmap配置4.3.创建三个statefulset和service headless配置4.4.创建service配置 五.安装完后的配置六.安装说明 简介 k8s集群中搭建rabbitmq集群服务一般都会用到pvc&#x…

传知代码-图神经网络长对话理解(论文复现)

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 情感识别是人类对话理解的关键任务。随着多模态数据的概念&#xff0c;如语言、声音和面部表情&#xff0c;任务变得更加具有挑战性。作为典型解决方案&#xff0c;利用全局和局部上下文信息来预测对话中每…

2024世界人工智能大会:AI产品技术与未来趋势的深度解析

随着2024年世界人工智能大会&#xff08;WAIC 2024&#xff09;在上海的圆满落幕&#xff0c;我们见证了人工智能技术的又一次飞跃。本次大会以“以共商促共享&#xff0c;以善治促善智”为主题&#xff0c;汇聚了全球顶尖的智慧&#xff0c;共同探讨了AI技术的未来趋势和应用前…

妙笔生词智能写歌词软件:创新助力还是艺术之殇?

在音乐创作日益普及和多样化的当下&#xff0c;各种辅助工具层出不穷&#xff0c;妙笔生词智能写歌词软件便是其中之一。那么&#xff0c;它到底表现如何呢&#xff1f; 妙笔生词智能写歌词软件&#xff08;veve522&#xff09;的突出优点在于其便捷性和高效性。对于那些灵感稍…

JVM内存泄露的ThreadLocal详解

目录 一、为什么要有ThreadLocal 二、ThreadLocal的使用 三、实现解析 实现分析 具体实现 Hash冲突的解决 开放定址法 链地址法 再哈希法 建立公共溢出区 四、引发的内存泄漏分析 内存泄漏的现象 分析 总结 错误使用ThreadLocal导致线程不安全 一、为什么要有Thr…

Test-Time Adaptation via Conjugate Pseudo-labels--论文笔记

论文笔记 资料 1.代码地址 https://github.com/locuslab/tta_conjugate 2.论文地址 https://arxiv.org/abs/2207.09640 3.数据集地址 论文摘要的翻译 测试时间适应(TTA)指的是使神经网络适应分布变化&#xff0c;在测试时间仅访问来自新领域的未标记测试样本。以前的TT…

【pytorch24】Visdom可视化

TensorboardX pytorch有一个工具借鉴了tensorboard pip install tensorboardX 有查看变量的数值、监听曲线等功能 如何使用 新建SummaryWriter()实例 要把监听的数据&#xff0c;比如说要监听dummy_s1[0]&#xff08;y 坐标&#xff09;存放到data/scalar1中&#xff0c;…

普中51单片机:中断系统与寄存器解析(六)

文章目录 引言中断流程图中断优先级下降沿中断结构图中断相关寄存器IE中断允许寄存器&#xff08;可位寻址&#xff09;XICON辅助中断控制寄存器&#xff08;可位寻址&#xff09;TCON标志控制寄存器SCON串行口控制寄存器 中断号中断响应条件中断函数代码模板电路图开发板IO连接…

洁净车间的压缩空气质量如何检测(露点、水油、粒子、浮游菌)

通常一个空压机站的设备即为一个狭义的压缩空气系统&#xff0c;下图为一个典型的压缩空气系统流程图&#xff1a; 气源设备&#xff08;空气压缩机&#xff09;吸入大气&#xff0c;将自然状态下的空气压缩成为具有较高压力的压缩空气&#xff0c;经过净化设备除去压缩空气中的…

新手如何正确学习Python?分享我是如何2个月熟练掌握Python的!学习大纲+学习方式+学习资料 汇总!

前言 一直以来都有很多想学习Python的朋友们问我&#xff0c;学Python怎么学&#xff1f;爬虫和数据分析怎么学&#xff1f;web开发的学习路线能教教我吗&#xff1f; 我先告诉大家一个点&#xff0c;不管你是报了什么培训班&#xff0c;还是自己在通过各种渠道自学&#xff…

[C++][ProtoBuf][Proto3语法][三]详细讲解

目录 1.默认值2.更新消息1.更新规则2.保留字段reserved 3.未知字段1.是什么&#xff1f;2.未知字段从哪获取 4.前后兼容性5.选项option1.选项分类2.常用选项列举3.设置自定义选项 1.默认值 反序列化消息时&#xff0c;如果被反序列化的⼆进制序列中不包含某个字段&#xff0c;…