【C语言】C预处理器(宏、文件包含、条件编译...)

news2024/11/24 4:26:13

  • 一、C语言编译的预处理阶段
    • 1.1 C语言的编译过程
    • 1.2 C语言编译的预处理
  • 二、C语言 宏
    • 2.1替换常量
    • 2.2函数宏
    • 2.3 字符串化和连接:#和##
    • 2.4 变参宏
  • 三、文件包含:#include
    • 3.1 写法
    • 3.2 头文件的作用——声明
    • 3.3 头文件和extern 、static
  • 四、 其他指令
    • 4.1 #undef
    • 4.2 条件编译
    • 4.3 预定义宏
    • 4.4 #line和#error
    • 4.5 #praga
    • 4.6 头文件如何命名

一、C语言编译的预处理阶段

1.1 C语言的编译过程

C语言的编译过程是将高级的C语言代码转换为可执行的机器代码的过程。

C语言的翻译过程包括预处理、编译、汇编、链接、加载和执行阶段。每个阶段都有特定的任务和目标

  1. 预处理(Preprocessing)
    在翻译过程的第一阶段,称为预处理阶段,预处理器将对源代码进行处理。预处理器会根据预处理指令(以#开头的命令)执行相应的操作。这些指令可以用于包含其他文件、宏替换、条件编译等。预处理器会生成一个扩展后的源代码文件,供下一阶段使用。

  2. 编译(Compilation):
    在编译阶段,编译器将预处理后的源代码转换为汇编语言代码。编译器会进行词法分析和语法分析,检查代码的语法和语义是否正确。如果发现错误,编译器会生成相应的错误信息。如果没有错误,编译器会生成中间代码(如汇编语言代码或机器无关的代码)。

  3. 汇编(Assembly):
    在汇编阶段,汇编器将中间代码(汇编语言代码)转换为可重定位的机器代码,也就是目标文件。目标文件包含了机器指令和符号信息。符号信息用于在链接阶段解析函数和变量的引用。

  4. 链接(Linking):
    在链接阶段,链接器将多个目标文件和库文件(如标准库)合并为一个可执行文件。链接器会解析目标文件中的符号引用,将其与符号定义进行匹配。如果找不到符号定义,链接器会生成未解析的符号错误。链接器还会处理重定位,将符号引用替换为正确的地址。

  5. 加载(Loading):
    在加载阶段,操作系统将可执行文件加载到内存中,并为其分配内存空间。加载器会解析可执行文件的头部信息,确定程序入口点,并建立程序的执行环境。加载器还会处理动态链接,将程序与共享库进行链接。

  6. 执行(Execution):
    在执行阶段,处理器按照指令逐条执行可执行文件中的机器指令。程序的执行顺序和行为取决于代码的逻辑和算法。程序执行期间会访问内存、执行算术和逻辑操作,以及与外部设备进行交互。最终,程序会产生所期望的结果或效果。

本文介绍C语言的预处理及预处理指令

1.2 C语言编译的预处理

预处理是C语言翻译过程中的第一阶段,它负责对源代码进行处理和转换,以生成供编译器使用的扩展后的源代码

预处理器根据以#开头的预处理指令执行相应的操作,下面将详细介绍预处理的过程和常用的预处理指令。

  1. 宏替换(Macro substitution):
    宏是一种预处理指令,用于将代码中的标识符替换为相应的代码片段。定义宏使用#define指令,例如:
#define PI 3.14159

在预处理阶段,所有的PI标识符都会被替换为3.14159。宏还可以带有参数,被称为函数宏,例如:

#define SQUARE(x) ((x) * (x))

这个宏可以计算一个数的平方,在预处理阶段,SQUARE(5)会被替换为((5) * (5)),即25

  1. 文件包含(File inclusion):
    预处理器提供了文件包含的功能,可以将其他文件的内容插入到当前源文件中。使用#include指令,例如:
#include <stdio.h>

这个指令会将标准库头文件stdio.h的内容包含到当前源文件中。文件包含可以是尖括号<>形式或双引号""形式,区别在于搜索头文件的路径不同。

  1. 条件编译(Conditional compilation):
    条件编译指令用于根据条件选择性地编译代码块。常用的条件编译指令有#ifdef#ifndef#if#elif#endif。例如:
#ifdef DEBUG
    printf("Debug mode\n");
#else
    printf("Release mode\n");
#endif

如果定义了DEBUG宏,编译器会编译第一行代码,否则会编译第三行代码。

  1. 注释删除(Comment removal):
    预处理器会删除源代码中的注释,以提高编译速度和减少生成的中间代码的大小。C语言有两种注释形式:/* ... */多行注释和//单行注释。

  2. 其他预处理指令
    预处理器还提供了其他一些常用的指令,如#undef用于取消宏定义,#error用于生成错误信息,#pragma用于向编译器发出特定指令等。

预处理阶段的输出是经过预处理后的源代码,它将作为编译器的输入进行下一阶段的处理。预处理可以扩展源代码、处理宏替换、包含其他文件、条件编译等。

二、C语言 宏

宏(Macro)是C语言中的一种预处理指令,用于将代码中的标识符替换为相应的代码片段。宏定义使用#define指令。

宏的替换是简单的文本替换,它在预处理阶段进行。当编译器遇到宏名称时,会将其替换为定义中指定的代码片段。宏替换是直接替换,没有类型检查或语法分析

宏的主要目的是提供一种简便的方式来定义和使用可重复的代码块。通过使用宏,可以在代码中使用自定义的符号来表示一段代码,从而增加代码的可读性和灵活性。

宏可以具有不同的形式和功能。一些常见的宏使用包括:

  • 替换常量:使用宏定义来表示常量值,以方便修改和维护。
  • 函数宏:使用宏定义来表示一段代码片段,类似于函数的调用。函数宏可以带有参数,用于在代码中执行一系列操作。
  • 条件编译:使用宏定义来选择性地编译代码块,以便在不同的编译条件下执行不同的代码。(放到第三节将)
  • 字符串化和连接操作:通过使用特殊的预处理运算符###,可以在宏定义中对参数进行字符串化和连接操作。

2.1替换常量

如:

#define PI 3.14159
#define IP  "10.0.0.1"

有人说:宏定义时,不用关心类型,只是简单的文本替换。它的类型在调用这个宏的地方的参数类型决定。

但事实上,宏的类型与后面他要替换的内容有关,比如上面的第一个宏,默认是double型,第二个宏默认是char *。在使用的时候应该根据宏替代的文本的默认类型来正确使用。

printf("%s\n",IP);  //正确
printf("%s\n",PI);  //错误

2.2函数宏

#define SQUARE(x) ((x) * (x))

这个宏定义了一个函数宏,用于计算一个数的平方。在预处理阶段,所有的SQUARE(x)会被替换为((x) * (x)),然后编译器将继续处理替换后的代码。

它的返回值类型与传递的实参值相同(当然这个函数宏的返回值不能是个字符串,关于字符串,见2.3小节),传给它整数类型,返回值就是整数类型,传浮点数,返回值就死浮点型。这一点似乎比写一个相同功能的函数更有效。

应该使用函数、还是函数宏,要注意:

  1. 性能:函数宏在编译时展开,以文本替换的方式直接插入代码,因此没有函数调用的开销。这使得函数宏在一些性能敏感的场景中可能更有效率。然而,这也可能导致代码膨胀,增加可执行文件的大小。

  2. 参数求值:函数宏的参数是在宏展开时进行替换的,而函数的参数是在运行时求值的。如果参数求值具有副作用或对性能有重要影响,函数宏可能更合适。然而,如果需要确保参数只求值一次或需要动态计算参数,函数可能更适合。

  3. 可读性和维护性:函数宏可能使代码更简洁,但过度使用宏可能会降低代码的可读性和维护性。函数通常具有更明确的语义,并且可以在编译时进行类型检查,使得代码更易于理解和调试。如果可读性和可维护性对项目至关重要,函数可能更可取。

  4. 代码重用:函数可以被多次调用,实现代码的重用性。如果有多个地方需要执行相同的操作,函数通常更适合。函数宏在每个使用处都会进行代码展开,可能导致重复的代码。

综上所述,使用函数宏还是函数应根据具体情况进行权衡。在性能敏感、参数简单且无副作用的情况下,函数宏可能更合适。而在需要明确语义、可读性和维护性重要的情况下,函数可能更适合。

2.3 字符串化和连接:#和##

在C语言的宏定义中,#(井号)和##(双井号)是两个特殊的预处理运算符(不是define前面的那个#),它们在宏展开过程中具有不同的作用。下面将详细介绍它们的用途和功能。

(1)# 运算符(字符串化操作符):

在宏定义中,#运算符将宏参数转换为字符串。它可以用于将宏参数的值转换为对应的字符串常量。例如:

#define STR(x) #x

printf("%s\n", STR(Hello)); // 输出 "Hello"

在上述示例中,宏定义了一个STR宏,它将传递给宏的参数x转换为字符串常量。当调用STR(Hello)时,宏展开为"Hello",最终在printf函数中输出字符串Hello

注意,在使用#运算符时,宏参数在宏定义中必须通过另一个宏进行替换。

再来一个例子:


#define PRINT_a(x) printf("x * x = %d\n",(x)*(x));
#define PRINT_b(x) printf(""#x" * "#x" = %d\n",(x)*(x));
...
PRINT_a(4);
PRINT_b(4);
...

输出为:

在这里插入图片描述

(2) ## 运算符(连接操作符):

##运算符用于连接两个标识符,使它们成为一个单独的标识符。它可以在宏定义中用于生成新的标识符。例如:

#define CONCAT(a, b) a##b
int xy = CONCAT(10, 20); // 展开为 int xy = 1020;

在上述示例中,宏定义了一个CONCAT宏,它将两个参数ab连接为一个新的标识符。当调用CONCAT(10, 20)时,宏展开为1020的连接,最终生成一个名为xy的整型变量。

使用##运算符时,注意要确保连接的两个标识符合法并且能够正确组合在一起。否则,可能会导致语法错误或意外的结果。

总结来说,#运算符用于将宏参数转换为字符串,而##运算符用于连接标识符。它们是宏定义中的特殊预处理运算符,为宏的灵活性和功能增添了更多的可能性。然而,在使用它们时需要小心,确保正确使用并避免潜在的问题。

2.4 变参宏

变参宏允许宏在不同的调用中接受可变数量的参数。变参宏提供了一种灵活的方式来处理不确定数量的参数,并在预处理阶段展开为相应的代码。

在传统的宏定义中,我们只能指定一个固定数量的参数。但是,通过使用特殊的预处理运算符__VA_ARGS__,我们可以定义接受可变数量参数的宏。__VA_ARGS__表示宏的参数列表中的可变部分。

常见的2中形式:

#include <stdio.h>

#define PR(...) printf(__VA_ARGS__)
#define PP(x,...) printf("第 " #x " 个调用:"__VA_ARGS__)

int main() {

	int x = 888;
	PR("helo\n");
	PR("x=%d\n", x);
	
	PP(1, "x = %d\n", x);
	PP(2, "x^2 = %d\n", x*x);

	return 0;
}

输出:

helo
x=8881 个调用:x = 8882 个调用:x^2 = 788544

一个实际应用:打印系统状态

[20:37:16:118]  Wifi state changed! Now control_flag=0
[20:37:16:168] 
[20:37:16:168] ===============0============
[20:37:16:177] 
[20:37:16:371] [6986] INFO Wifista:  sysTicks: 1117762488, kernelTicks: 6986
[20:37:16:382] [6988] INFO Wifista:  IsWifiActive: 0
[20:37:16:382] [6990] INFO Wifista:  EnableWifi: 0
[20:37:16:382] [6992] INFO Wifista:  IsWifiActive: 1
[20:37:16:405] [7003] INFO Wifista:  GetDeviceMacAddress: errCode:0, FormatMacAddress:28:
[20:37:16:410] [7006] INFO Wifista:  OnWifiScanStateChanged 101, state = 0, size = 0
[20:37:18:021] [wifi_service]: dispatch scan event.
[20:37:18:023] [7812] INFO Wifista:  OnWifiScanStateChanged 101, state = 1, size = 9

三、文件包含:#include

#include是一个预处理指令,用于将外部文件的内容包含到源代码中。它通常用于包含头文件,以便在程序中可以访问其他文件中定义的函数、变量和宏等。

使用#include时,会将对应的头文件的所有内容包含到你的源文件中,位置是你的include的位置,所以,include通常放在源文件开头。

不过,编译的时候,只对你实际使用到的额代码进行编译和链接。

使用include包含头文件的好处是:简化了多个文件的管理,但是会增加编译时间。

3.1 写法

#include指令后面紧跟着文件名,可以使用双引号或尖括号将文件名括起来,具体取决于你要包含的文件类型和位置。有两种常见的用法:

  1. 使用双引号""

    #include "filename.h"
    #include "/usr/xxx/xx.h"
    

    这种用法通常用于包含项目内部的头文件。编译器首先在当前目录中(或指定的路径)查找文件,如果找不到,则在其他指定的搜索路径中查找。

  2. 使用尖括号<>

    #include <filename.h>
    

    这种用法通常用于包含系统或标准库的头文件。编译器将根据预定义的搜索路径来查找文件。

3.2 头文件的作用——声明

头文件用于存储函数、变量、宏定义和结构体等的声明和定义。头文件通常使用.h作为文件扩展名,并且包含在源代码文件中,以便在程序中可以引用和使用其中定义的内容。

头文件的作用:

  • 函数声明;
  • 全局变量声明;
  • 结构体定义;
  • 宏定义。

不要在头文件中定义函数和变量,这样会导致重复定义变量的错误。

  • 当多个源代码包含同一个头文件时,每个源文件都会复制头文件中的内容,这会导致编译链接阶段的多个变量、函数同名。
  • 为什么可以定义结构体呢:
    • 结构体的定义只是定义了结构体的模板,有哪些成员,及其类型;而不是结构体变量的定义。
  • 为什么宏定义可以放在头文件呢:
    • 宏定义是文本的替换,在预处理阶段就在调用宏的地方进行文本替换了,不存在什么重复定义的问题。
  • 宏定义和结构体定义可以说是一种“声明”,并没有定义实际的内容。

展开说:

  1. 函数声明:函数声明描述了函数的名称、参数列表和返回类型,但不包含函数的实际实现代码。它的目的是为了告诉编译器有关函数的信息,以便在编译阶段进行类型检查和符号解析。函数声明的一般形式为:

    返回类型 函数名(参数列表);
    
  2. 变量声明:变量声明描述了变量的名称和类型,用于告诉编译器有关变量的信息。变量声明使得其他源文件可以访问这些变量,而不需要了解其具体的定义。变量声明的一般形式为:

    extern 数据类型 变量名;
    
  3. 宏定义:宏定义使用预处理指令#define定义一个标识符代表某个值或代码片段。头文件中的宏定义可以用于定义常量、简化代码、创建函数宏等。它的一般形式为:

    #define 标识符 替换文本
    
  4. 结构体定义:结构体定义描述了一种自定义的数据类型,可以包含多个不同类型的成员变量。头文件中的结构体定义使得其他源文件可以使用这个结构体类型。结构体定义的一般形式为:

    typedef struct {
        数据类型 成员1;
        数据类型 成员2;
        // ...
    } 结构体类型名;
    

3.3 头文件和extern 、static

【C语言】存储类别(作用域、链接、存储期)、内存管理和类型限定符(主讲const)

四、 其他指令

4.1 #undef

#undef 是一个预处理指令,用于取消定义(undefine)先前通过 #define 指令定义的宏。

但有时候可能需要取消对某个标识符的定义,这时就可以使用 #undef 来实现。

下面是 #undef 的使用示例:

#include <stdio.h>

#define MAX_VALUE 100  // 定义一个宏

int main() {
    printf("Max value: %d\n", MAX_VALUE);  // 输出 Max value: 100

    #undef MAX_VALUE  // 取消对宏的定义

    // 下面这行代码会导致编译错误,因为 MAX_VALUE 没有定义
    // printf("Max value: %d\n", MAX_VALUE);

    return 0;
}

在上述示例中,首先通过 #define 定义了一个宏 MAX_VALUE,它被赋值为 100。然后在 main 函数中使用了该宏。但是,通过 #undef MAX_VALUE 指令取消了对宏 MAX_VALUE 的定义。因此,如果在取消定义之后尝试使用 MAX_VALUE,会导致编译错误。

使用 #undef 可以有效地取消宏的定义,使其在取消定义之后不再可用。这样可以提供更大的灵活性,在需要时可以取消宏的定义,以便在后续代码中使用不同的定义或避免命名冲突。

4.2 条件编译

条件编译是一种在源代码中根据条件选择性地编译特定部分的机制。它使用预处理指令来指定编译时应该包含或排除哪些代码。

条件编译通常用于处理不同平台、不同编译器或不同配置下的代码差异。它允许在同一份源代码中包含多个代码分支,每个分支根据不同的条件进行选择性编译。

在 C 和 C++ 中,常用的条件编译指令是 #ifdef#ifndef#if#elif#else#endif

下面是一些常见的条件编译指令及其使用示例:

  1. #ifdef#ifndef#ifdef 指令检查某个标识符是否已经定义,#ifndef 指令检查某个标识符是否未定义。如果条件为真,则执行对应的代码块。

    #ifdef DEBUG
        // 在 DEBUG 模式下执行的代码
    #endif
    
    #ifndef DEBUG
        // 在非 DEBUG 模式下执行的代码
    #endif
    
  2. #if#elif#else#if 指令允许在编译时进行条件判断,根据表达式的值来选择性地编译代码。#elif 指令用于指定额外的条件,#else 指令用于指定除前面条件外的默认情况。

    #if defined(PLATFORM_WINDOWS)
        // Windows 平台下的代码
    #elif defined(PLATFORM_LINUX)
        // Linux 平台下的代码
    #else
        // 默认情况下的代码
    #endif
    

条件编译指令允许根据条件来选择性地编译代码,从而在不同情况下实现不同的代码逻辑。这对于处理跨平台开发、调试代码或根据不同的配置构建不同的版本非常有用。但应注意,过度使用条件编译可能导致代码可读性降低和维护困难,因此应谨慎使用,并根据实际需要进行合理的代码组织。

4.3 预定义宏

预定义宏(Predefined Macros)是在编译器中预先定义的一组宏,它们提供了有关编译环境、编译选项和代码特性的信息。预定义宏在编译过程中自动可用,可以在源代码中使用或通过条件编译进行判断。

预定义宏的名称通常以双下划线开头和结尾,以区分它们与用户定义的宏。预定义宏的具体定义和可用性取决于编译器和平台,以下是一些常见的预定义宏及其含义:

  1. __FILE__:表示当前源文件的文件名(字符串常量)。

  2. __LINE__:表示当前代码行号的整数值。

  3. __DATE__:表示当前编译的日期,格式为"MMM DD YYYY"(字符串常量)。

  4. __TIME__:表示当前编译的时间,格式为"HH:MM:SS"(字符串常量)。

  5. __cplusplus:仅在 C++ 编译环境中定义,表示当前编译器正在编译 C++ 代码。

  6. __STDC__:表示编译器遵循 C 标准的版本号。如果编译器遵循 ANSI C 标准,则定义为 1。

  7. __STDC_HOSTED__:表示编译器是否在宿主环境中运行。如果是宿主环境(如完整的操作系统),则定义为 1;如果是嵌入式环境,则未定义。

  8. __STDC_VERSION__:表示编译器遵循 C 标准的版本号。对于 C89/C90 标准,该宏的值为 199409L;对于 C99 标准,该宏的值为 199901L;对于 C11 标准,该宏的值为 201112L。

通过使用预定义宏,可以在源代码中获取有关编译环境和代码特性的信息,以及根据不同的条件执行不同的代码逻辑。预定义宏在编写跨平台代码、条件编译和调试代码时非常有用。

4.4 #line和#error

#line#error 用于在编译过程中进行控制和错误处理。

  1. #line#line 指令用于修改编译器生成的行号和文件名。它可以在代码中模拟指定不同的行号和文件名,有时在代码生成工具或宏展开过程中会使用到。

    语法:#line line_number "file_name"

    • line_number 表示要设置的行号。
    • file_name 表示要设置的文件名。

    示例:

    #line 42 "custom_file.c"
    // 后续的代码将模拟在文件 custom_file.c 中的第 42 行
    

    在上述示例中,#line 指令将设置代码的行号为 42,并将文件名设置为 “custom_file.c”。这在某些情况下可能有用,例如在代码生成工具中生成特定的行号和文件名。

  2. #error#error 指令用于在预处理阶段生成一个编译错误。它允许在预处理期间根据特定的条件或要求生成自定义的错误消息,以防止代码继续编译。

    语法:#error error_message

    • error_message 表示要生成的错误消息。

    示例:

    #if !defined(VERSION)
        #error "VERSION 宏未定义,请定义 VERSION 宏"
    #endif
    

    在上述示例中,如果宏 VERSION 未定义,则预处理阶段将生成一个编译错误,提示用户定义 VERSION 宏。这可以用于强制要求在编译时提供必要的宏定义或进行条件检查。

使用 #line#error 可以在预处理阶段对代码进行控制和错误处理。 #line 指令允许修改行号和文件名,而 #error 指令允许生成自定义的编译错误。

4.5 #praga

#pragma 用于向编译器传递特定的指示或命令。它可以用于控制编译器的行为、设置编译选项、调整编译环境或提供特定的编译器指示。

#pragma 的具体使用方式和支持的功能取决于编译器和平台。不同的编译器可能支持不同的 #pragma 指令,并提供不同的功能。下面是一些常见的 #pragma 用途:

  1. 编译器警告抑制:#pragma warning#pragma GCC diagnostic 可以用于控制编译器的警告行为。通过指定警告的级别或警告的开启/关闭状态,可以对特定的警告进行抑制或启用。

    #pragma warning(disable: 1234)  // 抑制警告编号为 1234 的警告
    
    #pragma GCC diagnostic ignored "-Wformat"  // 忽略 "-Wformat" 警告
    
  2. 对齐设置:#pragma pack 可以用于指定结构体或数据的对齐方式。通过设置对齐方式,可以控制数据在内存中的布局。

    #pragma pack(1)  // 按 1 字节对齐
    
  3. 扩展和特定功能:某些编译器可能提供特定的 #pragma 扩展,用于启用或配置特定的功能。例如,#pragma omp 用于指定 OpenMP 并行化的指令。

    #pragma omp parallel for  // OpenMP 并行化指令
    

#pragma 是编译器特定的指令,不属于 C 或 C++ 的标准。因此,使用 #pragma 指令可能会导致代码在不同的编译器上产生不同的行为或不可移植性。为了确保代码的可移植性,应谨慎使用 #pragma 并尽可能遵循标准的语言功能。

4.6 头文件如何命名

假如有个头文件:example.h。一般它的内容如下:

#ifndef EXAMPLE_H
#define EXAMPLE_H

// 函数声明
void myFunction(int arg1, float arg2);

// 宏定义
#define MAX_VALUE 100

// 类型定义
typedef struct {
    int x;
    int y;
} Point;

#endif

在这里,EXAMPLE_H 是一个宏定义,用于防止头文件的多次包含。这个名称可以根据头文件的名称来选择,通常使用大写字母和下划线的方式来表示宏定义。在这种情况下,EXAMPLE_H 可以理解为是一个与头文件 example.h 相关的唯一标识符。

使用大写字母和下划线的命名方式有助于提高宏定义的可读性,并且与其他变量或函数名的命名方式有所区别,以避免命名冲突。请注意,这种命名约定是一种常见的做法,并不是硬性规定,你可以根据自己的喜好和项目的约定进行调整。关键是保持一致性,并确保在整个项目中使用唯一的宏定义名称。

但是,#define example.h肯定不行,宏定义不能有扩展名和路径。



~

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

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

相关文章

Ansible基础4——变量、机密、事实

文章目录 一、变量二、机密2.1 创建加密文件2.2 查看加密文件2.3 编辑加密文件内容2.4 加密现有文件2.5 解密文件2.6 更改加密密码 三、事实3.1 收集展示事实3.2 展示某个结果3.3 新旧事实命令3.4 关闭事实3.5 魔法变量 一、变量 常设置的变量&#xff1a; 要创建的用户要安装的…

【C++ 基础篇:19】:类的构造函数与初始化列表:用法说明及构造函数的细节内容补充!

本系列 C 相关文章 仅为笔者学习笔记记录&#xff0c;用自己的理解记录学习&#xff01;C 学习系列将分为三个阶段&#xff1a;基础篇、STL 篇、高阶数据结构与算法篇&#xff0c;相关重点内容如下&#xff1a; 基础篇&#xff1a;类与对象&#xff08;涉及C的三大特性等&#…

Kubernetes_容器网络_循序渐进地学习kubernetes网络

文章目录 前言一、Linux网络命名空间1.1 linux网络命名空间1.2 不同网络命名空间的通信两个网络命名空间通信多个网络命名空间通信 二、K8S Pod网络通信2.1 Pod内部容器的网络通信2.2 相同node: 不同pod间的网络通信2.3 不同node: 不同pod间的网络通信2.4 容器网络插件: Flanne…

C++STL库之map

文章目录 关于仿函数stackdeque&#xff08;双端对列&#xff09;queuepriority_queuemap(重点)set(去重) 关于仿函数 //C不能重载的运算符sizeof、 ::、 ? :、 .、 *、 class Add { public:int operator()(int a, int b)const{return a b;} }; //函数对象&#xff0c;仿函数…

EDA数字钟(三)

文章目录 前言一、设计内容二、模块结构三、代码编写1、顶层模块Digclk2、状态控制模块Ctrl3、按键消抖模块Filter4、计时模块Time5、闹钟模块Alarm6、显示模块Display7、数码管驱动模块Smg 四、测试文件五、波形仿真总结 前言 再次编写数字钟Verilog程序&#xff0c;使其符合…

数据迁移工具,用这8种就够了

前言 最近由于工作需要需要进行数据迁移&#xff0c;那么ETL数据迁移工具该用哪些呢&#xff1f; ETL(是Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程)&#xff0c;对于企业应用来说&#xff0c;我们经常会遇到各种数据的处理、转换、迁移的场景。…

50 Projects 50 Days - Split Landing Page 学习记录

项目地址 Split Landing Page 展示效果 Split Landing Page 实现思路 当鼠标移动到左右两块区域时&#xff0c;分别给容器添加不同的class实现样式的变换。 有两种思路可以实现&#xff0c;一种是hover时改变宽度&#xff0c;一种是hover时改变flex拉伸比例&#xff0c;两…

从零手写操作系统之RVOS外设中断实现-04

从零手写操作系统之RVOS外设中断实现-04 RISC-V 中断&#xff08;Interrupt&#xff09;的分类RISC-V Trap &#xff08;中断&#xff09;处理中涉及的寄存器寄存器 mie、mip中断处理流程PLIC 介绍外部中断&#xff08;external interrupt &#xff09;PLICPLIC Interrupt Sour…

精调万分(Fine tune SAM)-万分预测器的解读和精调之一

缘起 分割万物(segment-anything model, SAM, 万分), 是图像分割领域的革命, 图像分割从此进入大模型时代. 如何自定义这个大模型以为己用? 或者说, 通过精调取长补短用于自己的项目?这是一个值得研究的问题, 在这里我试着探索一下, 万分在医学影像学里面的脊柱分割的应用. …

【sentinel】滑动时间窗口算法在Sentinel中的应用

固定窗口算法&#xff08;计数器法&#xff09; 算法介绍 计数器法是限流算法里最简单也是最容易实现的一种算法。比如我们规定&#xff0c;对于A接口来说&#xff0c;我们1秒的访问次数不能超过10次。那么我们可以这么做&#xff1a;在一开始的时候&#xff0c;我们可以设置…

ESP-BOX官方例程实践

1.下载esp-box项目代码 github仓库&#xff1a;https://github.com/espressif/esp-box gitee仓库&#xff1a;https://gitee.com/EspressifSystems/esp-box 使用git工具和如下命令进行下载&#xff1a; git clone --recursive https://github.com/espressif/esp-box.git or gi…

【C++ 基础篇:21】:friend 友元四连问:什么是友元?友元类?友元函数?什么时候用友元?

本系列 C 相关文章 仅为笔者学习笔记记录&#xff0c;用自己的理解记录学习&#xff01;C 学习系列将分为三个阶段&#xff1a;基础篇、STL 篇、高阶数据结构与算法篇&#xff0c;相关重点内容如下&#xff1a; 基础篇&#xff1a;类与对象&#xff08;涉及C的三大特性等&#…

S7-200 PLC的CPU模块介绍

更多关于西门子S7-200PLC内容查看&#xff1a;西门子200系列PLC学习课程大纲(课程筹备中) 1.什么是西门子200PLC的CPU? 如下图1-1所示&#xff0c;S7-200 PLC CUP是将一个微处理器&#xff0c;一个集成电源&#xff0c;一定的数字量或模拟量I/O&#xff0c;一定的通信接口等…

【Linux】—— git的管理以及使用

前言&#xff1a; 在上篇我们已经学习了关于调试器gdb的相关知识&#xff0c;本期我将为大家介绍的是关于版本控制工具——git的使用教程&#xff01;&#xff01;&#xff01; 目录 前言 &#xff08;一&#xff09;git的历史介绍 &#xff08;二&#xff09;github和gite…

Unity异步编程【6】——Unity中的UniTask如何取消指定的任务或所有的任务

今天儿童节&#xff0c;犬子已经9个多月了&#xff0c;今天是他的第一个儿童节。中年得子&#xff0c;其乐无穷&#xff08;音&#xff1a;ku bu kan yan&#xff09;…回头是岸啊 〇、 示例效果 一连创建5个异步任务[id 从0~4]&#xff0c;先停止其中的第id 4的任务&#x…

Flutter进阶篇-布局(Layout)原理

1、约束、尺寸、位置 overrideWidget build(BuildContext context) {return Scaffold(body: LayoutBuilder(builder: (context, constraints) {print("body约束:" constraints.toString());return Container(color: Colors.black,width: 300,height: 300,child: L…

【企业化架构部署】基于Nginx搭建LNMP架构

文章目录 一、安装 MySQL 数据库1. 安装Mysql环境依赖包2. 创建运行用户3. 编译安装4. 修改mysql 配置文件5. 更改mysql安装目录和配置文件的属主属组6. 设置路径环境变量7. 初始化数据库8. 添加mysqld系统服务9. 修改mysql 的登录密码10. 授权远程登录 二、编译安装 nginx 服务…

Maven 工具

Maven 工具 Maven简介Maven 基础概念创建 Maven项目依赖配置生命周期与插件分模块开发聚合和继承聚合继承聚合与继承的区别 属性版本管理多环境配置与应用私服 Maven简介 Maven 本质是一个项目管理工具&#xff0c;将项目开发和管理过程抽象成一个项目对象模型&#xff08;POM…

【爬虫】3.4爬取网站复杂数据

1. Web服务器网站 进一步把前面的Web网站的mysql.html, python.html, java.html丰富其中 的内容&#xff0c;并加上图形&#xff1a; mysql.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>my…

ICV报告: 智能座舱SoC全球市场规模预计2025年突破50亿美元

在智能化、互联化车辆需求不断增加的推动下&#xff0c;汽车行业正在经历一场范式转变。这一转变的前沿之一是智能座舱SoC。本市场研究报告对智能座舱SoC市场进行了全面的分析&#xff0c;包括其应用领域、当前状况和主要行业参与者。 智能座舱SoC指的是现代汽车智能座舱系统的…