C语言-预处理详解

文章目录

  • 🎯引言
  • 👓预处理详解
    • 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语言的预处理技术,在实际编程中发挥其最大效力。如果你有任何疑问或想法,欢迎在评论区与我们交流。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Redis实现消息队列:List、Pub/Sub和Stream的实践

摘要 Redis是一个高性能的键值存储系统&#xff0c;它的多种数据结构使其成为实现消息队列的理想选择。本文将探讨如何使用Redis的List、Pub/Sub和Stream数据结构来实现一个高效的消息队列系统。 1. 消息队列的基本概念 消息队列是一种应用程序之间进行通信的机制&#xff0…

解锁算力新极限,Xilinx UltraScale+赋能的高性能低延时FPGA加速卡

01、产品概述 AiHPC-V9P 是一款基于 AMD Virtex UltraScale FPGA VU9P 的 PCIe Gen3.0 x16 接口智能网卡&#xff0c;具有最大2*200GbE /或者16*10GbE(典型应用&#xff09;接入容量的高性能低延时智能网卡。 对外接口支持两组QSFP-DD 最高25Gb/s x8Lane 光口接入&#xf…

Java基础-组件及事件处理(中)

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 BorderLayout布局管理器 说明&#xff1a; 示例&#xff1a; FlowLayout布局管理器 说明&#xff1a; …

我跟ai学web知识点:“短链接”

我跟ai学web知识点&#xff0c;短链接不是“免费午餐”。 (笔记模板由python脚本于2024年07月08日 12:44:47创建&#xff0c;本篇笔记适合喜欢Web知识的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经…

Windows下编译OpenSSL静态库

目录 1. 版本与下载地址 2. 下载与安装VS2015 3. 下载与安装Perl 4. 测试ActivePerl是否安装正确 5. 下载OpenSSL 6. 编译32位OpenSSL静态库 6.1 解压openssl-1.0.2l.tar.gz 6.2 打开VS2015 x86本机工具命令提示符 6.3 输入命令进入到openssl的目录中 6.4 执行配置命…

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视 随着全球数字经济的蓬勃发展&#xff0c;电子游戏行业也迎来了前所未有的机遇。巴基斯坦&#xff0c;这个拥有庞大人口基数和日益增长的消费能力的国家&#xff0c;其电子游戏市场潜力巨大。本文旨在探讨巴基斯坦电子游戏…

tomcat安装

tomcat tomcat和php一样&#xff0c;都是用来处理动态页面的。 tomcat也可以作为web应用服务器&#xff0c;开源的。 php .php tomcat .jsp nginx .html tomcat是用java代码写的程序&#xff0c;运行的是java的web应用程序 tomcat的特点和功能&#xff1a; 1、servlet容…

首席数据官CDO证书报考指南:方式、流程、适考人群与考试难度

在信息泛滥的今天&#xff0c;数据已转变为企业不可或缺的宝贵资源。 面对海量的信息&#xff0c;如何提炼出价值&#xff0c;为企业带来实质性的收益&#xff1f;首席数据官&#xff08;CDO&#xff09;认证的出现正是为了满足这一需求&#xff0c;它不仅是个人专业能力的体现…

AI Earth——1990-2022年全国月度气象数据检索应用app

应用结果 代码 #导入安装包 import os import json import datetime import streamlit as st import streamlit.components.v1 as components import traceback from PIL import Imageimport aie#读取当前目录的内容 current_work_dir = os.path.dirname(__file__) #添加地图…

简单的Java面向对象小游戏并使用三层架构(表示层、业务逻辑层、数据访问层)

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

AE-时间轴的基础操作

目录 预览&#xff08;快捷键空格&#xff09; 调整时间线显示比例&#xff08;Alt鼠标滚轮&#xff09; 控制预览长度&#xff08;B/N&#xff09; 逐帧移动&#xff08;笔记本&#xff1a;按住fn上下方向键&#xff09; 视频剪切&#xff08;ctrlshiftD&#xff09; 剪…

数据结构:顺序表+链表

数据结构&#xff1a;顺序表链表 一。顺序表&#xff1a; 首先在了解顺序表和链表之前&#xff0c;先了解一下线性表&#xff0c;**线性表&#xff08;linear list&#xff09;**是n个具有相同特征元素的有限序列 &#xff0c;在逻辑上是线性结构&#xff0c;也就是一条连续的…

深入剖析预处理

目录 1.预定义符号 2.#define 定义常量 3.#define定义宏 4.带有副作用的宏参数 5.宏替换的规则 6.宏函数的对比 7.#和## 7.1 #运算符 7.2 ## 运算符 8.命名约定 9.#undef 10.命令行定义 11.条件编译 12.头文件的包含 12.1 头文件被包含的方式&#xff1a; 12.1…

React setState

老生常谈之setState 是同步的还是异步的&#xff1f; 设想setState是同步的&#xff0c;那也就是每次调用setState都要进行新旧虚拟DOM的对比&#xff0c;然后将差异化的dom更新到页面上&#xff0c;性能损耗很大 所以react把setState设置为了异步&#xff0c;当状态更新时不…

基于springboot+vue实现的厨艺交流平台(文末源码+Lw)093

93基于SpringBootVue的实现的厨艺交流平台&#xff08;源码数据库万字Lun文流程图ER图结构图演示视频软件包&#xff09; 系统功能&#xff1a; 这次开发的厨艺交流平台功能有个人中心&#xff0c;食材分类管理&#xff0c;用户管理&#xff0c;菜品分类管理&#xff0c;菜谱信…

【Axure】产品原型如何在谷歌浏览器中打开

作为一名前端开发来说&#xff0c;在拿到产品的原型图后&#xff0c;如何打开&#xff1f;直接用谷歌浏览器打开&#xff0c;是打不开的&#xff0c;需要安装对应的插件。但是谷歌插件市场在不翻墙的情况下&#xff0c;是没有办法直接打开的&#xff0c;分享一种超级简单的方法…

Softmax回归中的损失函数

目录 一、损失函数介绍&#xff1a; 因为Softmax回归时逻辑回归的推广&#xff0c;所以Softmax回归的损失函数是逻辑回归损失函数的推广。 原文链接&#xff1a;逻辑回归中的损失函数 一、损失函数介绍&#xff1a; 与回归问题成本函数不同的是&#xff0c;Softmax回归模型&a…

python-小杨的储蓄(赛氪OJ)

题目描述 小杨共有 N 个储蓄罐&#xff0c;编号从 0 到 N−1。从第 1 天开始&#xff0c;小杨每天都会往存钱罐里存钱。具体来说&#xff0c;第 i 天他会挑选一个存钱罐 ai​&#xff0c;并存入 i 元钱。过了 D 天后&#xff0c;他已经忘记每个储蓄罐里都存了多少钱了&#xff…

如何网页在线编辑微软Office Word,并导出为PDF格式。

随着互联网技术的不断发展&#xff0c;越来越多的企业开始采用在线办公模式&#xff0c;微软Office Word 是最好用的文档编辑工具&#xff0c;然而doc、docx、xls、xlsx、ppt、pptx等格式的Office文档是无法直接在浏览器中直接打开的&#xff0c;如果可以实现Web在线预览编辑Of…

Vue3 pdf.js将二进制文件流转成pdf预览

好久没写东西&#xff0c;19年之前写过一篇Vue2将pdf二进制文件流转换成pdf文件&#xff0c;如果Vue2换成Vue3了&#xff0c;顺带来一篇文章&#xff0c;pdf.js这个东西用来解决内网pdf预览&#xff0c;是个不错的选择。 首先去pdfjs官网&#xff0c;下载需要的文件 然后将下载…