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

  • 一、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肯定不行,宏定义不能有扩展名和路径。



~

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

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

相关文章

路径之谜 2016年国赛 深度优先搜索

目录 解题思路 AC代码&#xff1a; 题目描述 小明冒充 XX 星球的骑士&#xff0c;进入了一个奇怪的城堡。 城堡里边什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡地面是 nn 个方格。如下图所示。 按习俗&#xff0c;骑士要从西北角走到东南角。可以横向或纵向…

公司新来一00后,真让人崩溃...

2022年已经结束结束了&#xff0c;最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;相信很多小伙伴也在准备今年的金九银十的面试计划。 在此展示一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的…

Executor框架的两级调度模型

Executor框架的两级调度模型 在HotSpot VM的线程模型中Java线程&#xff08;java.lang.Thread&#xff09;被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程&#xff1b;当该Java线程终止时&#xff0c;这个操作系统线程也会被回收。操作系统会调度…

计算机网络-网络层与链路层协议分析实验

一.实验目的 通过本实验&#xff0c;进一步熟悉PacketTracer的使用&#xff0c;学习路由器与交换机的基本配置&#xff0c;加深对网络层与链路层协议的理解。 二.实验内容 1.完成路由器交换机的基本配置 2.了解 ICMP 数据包的格式 3.检查ARP交换 三.实验过程 1.完成路由…

【Python】Python系列教程-- Python3 列表(十二)

文章目录 前言访问列表中的值更新列表删除列表元素Python列表截取与拼接嵌套列表列表比较Python列表函数&方法 前言 往期回顾&#xff1a; Python系列教程–Python3介绍&#xff08;一&#xff09;Python系列教程–Python3 环境搭建&#xff08;二&#xff09;Python系列…

【熬夜送书 | 第四期】python期末考试总结

文章目录 前言单选题程序填空题函数题编程题熬夜送书 第三期 前言 博主也是第一次接触到python语言&#xff0c;在考试前过了一遍python语法&#xff0c;因为有Java基础学习起来相对比较轻松&#xff0c;学校考的题相对简单一些&#xff0c;也是PTA上机考试&#xff0c;大概30…

一文说透ES6中的箭头函数表达式

一 总述 ​箭头函数表达式的语法比函数表达式更简洁&#xff0c;并且没有自己的this&#xff0c;arguments&#xff0c;super或new. target。箭头函数表达式更适用于那些本来需要匿名函数的地方&#xff0c;并且它不能用作构造函数。 二 详细 1 1个或多个参数 (param1, par…

Linux 实操篇-进程管理(重点)

Linux 实操篇-进程管理(重点) 基本介绍 在LINUX 中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID 号(pid,进程号)。>windows > linux每个进程都可能以两种方式存在的。前台与后台&#xff0c;所谓前台进程就是用户目前的屏幕上可以进行操作的。后…

基于matlab仿真带有飞机的虚拟场景

一、前言 此示例演示如何通过 MATLAB接口使用空间鼠标。 开始此示例后&#xff0c;带有飞机的虚拟场景将显示在 Simulink 3D 动画查看器中。您可以使用空格鼠标在场景中导航平面。通过按下设备按钮 1&#xff0c;您可以在当前平面位置放置标记。 此示例需要空间鼠标或其他兼容设…

chatgpt赋能python:Python就业学历要求

Python 就业学历要求 Python 是一门广泛应用于数据科学、人工智能、Web 开发和自动化等领域的编程语言&#xff0c;正在迅速成为行业内最受欢迎的语言之一。如果你想进入这些领域从事相关职业&#xff0c;那么 Python 编程技能将是你的一个优势。但是&#xff0c;Python 就业所…

【LeetCode全题库算法速练】2、两数相加

文章目录 一、题目&#x1f538;题目描述&#x1f538;样例1&#x1f538;样例2&#x1f538;样例3 二、代码参考 作者&#xff1a;KJ.JK &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &a…

深入浅出讲解闭包及其原理

闭包 什么是闭包&#xff1f; 闭包的概念并不复杂&#xff0c;但是它的定义比较绕&#xff08;就像平时经常用到它&#xff0c;却又说不出来是什么&#xff09;。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员&#xff0c;这就是闭包。给一个建议&…

“大四在读生”都四面成功拿到字节跳动Offer了,你还有什么理由去摸鱼?

博主大四在读&#xff0c;投的是字节 Data 的软件测试岗位实习生&#xff0c;base 杭州。 时间线&#xff1a; 4.12 投递4.13 安排简历筛选4.14 安排面试4.19 16:00 一面4.22 16:00 二面 4.23 8:00 三面4.23 16:00 HR 面4.23 16:30 Offer 一面 你对字节跳动的了解和认知有哪…

《架构设计》-09-分布式服务架构(注册中心、服务发布、服务调用、服务治理)

文章目录 1. 概述2. 集群容错策略3. 服务路由3.1 直接路由3.2 间接路由和注册中心3.3 路由规则3.4 服务路由/负载均衡/集群容错的关系 4. 服务发布4.1 发布启动器4.2 动态代理4.3 发布管理器4.4 协议服务器 5. 服务调用6. 服务治理 1. 概述 RPC架构的意义 解决了分布式环境下两…

C++语法(24) 哈希应用

C语法&#xff08;23&#xff09;-- 模拟实现unordered_set和unordered_map_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130449452?spm1001.2014.3001.5501 目录 1.位图 1.定义 2.实现 3.应用 4.特点 2.布隆过滤器 1.介绍 2.设计场…

JavaSE01_初识Java

JavaSE-01【初识Java】 第一章&#xff1a;Java开发序言 1.1 Java语言概述 1、什么是Java语言 Java语言是美国Sun公司&#xff0c;在1995年推出的高级编程语言。 所谓编程语言&#xff0c;就是计算机语言&#xff0c;人们可以使用编程语言对计算机下达指令&#xff0c;让计…

LVGL学习(2):图片的转换和显示

我们在设计UI的过程中可能需要显示一些图片&#xff0c;本篇文章将介绍如何转换并显示一个固定的图片到lv_img中。 文章目录 1 图片转换1.1 GUI Guider1.2 在线转换 2 图片的显示 1 图片转换 和之前我写的一篇字体转换的文章一样&#xff1a;LVGL学习(1)&#xff1a;中文字体…

UnityVR--组件5--Animation动画

目录 新建动画Animation Animation组件解释 应用举例1&#xff1a;制作动画片段 应用举例2&#xff1a;添加动画事件 Animator动画控制器 应用举例3&#xff1a;在Animator中设置动画片段间的跳转 本篇使用的API&#xff1a;Animation、Animator以及Animator类中的SetFlo…

MySQL学习(联结,组合查询,全文本搜索)

联结 SQL最强大的功能之一就是能在数据检索查询的执行中联结表&#xff1b; 关系表 为什么要使用关系表&#xff1f; 使用关系表可以储存数据不重复&#xff0c;从而不浪费时间和空间&#xff1b;如果有数据信息变动&#xff0c;只需更新一个表中的单个记录&#xff0c;相关…

Zabbix(一)

介绍 zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。 功能组件 Server &#xff1a; Zabbix server是zabbix软件的核心组件 Zabbix agent向其报告可用性、系统完整性和统计信息 Zabbix server存储所有的配置信息、统计信息和操作信…