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");
#elseprintf("This compiler does not conform to the ANSI C standard.\n");
#endifreturn 0;
}

输出示例:

This compiler conforms to the ANSI C standard.

2.#define定义常量

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

2.1 定义数值常量

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

include <stdio.h>#define PI 3.14159int 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) #xint 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##bint main() {int xy = 100;printf("%d\n", CONCAT(x, y)); // 输出 100return 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)); // 预期输出:10return 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 100int main() {printf("MAX: %d\n", MAX); // 输出 "MAX: 100"#undef MAX#ifdef MAXprintf("MAX is defined.\n");#elseprintf("MAX is not defined.\n"); // 输出 "MAX is not defined."#endifreturn 0;
}

在这个示例中:

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

避免命名冲突

#include <stdio.h>#define VALUE 10void first_function() {printf("VALUE in first_function: %d\n", VALUE); // 输出 "VALUE in first_function: 10"#undef VALUE
}void second_function() {#define VALUE 20printf("VALUE in second_function: %d\n", VALUE); // 输出 "VALUE in second_function: 20"#undef VALUE
}int main() {first_function();second_function();#ifdef VALUEprintf("VALUE in main: %d\n", VALUE);#elseprintf("VALUE is not defined in main.\n"); // 输出 "VALUE is not defined in main."#endifreturn 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_MODEprintf("Debug mode is enabled.\n");#elseprintf("Debug mode is disabled.\n");#endifreturn 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
#endifint main() {printf("This message will always be displayed.\n");// 如果 RELEASE_MODE 宏未被定义,则输出消息#ifndef RELEASE_MODEprintf("Not in release mode.\n");#endifreturn 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 2int main() {// 根据 DEBUG_MODE 和 DEBUG_LEVEL 的定义输出不同级别的调试信息#if defined(DEBUG_MODE) && DEBUG_LEVEL > 2printf("Detailed debug information.\n");#elif defined(DEBUG_MODE) && DEBUG_LEVEL > 0printf("Basic debug information.\n");#elseprintf("No debug information.\n");#endifreturn 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_MODEprintf("Release mode is enabled.\n");#elseprintf("Debug mode is enabled.\n");#endifreturn 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://xiahunao.cn/news/3227117.html

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

相关文章

windows远程连接virtualbox的ubuntu问题

一.安装vritualbox ubuntu&#xff0c;18、22版本比较稳定 1.推荐使用ubuntu22版本 2.ubuntu24对内存要求较高至少4G&#xff0c;时不时会死机&#xff0c;安装老是崩溃&#xff0c;恢复不了&#xff0c;如果电脑性能强悍那可以尝试。 3.ubuntu18 对vscode最高只能支持1.85.…

Spring中的工厂模式详解及应用示例

1. Spring中的BeanFactory BeanFactory是一个接口&#xff0c;表示它是一个工厂&#xff0c;负责生产和管理bean。在Spring中&#xff0c;BeanFactory是IOC容器的核心接口&#xff0c;定义了管理Bean的通用方法&#xff0c;如 getBean 和 containsBean。 BeanFactory与IOC容器…

海外视频媒体发布/发稿:如何在国外媒体以视频的形式宣发

1. 背景介绍 在如今数字化时代&#xff0c;每个国家都拥有着各自的视频媒体平台&#xff0c;而主流媒体也都纷纷加入了视频发布的行列。视频媒体的宣发形式主要包括油管Youtube等视频分享平台&#xff0c;以及图文配合的发布方式。通过在视频中夹带链接&#xff0c;媒体可以以…

C++ 宏和内联、范围for、nullptr

C 宏函数和内联函数、范围for、nullptr 宏函数和内联函数 ​ 函数重载中提到过&#xff0c;一个程序编译需要经过四个阶段&#xff0c;第一个阶段预处理中有一个操作是宏替换。由于是替换&#xff0c;所以宏不建立栈帧&#xff0c;且没有数据类型的限制&#xff0c;能够提高我…

CCSI: 数据无关类别增量学习的持续类特定印象| 文献速递-基于深度学习的多模态数据分析与生存分析

Title 题目 CCSI: Continual Class-Specific Impression for data-free class incremental learning CCSI: 数据无关类别增量学习的持续类特定印象 01 文献速递介绍 当前用于医学影像分类任务的深度学习模型表现出令人鼓舞的性能。这些模型大多数需要在训练之前收集所有的…

element plus 实现跨页面+跨tab栏多选

文章目录 element plus 层面数据层面 菜鸟好久没写博客了&#xff0c;主要是没遇见什么很难的问题&#xff0c;今天碰见了一个没有思路的问题&#xff0c;解决后立马来和大家伙分享了&#xff01; 菜鸟今天要实现一个需求&#xff0c;就是&#xff1a;实现跨页面跨 tab栏 多选…

解锁算力新极限,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…

《梦醒蝶飞:释放Excel函数与公式的力量》10.4 IMREAL函数

第四节 10.4 IMREAL函数 10.4.1 函数简介 IMREAL函数是Excel中的一个工程函数&#xff0c;用于提取复数的实部。在复数运算中&#xff0c;实部是复数的一部分&#xff0c;表示没有虚部参与的部分。IMREAL函数提供了一个简单的方法来获取复数的实部&#xff0c;便于进一步计算…

Docker 使用基础(2)—镜像

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;秒針を噛む—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 4:20 &#x1f504; ◀️ ⏸ …

linux下安装cutecom串口助手;centos安装cutecom串口助手;rpm安装包安装cutecom串口助手

在支持apt-get的系统下安装 在终端命令行中输入&#xff1a; sudo apt-get install cutecom 安装好后输入 sudo cutecom 就可以了 关于如何使用&#xff0c;可以看这个https://www.cnblogs.com/xingboy/p/14388610.html 如果你的电脑不支持apt-get。 那我们就通过安装包…

7.10号小项目部分说明

总体说明 糖锅小助手 我这次主要对上次糖锅小助手界面添加了一个侧边栏&#xff08;侧边输入框放置了三个按钮&#xff0c;可以跳转到其他ai聊天界面&#xff0c;还可以退出聊天界面回到登录界面&#xff09;和一个日期输入框&#xff08;日期输入框获取时间&#xff0c;根据时…

常见摄像头模块性能对比

摄像头模块在现代电子设备与嵌入式开发中扮演着重要角色&#xff0c;从智能手机到安全监控系统&#xff0c;再到机器人视觉系统&#xff0c;它们无处不在。以下是一些常见的摄像头模块及其特点的对比&#xff1a; OV2640 分辨率&#xff1a;最高可达200万像素&#xff08;1600x…

GPT-4o不香了, Claude3.5 Sonnet来了,直接免费可用

大家好&#xff01;我是YUAN。 刚刚&#xff01; AI领域又迎来了一场新的风暴——Anthropic公司发布了全新的Claude 3.5 Sonnet模型&#xff0c;这一消息如同一颗重磅炸弹&#xff0c;震撼了整个科技界。 Claude 3.5 Sonnet与GPT-4o对比 性能飞跃&#xff1a;超越GPT-4o Cl…

SA8317E单通道 2.7-15.0V 持续电流 2.5A H 桥驱动芯片

描述 SA8317E 是为消费类产品&#xff0c;小家电和其他 低压或者电池供电的运动控制类应用提供 了一个集成的电机驱动器解决方案。此器 件能够驱动一个直流电机&#xff0c;由一个内部电 荷泵生成所需的栅极驱动电压电路和 4 个 功率 NMOS 组成 H 桥驱动&#xff0c;集成了电…

01 企业网站架构部署与优化之Apache配置与应用

目录 3.1 Apache连接保持 3.2 Apache的访问控制 3.2.1 客户机地址限制 3.2.2 用户授权限制 1. 创建用户认证数据文件 2. 添加用户授权配置 3. 验证用户访问授权 3.3 Apache日志分割 1. Apache自带rotatelogs分割工具 2. 使用第三方工具cronolog 3.4 AWStats日志分析 3.4.1 …

ns3学习笔记(四):路由概述

基于官网文档的 Routing Overview 部分详细研究一下ns3中路由是怎么工作的 文档链接16.4. Routing overview — Model Library 一、概述 NS3整体的工作架构如下&#xff1a; 路由部分的工作架构如下&#xff1a; 路由部分目前大多数用到的算法都包含在Ipv4RoutingProtocol部分…

【排序 - 插入排序 和 希尔排序】

插入排序&#xff08;Insertion Sort&#xff09;是一种简单直观的排序算法&#xff0c;它的工作原理是逐步构建有序序列。在排序过程中&#xff0c;它将未排序的元素逐个插入到已排序的部分中&#xff0c;从而在每次插入时扩展已排序序列的长度。 原理介绍 插入排序的基本思…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验17 开放最短路径优先OSPF

一、实验目的 1.验证OSPF协议的作用&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.验证OSPF协议的作用。 四、实验步骤 1.构建网…

Python办公自动化:增值税发票批量识别和核验

腾讯云免费体验地址: https://console.cloud.tencent.com/api/explorer?Product=ocr&Version=2018-11-19&Action=VatInvoiceVerifyNew 首先进行识别,这里以python为例子 # -*- coding: utf-8 -*- import jsonfrom tencentcloud.common.common_client import Commo…

随身WiFi市场乱象横生,随身WiFi测评最好的格行随身WiFi如何引领变革?

在当今随身WiFi市场乱象频发、内卷严重的背景下&#xff0c;消费者对于产品的性能与商家是否会后台割韭菜依旧存疑&#xff0c;尤其是“随身WiFi到底卡不卡&#xff1f;”的问题&#xff0c;成为了广大消费者关注的重点。然而&#xff0c;在众多品牌中&#xff0c;格行随身WiFi…