【C语言进阶】预处理那些事儿

news2024/12/23 5:56:01

在这里插入图片描述

文章目录

  • 📖预定义符号
  • 📖预处理指令
  • 📖#define
    • 🔖#define定义标识符
    • 🔖#define定义宏
    • 🔖#define替换规则
    • 🔖#和##
    • 🔖带有副作用的宏参数
    • 🔖宏和函数的对比
    • 🔖命名约定
    • 🔖#undef
  • 📖命令行定义
  • 📖条件编译
  • 📖文件包含
    • 🔖头文件被包含的两种方式
    • 🔖头文件被重复包含
  • 📖模拟实现offsetof
  • 📖交换一个二进制数的奇数位和偶数位

前言
 上一次分享了与程序有关的两种环境,分别是 翻译环境执行环境,在执行环境中又细分出了 预处理(预编译)、 编译汇编链接几个过程,今天就让我们来深入了解一下 预处理过程都干了些什么,话不多说,让我们开启今天的学习吧!

📖预定义符号

  • __FILE__----当前源文件所在的路径
  • __LINE__----文件当前的行号
  • __DATE__----文件被编译的日期
  • __TIME__----文件被编译的时间
  • __STDC__----如果编译器遵循ANSI C ,其值为1,否则未定义
  • __FUNCTION__----__FUNCTION__所在函数的函数名
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}
//上面这段代码是在VS2019这个环境下运行的,__STDC__显示未定义
//说明VS2019不支持ANSI C标准

在这里插入图片描述
 这些预处理符号在预处理阶段就会被具体的值替换,如下图所示:

在这里插入图片描述

📖预处理指令

 我们常见的下面这些符号都被叫做预处理指令:

  • #define----定义标识符常量
  • #include----头文件的包含
  • #pragma

 对这些预处理指令都是在预处理阶段执行的。

📖#define

🔖#define定义标识符

语法:

#define name stuff
//表示代码中所有name的地方都用stuff来进行替换

实例:

#define MAX 1000//把代码中所有的MAX用1000来替换
#define reg register//为register这个关键字,创建一个简短的名称
#define do_forever for(;;)//用更形象的符号来替换一种实现
#define CASE break;case//在写case语句的时候自动把 break写上。
//如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续航符)

#define定义标识符的时候,要不要在最后加;?
比如:

#define MAX 1000
#define MAX 1000;

 建议不要加上;,这样容易导致问题,比如下面的场景:

  • 情景一:(加上;没有影响)
#define MAX 100;

int main()
{
    int min = MAX;
    return 0;
}

在这里插入图片描述
 上面的代码在预处理阶段,用100;去替换程序中的MAX,这就导致在text.i文件中100的后面有两个分号,其中一个时我们在源代码(text.c)中写的,另一个是在对MAX替换后得到的。这两个分号并不会对程序造成什么影响,第二个分号会被当成一条空语句去执行。

  • 情景二:(加上;导致异常)
#define MAX 100;

int main()
{
    int i = 1;
    int n = 0;
    if(i > 0)
        n = MAX;
    else
        n = 0;
    return 0;
}

在这里插入图片描述
 可以看出,此时程序报错了。为什么呢?因为根据语法规定:if语句后面如果没有大括号的话只能有一条语句,但是从预编译的得到的text.i文件中可以看出if语句后面跟了两条语句,分别是赋值语句n = 100;和空语句;,这显然不符合语法规定,报错也是理所当然。当然针对上面的错误也有以下几种修改手段:

  1. 在#define的时候,后面不加;,这是一本万利的方法
  2. 在写源代码的时候MAX后面不写;,这种方法虽然可行,但是不符合我们的日常使用习惯,一条语句结束没有;给我们的第一感觉就是代码写的有问题
  3. 在写源代码的时候对if else子句加上大括号,当然这种方法也仅仅是针对当前的情况有效,如果是其他情况还是需要另寻它路。

 通过分析我们得出结论在用#define定义标识符的时候不要加;

🔖#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)定义宏(#define macro)。
语法:

#define name(parament-list) stuff
  • name是宏的名字
  • parament-list是一个用逗号隔开的符号表,它们可能会出现在stuff中(类似于参数,没有类型
  • stuff会用parament-list来实现一定的功能

注意: 参数列表必须的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
实例:

#define SQUARE(x) x*x

int main()
{
	printf("%d\n", SQUARE(2));
	printf("%f\n", SQUARE(5.0));
	return 0;
}

工作原理:

在这里插入图片描述
 可以看出在预处理阶段对源程序中的SQUARE(2)SQUARE(5.0)进行了替换。以SQUARE(2)为例在预处理阶段宏的参数x就是2,然后用x*x就变成了2*2再用2*2去替换SQUARE(2)最终就得到了text.i文件中的结果。

存在的陷阱一:

#define SQUARE(x) x*x

int main()
{
	printf("%d\n", SQUARE(5 + 1));
	return 0;
}

在这里插入图片描述

 按照一般思维去看上面的代码,首先5+1=6我们会以为是把6传给x,然后用6*6来替换SQUARE(5 + 1)最终得到36,可结果并不像我们想的那样,正确结果是11,为什么会这样呢?去看看预处理后得到的文件我们就会恍然大悟

在这里插入图片描述
 可以看出真实的替换结果并不是我们想的用6*6去替换SQUARE(5 + 1),而是用5+1*5+1去替换的。这说明在预处理的时候并没有执行5+1,而是直接把5+1传了过去,最终得到的结果就是11。
总结: 宏的参数是不加运算直接进行替换的
 如何得到我们想要的36呢?有以下两种方法供大家参考:

  • 对宏调用进行修改:SQUARE((5 + 1)),给5+1再加一层括号,此时在替换的时候,x就是(5+1)SQUARE((5 + 1))就会被替换成(5+1)*(5+1)最终得到的结果就是36。
  • 对定义的宏进行修改:#define SQUARE(x) (x)*(x),此时在替换的时候,x是5+1SQUARE(5+1)会被替换成(5+1)*(5+1)最终得到的结果也是36。

 对比上面的两种方案,第一种方案带两个括号看起来比较别扭,所以更推荐第二种方案,也就是在定义宏的时候给参数带上括号

在这里插入图片描述
存在的陷阱二:

//我们希望计算一个数的二倍再乘10
#define DOUBLE(x) (x)+(x)//这里定义了一个宏来计算一个数的二倍

int main()
{
	printf("%d\n", 10 * DOUBLE(3));
	return 0;
}

在这里插入图片描述
 根据需求描述,我们希望得到的应该是60,但实际却得到的是33,本质原因就是宏只会进行替换,先进行参数的替换,再进行宏体的替换。让我们来看看上面的代码经过预处理会得到什么

在这里插入图片描述
 不难看出,为了得到我们希望的结果,应该对宏替换后得到的内容加上括号,也就是在定义宏的时候对处理结果加上括号:#define DOUBLE(x) ((x)+(x))

在这里插入图片描述
 通过上面介绍的两种陷阱,我们可以得出一个结论:在宏定义的时候,千万不要吝啬括号!!,先给每个参数带上括号,再给stuff整体带上括号。
 对于#define定义宏的注意事项总结如下:

  • 参数列表必须的左括号必须与宏的名字name紧邻
  • 宏的参数都是不加计算直接替换的
  • 不要吝啬括号

🔖#define替换规则

 在程序中扩展#define定义符号和宏时,需要涉及以下几个步骤:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果有,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏、参数名被他们的值所替换。
  3. 最后再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果有,就重复上面述处理过程

注意:

  • 宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归
  • 当预处理器搜索#define定义的符号的时候,字符串常量的值并不被搜索

🔖#和##

 先来看下面的代码

int main()
{
	char* p = "hello ""world\n";
	printf("hello ""world\n");
	printf("%s", p);
	return 0;
}

在这里插入图片描述

 通过结果可以看出:对于两个字符串"hello ""world\n"编译器会自动对它们进行合并得到一个字符串hello world\n字符串是有自动连接的特点我们是否可以写出下面这种代码?

//这里我们写了一个宏用来打印信息
//我们希望传a的时候它可以打印出:
//a的值是:0
//传b的时候它可以打印出:
//b的值是:10
#define PRINT(x) printf("x的值是:%d\n", x)

int main()
{
	int a = 0;
	PRINT(a);
	int b = 10;
	PRINT(b);
	return 0;
}

在这里插入图片描述
 结果不尽如人意,printf("x的值是:%d\n", x)中的第一个x并没有被替换掉,那如何实现我们的需求呢?此时就需要用到#,它可以把宏参数转换成字符串,不信我们试试,对上面的代码稍作修改:

#define PRINT(x) printf(#x"的值是:%d\n", x)

int main()
{
	int a = 0;
	PRINT(a);
	int b = 10;
	PRINT(b);
	return 0;
}

在这里插入图片描述
 此时我们的需求就得以实现,以PRINT(a);为例分析一下过程:首先用a去替换宏参数x,再把x带入到后面的宏体中,此时a是一个整型,再通过#把整型a转换成一个字符串"a",再利用两个字符串可以自动合并的特性,最终就实现了我们的需求。再来看看预处理后得到的结果:

在这里插入图片描述
 此时我们定义的宏PRINT只能打印整型变量,因为宏体里的格式化打印已经被固定为%d了,我们可以对当前的宏稍作修改,使它也可以打印浮点型数据,即把格式也当作宏参数传递:

#define PRINT(format, x) printf(#x"的值是:"format"\n", x)

int main()
{
	int a = 0;
	PRINT("%d",a);
	int b = 10;
	PRINT("%d",b);
	float c = 2.5f;
	PRINT("%f",c);
	return 0;
}

 经过预处理替换后得到下面的结果:

在这里插入图片描述
总结: #可以把宏参数转换成对应的字符串来进行显示,了解了#的作用,接下来了解##,它可以把位于它两边的符号合并成一个符号,它允许宏定义从分离的文本片段创建标识符。如下面的代码:

#define GET(str1, str2) str1##str2
int main()
{
	int Addstudent = 10;
	printf("%d\n", GET(Add, student));
	return 0;
}

在这里插入图片描述

 再看一下他们在编译替换后得到的结果:(printf("%s\n", GET("Hello ", "world"));再gcc环境下会报错)

在这里插入图片描述

🔖带有副作用的宏参数

 当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现永久有效性
 先来解释一下什么叫副作用,例如:

int main()
{
	int a = 10;
	int b = ++a;
	return 0;
}

 上面代码中定义了a = 10,然后想给b赋值成11,这里是通过++a来实现的,它存在的问题是在给b赋值11的同时,a的值也发生了永久性的变化,变成了11,这就叫做副作用,如果写成这样:int b = a + 1;这样就没有副作用了。
 接下来我们看一个带有副作用的宏参数的实例:

#define MAX(x, y) ((x)>(y) ? (x) : (y))

int main()
{
	int a = 4;
	int b = 5;
	int c = MAX(a++, b++);
	printf("c = %d\n", c);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	return 0;
}

在这里插入图片描述
 出现这种结果的原因在于:宏参数是不加运算进行替换的,所以会把副作用传到宏定义中,下面来看看预处理阶段替换后的结果:

在这里插入图片描述
 与宏参数不同的是:函数的实参会经过运算把最终的结果传递给形参

int GetMax(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	int a = 4;
	int b = 5;
	//int c = MAX(a++, b++);
	int c = GetMax(a++, b++);
	printf("c = %d\n", c);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	return 0;
}

在这里插入图片描述

🔖宏和函数的对比

宏通常被应用于执行简单的运算,主要有以下两个方面的原因:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。而宏参数是与类型无关的,宏可以适用于整型、长整型、浮点型等可以用于>来比较的类型

宏的缺点:

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  • 宏是没法调试的(调试是在已经生成可执行程序后进行的,宏早已被替换)
  • 宏由于与类型无关,也就不够严谨
  • 宏可能带来运算符优先级的问题,导致程序容易出现错误

 宏有时候可以做函数做不到的事情,比如:宏的参数可以出现类型,但是函数做不到,例如:

#define MALLOC(type, num) (type*)malloc(sizeof(type)*num)

int main()
{
	int* arr = MALLOC(int, 10);
	char* str = MALLOC(char, 10);
	float* ret = MALLOC(float, 10);
	return 0;
}

在这里插入图片描述
将宏和函数做如下对比:

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅增长函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的结果,所以建议宏在书写的时候不要吝啬括号函数的参数只在函数调用的时候求一次值,它的结果值传递给函数。表达式的求值结果更容易预测
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果函数参数在传参的时候求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使它们执行的任务是相同的
调试宏是不方便调试的函数是可以逐语句进行调试的
递归宏是不能递归的函数是可以递归的

 总结:代码逻辑比较简单可以写成宏,如果代码逻辑比较复杂就写成函数。

🔖命名约定

  • 把宏名全部大写
  • 函数名不要全部大写

🔖#undef

这条指令用于移除一个宏定义,例如:

#define MAX 100

int main()
{
	printf("%d\n", MAX);
#undef MAX
	printf("%d\n", MAX);//这里的MAX就成未定义了
	return 0;
}

📖命令行定义

 许多C的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性很有用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大一些,我们需要一个大一点的数组)

#include <stdio.h>

int main()
{
	int arr[SIZE];//这里并没有给SIZE任何初值
    for(int i = 0; i < SIZE; i++)
    {
        arr[i] = i;
    }
    for(int i = 0; i < SIZE; i++)
    {
        printf("%d ",arr[i]);
    }
	return 0;
}

 通过下面这条指令对这段代码进行编译gcc text.c -D SIZE=10 -o text.exe

在这里插入图片描述
 这就是在命令行中指定某些符号的大小,指定大小后的替换动作也是在预处理阶段完成的,我们可以看看预处理后得到的文件

在这里插入图片描述

📖条件编译

 借用条件编译指令,我们在编译一个程序的时候可以对一条语句或者一组语句选择性的进行编译。举个🌰:

#define PRINT s//只要定义了就行,可以是任何数字或者字母,甚至没有也可以

int main()
{
#ifdef PRINT
	printf("hehe\n");//只要PRINT定义了,这段代码就可以被编译,没定义就不能被编译
#endif
	return 0;
}

#ifdefendif必须是成对出现的。只要#ifdef后面的标识符被定义了,那么#ifdefendif之间的代码就可以被编译,反知则不能被编译。

在这里插入图片描述
 上图中的源文件中没有对PRINT的定义,所以经过预编译,#ifdefendif之间的代码块就会被删掉。
常见的条件编译指令:

  1. 单分支
#if 常量表达式 
//...
#endif
//常量表达式由预处理器求值,
//值为真编译中间的代码块,否则不编译
  1. 多分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
  1. 判断是否被定义
#if defined(symbol)//判断symbol是否被定义
//...
#endif

#ifdef symbol//和上面的语句等价,判断symbol是否被定义
//...
#endif

#if !defined(symbol)//如果定义了symbol则不编译下面的代码块
//...
#endif

#ifbdef symbol//如果定义了symbol则不编译代码块
//...
#endif
  1. 嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

📖文件包含

 对于#include相信大家都不陌生,这条指令可以使另外一个文件被编译,就像它实际出现于#include指令的地方一样。这种替换方式很简单,预处理器先删除这条指令,并用包含文件的内容替换,这样一个源文件被包含几次,就会被编译几次。

🔖头文件被包含的两种方式

  1. 本地文件包含
#include "filename"

查找策略: 先在源文件所在目录下找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误。

  1. 库文件包含
#include <filename.h>

查找策略: 查找头文件直接去标准路径下查找,如果找不到就提示编译错误。这意味着:对库文件的包含也可以用双引号,但是这样查找的效率就低一些,当然这样也不容易区分是库文件还是本地文件了。

🔖头文件被重复包含

 上面提到过,一个头文件被包含几次,就会被编译几次,就像下面这样:

在这里插入图片描述
 当然,大家在写代码的时候应该不会出现上面这种极端情况,在一个源文件里边显式的把一个头文件包含多次。我们可能遇到的是:a包含了b,c也包含了b,d包含了a和c,那这样一来d就间接的把b给包含了两次。如何解决这种问题呢?有以下两种方案:

  1. 条件编译
#ifndef _THE_TEXT_//如果_THE_TEXT_没有被定义就编译下面的代码,否则就不编译
//第一次包含的时候,由于_THE_TEXT_没有定义,所以会编译下面的代码
//当第二次包含的时候,由于第一次包含已经定义了_THE_TEXT_,第二次就不再编译
#define _THE_TEXT_
struct S
{
    int a;
    char c;
};
#endif

在这里插入图片描述
 可见此时虽然包含了三次,但是预处理的时候只拷贝了一份,这将大大的降低代码冗余。

  1. #pragma once

#pragma once也可以解决头文件被包含多次的问题,比条件编译更简洁,只需要这一条预处理指令:

#pragma once
struct S
{
    int a;
    char c;
};

在这里插入图片描述

📖模拟实现offsetof

#define OFFSETOF(type, number) (size_t)&(((type*)0)->number)//把0变成一个结构体变量的地址,找到其中的成员再取地址,就是偏移量

struct S
{
	char a;
	int b;
	char c;
};

int main()
{
	printf("%d\n", OFFSETOF(struct S, a));
	printf("%d\n", OFFSETOF(struct S, b));
	printf("%d\n", OFFSETOF(struct S, c));
}

在这里插入图片描述
在这里插入图片描述

📖交换一个二进制数的奇数位和偶数位

#define SWAP(x) (((x&0x55555555)<<1)+((x&0xaaaaaaaa)>>1))
//(x&0x55555555)<<1:保留所有的奇数位,然后左移一位
//(x&0xaaaaaaaa)>>1:保留所有的偶数位,然后右移一位
int main()
{
	int a = 10;
	int b = SWAP(a);
	printf("%d\n", b);
	return 0;
}

在这里插入图片描述


 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
在这里插入图片描述

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

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

相关文章

正点原子 STM32F4/F7水星 开发板资料连载第二十一章LTDC LCD实验

正点原子 STM32F4/F7水星 开发板资料连载第二十一章LTDC LCD实验 - 知乎 (zhihu.com) 本章我们将通过 STM32F767 的 LTDC 接口来驱动 RGBLCD 的显示&#xff0c;另外&#xff0c;STM32F767 的 LTDC 还有DMA2D 图形加速&#xff0c;我们也顺带进行介绍。本节分为三个部分&#…

Kubernetes学习笔记-kubernetes应用扩展(1)-自定义API对象20230622

1、CustomResourceDefinitions介绍 开发者只需要只需向kubernetes api服务器提交CRD对象&#xff0c;即可定义新的资源类型。成功提交CRD之后&#xff0c;就能通过API服务器提交JSON清单或者YAML清单的方式创建自定义资源&#xff0c;以及其他kubernetes资源实例 创建一个CRD…

SpringCloud Alibaba入门6之Nacos配置

一、基本概念 1.命名空间 用于区分环境&#xff0c;开发、测试、生产环境等。 2.配置分组 多个配置文件放在一起&#xff0c;形成组。 3.配置集 一般指一个配置文件 4.配置集ID 这个配置文件全局唯一ID 5.配置项 配置的键值对 二、引入Nacos配置中心 我们在用户模块…

探索安卓内容提供者:构建、访问和管理数据【复习】

文章目录 一 ContentProvider1.1 数据模型- **ContentProvider 使用基于数据库模型的简单表格来提供需要共享的数据**&#xff0c;在该表格中&#xff0c;每一表示一条记录&#xff0c;而每一列代表特定类型和含义的数据&#xff0c;并且其中每一条数据记录都包含一个名为“_ID…

团体程序设计天梯赛-练习集L1篇⑤

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

概率论与数理统计教程第五章节笔记

参考书籍&#xff1a;概率论与数理统计教程第三版 茆诗松 程依明 濮晓龙 编著 文章声明&#xff1a;如有错误还望批评指正 文章目录 ξ 5.1 \xi5.1 ξ5.1总体与样本 ξ 5.2 \xi5.2 ξ5.2样本数据的整理与显示Python绘制直方图Python绘制茎叶图 ξ 5.3 \xi5.3 ξ5.3统计量及其分…

非煤电子封条系统 yolov7

非煤电子封条系统通过yolov7python网络模型技术&#xff0c;非煤电子封条系统利用智能化AI视频分析&#xff0c;实时监测分析矿井出入井人员人数变化、非煤及煤矿生产作业状态等情况&#xff0c;自动生成、推送报警信息&#xff0c;提示相关人员采取应急措施。Python是一种由Gu…

最火的几款STM32 F系列对比

最火的几款STM32 F系列对比 最常用且相对热门STM32F103系列STM32F407系列STM32F429系列STM32F746系列 最常用且相对热门 在STM32F系列中&#xff0c;以下是一些最常用且相对热门的芯片&#xff1a; STM32F103系列&#xff1a;这个系列是STM32F系列中最受欢迎的型号之一。它基于…

二进制安装K8S(单Master集群架构)

目录 一、安装K8S1、单Master集群架构2、操作系统初始化配置3、部署docker引擎4、部署 etcd 集群5、部署 Master 组件6、部署 Worker Node 组件7、部署 CNI 网络组件7.1 部署 flannel7.2 Flannel udp 模式的工作原理&#xff08;必备面试题&#xff09;7.3 Flannel vxlan 模式的…

Nexus如何导入jar以及批量导入Maven的本地库目录

前言 本篇基于 Nexus 的版本是 nexus-3.55.0-01本方法适用Linux和WindowsWindows 需要安装Git , 使用Git Bash执行 Nexus上传依赖包的方式 上传依赖包到Nexus 服务器的方式有多种&#xff0c; 包含&#xff1a; 单个jar上传&#xff1a; 在Nexus管理台页面上传单个jar源码编…

springboot高级教程基于 redis 通过注解实现限流

Spring Boot整合Redis有一种方便的方式是使用注解方式实现限流。 可以通过自定义注解的方式来标注需要限流的方法&#xff0c;在方法执行前进行限流的检查。 以下是具体实现方式&#xff1a; 1. 自定义注解RedisLimit&#xff0c;并定义注解元素&#xff0c;如限流的时间、限流…

MIT 6.S081 Lab Four

MIT 6.S081 Lab Four 引言trapsRISC-V assembly (easy)代码解析 Backtrace(moderate)代码解析 Alarm(Hard)test0: invoke handler(调用处理程序)test1/test2(): resume interrupted code(恢复被中断的代码)代码解析issue解答 可选的挑战练习 引言 本文为 MIT 6.S081 2020 操作…

「端午记忆,AI绘梦」微信群AI绘图比赛

点击上方「蓝字」&#xff0c;关注我们 01 活动介绍 亲爱的朋友们&#xff0c; 端午节即将来临&#xff0c;让我们一起用AI唤醒记忆&#xff0c;回忆古老传统的魅力。 这次活动&#xff0c;我们邀请大家进入一个微信群&#xff0c;一起用AI画出你记忆中端午的样子。 无论你是画…

一文通关Spring MVC

目录 &#x1f433;今日良言&#xff1a;少年负壮气&#xff0c;奋烈自有时 &#x1f433;一、Spring MVC的相关介绍 &#x1f415;1.Spring MVC的定义 &#x1f415;2.MVC 和 Spring MVC的关系 &#x1f433;二、Spring MVC的创建及使用 &#x1f42f;1.Spring MVC项目创…

Spring Boot 如何使用 Log4j2 进行日志记录

Spring Boot 如何使用 Log4j2 进行日志记录 在开发 Java 应用程序时&#xff0c;日志记录是非常重要的一环。Spring Boot 提供了多种日志输出方式&#xff0c;其中 Log4j2 是一种比较常用的日志框架。本文将介绍如何在 Spring Boot 应用程序中使用 Log4j2 进行日志记录。 为什…

Verilog基础:标识符的向上向下层次名引用

相关文章 Verilog基础&#xff1a;表达式位宽的确定&#xff08;位宽拓展&#xff09; Verilog基础&#xff1a;表达式符号的确定 Verilog基础&#xff1a;数据类型 Verilog基础&#xff1a;位宽拓展和有符号数运算的联系 Verilog基础&#xff1a;case、casex、ca…

基于阿尔法均值滤波的FPGA图像系统(工程+原理图+PCB+仿真)

目录 前言一、研究背景及意义二、本文研究内容三、硬件系统框架设计1、总框架设计2、原理图&PCB设计3、实物设计4、电路介绍 三、中值滤波算法研究及改进1、图像噪声的产生及危害2、中值滤波算法3、高斯滤波算法4、改进的中值滤波算法&#xff08;α均值滤波算法&#xff0…

【跑实验05】利用CLIP中的图像编码器,如何遍历文件夹中的图像,将图像文件改为28*28的尺寸,然后输出到excel中的每一列,最后一列全都标记为0

文章目录 一、初步实现二、警告信息的解决 一、初步实现 要遍历文件夹中的图像并将其尺寸调整为28x28&#xff0c;并将结果输出到Excel中&#xff0c;可以按照以下步骤进行操作&#xff1a; 首先&#xff0c;确保您已经安装了Pandas库&#xff0c;用于处理Excel文件。可以使用…

简单认识Nginx主配置文件及实操模拟

文章目录 一、Nginx主配置文件1、全局配置2、添加 I/O事件配置4.HTTP配置 实操模拟部分一、Nginx虚拟主机配置1.1基于域名1.2.基于IP1.3.基于端口 二、Nginx访问状态统计三、Nginx配置访问控制1.基于授权的访问控制2.基于客户端的访问控制 一、Nginx主配置文件 位置&#xff1…

【机器学习】sklearn数据集的使用,数据集的获取和划分

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 sklearn数据集 二、安装sklearn二、获取数据集三、…