【C语言】预处理(预编译)详解(下)(C语言最终篇)
文章目录
- 一、#和##
- 1.#运算符
- 2.##运算符
- 二、预处理指令#undef
- 三、条件编译
- 1.单分支条件编译
- 2.多分支条件编译
- 3.判断符号是否被定义
- 4.判断符号是否没有被定义
- 四、头文件的包含
- 1.库头文件的包含
- 2.本地头文件的包含
- 3.嵌套包含头文件的解决方法
- 使用条件编译指令
- 使用预处理指令#pragma
- 五、其它预处理指令
- 六、C语言更新结束感言
一、#和##
1.#运算符
这里的#不是#include和#define前面的那个#,它是一个运算符,这个运算符将宏的⼀个参数转换为字符串字⾯量,它仅允许出现在带参数的宏的替换列表中,#运算符所执⾏的操作可以理解为”字符串化“
在讲解这个运算符之前我们先来看一段代码,如下:
#include <stdio.h>int main()
{printf("hello world!\n");printf("hello"" world!\n");return 0;
}
这里的第二个printf中的字符串由两个字符串组成,这样写和第一种printf的效果有什么不同呢?我们来看看代码运行结果:
可以看到,两个printf打印出来的内容是一致的,所以我们可以得出,如果两个字符串挨着一起写,会实现字符串的合并
为了讲清楚#运算符,我们先来看看这样一个例子,我们定义一个变量a = 3,然后我们想要在屏幕上打印 the value of a is 3,该怎么做呢?经过前面的学习,我们已经可以直接写出来了,如下:
int a = 3;
printf("the value of a is %d\n",a);
这个时候我们随便更改a的值都可以使得这句话是正确的,它会随着a的改变而改变,然后我们这个时候说,再创建几个变量,也要以这种形式进行打印,那么每打印一次我们都要写这么长的一串,有没有什么办法简化一下呢?
这个时候我们就可以定义一个宏来解决,在上面那个字符串中,有三个变化的地方,一个就是of后面的a,还有就是占位符%d,然后就是最后面的a,那么我们就可以定义一个宏PRINT,它的参数就是我们要打印的那个值和占位符,如下:
#define PRINT(x , format) printf("the value of x is %d\n",x);
首先我们写出来这个宏后可以发现最后面的那个x会随着我们提供的变量变化,所以已经解决了,还有两个地方要处理,就是of后面的x和后面的占位符,因为它们都在字符串中,所以替换的时候不会替换它们,那么我们怎么办呢
对于占位符的替换,我们可以使用上面学习的两个字符串可以合并的思想,如下:
#define PRINT(x , format) printf("the value of x is "format"\n",x);
这样,前面的the value of x is 就变成一个字符串,后面的\n也变成了一个字符串,这个时候只需要我们传参的时候传上我们的占位符就可以了,如下:
int a = 3;
PRINT(a, "%d");
经过传参后format会被替换成"%d",又是一个字符串,前面一个,中间一个,后面一个字符串,最后合并成了一个字符串
然后就是最后一个要解决的问题,就是of后面的x,我们怎么能够使得它随着我们的传参变化而变化呢?这个时候就要请出我们的#运算符了,它可以使得宏的参数字符串化,只需要将x左右的字符串分开,然后在它前面加上#即可,如下:
#define PRINT(x , format) printf("the value of "#x" is "format"\n",x)
这里将原本的参数x字符串化了,最后也变成了一个字符串,和其它几个字符串合并在了一起,但是这个时候x的内容就会随着我们的传参改变而改变了,比如把变量a传给x,那么of后面就是a,把变量b传给x,那么of后面就是b
然后我们来看看完整代码:
#include <stdio.h>#define PRINT(x , format) printf("the value of "#x" is "format"\n",x)int main()
{int a = 3;float b = 5.1f;PRINT(a, "%d");PRINT(b, "%f");return 0;
}
运行结果:
这样我们就使用#运算符实现了参数的字符串化,#运算符基本上用不到,但是我们还是要了解一下,毕竟它还是挺有趣的
2.##运算符
##运算符又是一个完全不同的运算符了,#运算符的作用是是参数字符串化,##运算符则是可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。##被称为记号粘合运算符
比如有一个变量class115,我们就可以通过class和115的粘合来得到,虽然看起来没有意义,但是我们这里也只是举例,后面我们会使用##运算符实现一个很有趣的功能
现在我们就来使用class115这个来举个例子,我们定义一个宏,它的作用就是帮我们粘合两个符号,如下:
#define CAT(x,y) x##y
CAT这个宏就可以帮助我们粘合我们传过去的参数x和y,比如我们传参class和115,那么它就可以帮我们粘合成class115,如下:
CAT(class,115);
//经过预处理后变成
class115
我们现在写一段代码测试一下,看看它能否实现我们的要求:
#include <stdio.h>#define CAT(x,y) x##yint main()
{int class115 = 5;printf("%d\n", CAT(class, 115));//这里CAT(class,115)相当于class115//因为CAT的作用就是粘合两个符号//这句话就变成了printf("%d\n",class115)//会直接打印5return 0;
}
接下来我们来看看代码运行结果:
可以看到这里确实实现了我们的想法,将class和115两个符号粘合到了一起,组合成了变量名class115
上面我们举的例子很简单,甚至有点大聪明,因为我们只是想要说明##运算符的作用,就是起到两边符号的粘合作用,接下来我们就来实现一些有趣的宏
我们就以求最大值这个函数为例,当我们要找出两个整型数据的最大值时,我们需要一个函数,当我们要找出两个浮点型数据的最大值时,又需要一个函数来实现,但是其实这两个函数的实现内容是非常一致的,如下:
int int_max(int x, int y){return x>y?x:y;}float float_max(float x, float y){return x>yx:y;}
现在我们就来写一个宏,宏名为:GENERATE_MAX,这个宏的作用就是,根据我们传过去的数据类型,自动生成一个找两个数据中最大值的函数,它们的形式类似于:类型_max,这个时候我们就可以用到我们的##运算符,如下:
#define GENERATE_MAX(type) \
type type##_max(type x, type y) \
{\return x>y?x:y; \
}
上面就是我们写出的创建函数的宏,它可以根据参数type来确定函数名和函数类型,其中的\是续航符,可以使内容看起来隔开了,实际上是连贯起来的
这里我们也使用到了##运算符,如果我们单纯写一个type_max,那么在处理时会把它当作一个整体,每次我们创建的函数名就都是type_max了,而不会根据type的变化生成不同的函数名
而如果我们使用了##运算符就可以使得type成为一个独立的参数,而后面的_max就是粘合的符号,这样type这个类型在变化,我们创建的函数名也就跟着变化了
现在我们就使用这个宏来分别创建一个比较整型最大值和浮点型最大值的函数,如下:
GENERATE_MAX(int)
GENERATE_MAX(float)
接着在VS2022中,我们将鼠标指向这个宏,随后我们就可以看到我们使用这个宏后会发生什么,可以看出来这两条语句经过处理后会替换成两个函数,如下图:
如上图,从扩展到后面的信息可以看出,经过预处理后,这个宏会被替换成两个名为int_max和float_max的函数,实现求最大值的功能,是不是特别神奇呢?代码可以写的这么有趣
接着我们就赶紧使用一下这两个函数来验证一下是否正确,如下:
#include <stdio.h>#define GENERATE_MAX(type) \
type type##_max(type x, type y) \
{\return x>y?x:y; \
}GENERATE_MAX(int)
GENERATE_MAX(float)int main()
{int a = 4;int b = 5;float c = 5.3f;float d = 6.2f;int ret1 = int_max(a, b);float ret2 = float_max(c, d);printf("%d %f\n", ret1, ret2);return 0;
}
我们来看看运行结果:
可以看到我们使用##运算符写的宏确实帮我们生成了两个不同类型的最大值函数,并且函数名也随着类型进行改变,是不是非常美,非常神奇呢
二、预处理指令#undef
#undef指令的作用是移除一个#define的定义,它的使用格式如下:
#undef NAME
其中的NAME就是我们要移除的宏的名称,当我们想要更换一下宏名的定义时,就可以使用#undef指令先移除它原本的定义,然后再重新定义它,如下:
#include <stdio.h>
#define N 100int main()
{ printf("%d\n", N);
#undef N
#define N "hello"printf("%s\n", N);return 0;
}
这里我们首先使用#define将N定义为了100,将它打印后我们想要改变它的定义,就使用#undef把它原本的定义移除,然后将它重新定义成了一个字符串,然后再重新打印,我们来看看它的运行结果:
三、条件编译
条件编译有点类似于我们的分支语句,不过条件编译是在预处理阶段进行的,它会根据我们的条件来决定是否编译某些语句,接下来我们就来学习条件编译
1.单分支条件编译
单分支条件编译就是我们只有一条分支需要进行条件判断,使用格式如下:
#if 常量表达式
//如果条件为真那么就编译这里的语句
//如果条件为假就不会编译这里的语句
#endif
//结束的标志
可以看到单分支条件编译从#if开始,然后再#endif结束,它和分支语句类似,但是它有一个结束标志,就是#endif,这是分支语句中没有的
如果条件为真,那么就会编译中间的语句,也就是说最后会执行那些语句,如果为假则不会执行,但是这个条件需要一个常量表达式,我们等下来解释为什么不能使用变量,现在我们可以先来测试一下#if和#endif,如下:
#include <stdio.h>#define N 5int main()
{
#if N == 6printf("hello\n");
#endifreturn 0;
}
这段代码很简单,最后运行它应该什么都不会打印,因为我们定义的N是5,这里当然不等于6,所以不会编译语句printf(“hello\n”),也就不会执行它,那么经过预处理后这条语句跑哪里去了呢?
如果条件编译的结果为假,那么条件编译中的语句经过预处理后会被直接删除,就像我们的注释一样,也是经过预处理后直接删除,所以后面编译就不会带上条件编译中的语句,最后运行生成的可执行程序也就不会执行这段语句
现在我们来看看这段代码的运行结果:
现在我们再回到之前的那个问题,为什么#if后面必须跟一个常量表达式,不能是变量呢?这是因为#if是在预处理阶段进行处理的预处理指令,在预处理阶段还没有给变量分配空间,也就是变量在这个阶段都不存在,自然不能使用变量了,只能使用常量
2.多分支条件编译
多分支条件编译也与分支语句中的多分支语句原理差不多,我们来看看在多分支条件编译中需要用到哪些语句:
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
//...
这里我们列出的结构就是多分支条件编译的结构,这样看有点陌生,我们拿分支语句中的多分支语句跟它们进行一一对应,来进行类比学习:
#if -- if
#elif -- else if
#else -- else
#endif -- 分支语句中没有,是条件编译结束的标志
这样看是否就简单多了,它们的用法都差不多,只是条件编译在预处理阶段进行处理,不能使用含变量的表达式,我们现在就来看一个例子,看看它的结果是什么,如下:
#include <stdio.h>#define N 10int main()
{
#if N == 5printf("hehe\n");
#elif N == 10printf("haha\n");
#elseprintf("hello\n");
#endifreturn 0;
}
可以先自行思考,这里我们直接给出答案,最后程序只会打印haha,首先第一个#if中判断N是不是5,很明显不是,所以hehe将不会被打印,然后到了#elif,判断N是否等于10,条件成立,最后就会打印10,也就不会走到#else,它的内容也就不会被打印
我们来看看代码运行结果:
3.判断符号是否被定义
在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃编译,就可以使用条件编译,比如调试性的代码删掉很浪费,保留又很碍事,我们就可以使用条件编译,在编译的时候不编译这些调试性的代码
在这里我们就可以使用一个技巧,在最开头使用#define定义一个符号,如果我们没有注释或者删除这个符号,那么我们就可以编译里面的调试性代码,进行正常调试,如果我们注释或者删除这个符号,那么我们就不编译里面的调试性代码,不影响代码的正常运行
在实现这个功能之前,我们先来学习如何判断一个符号是否被定义,有两种方式:
- 使用#if defined进行判断:从字面意思来也很容易理解,判断符号是否已经被定义,它的使用格式如下:
#if defined(符号)
#if defined后面的小括号里面就要写上要判断是否被定义过的符号
- 使用#ifdef进行判断:#ifdef实际上就是#if defined的缩写,只是缩写后它的使用方法有点不同,如下:
#ifdef 符号
在使用#ifdef就不再需要小括号了,而是直接在后面写上我们要判断是否被定义过的符号
现在我们学习了如何判断一个符号是否被定义过,现在就来实现一下上面的我们提出的功能,首先我们定义一个符号DEBUG来表示调试,当我们注释掉它的时候,调试信息跟着一起不会执行了
我们现在就引入一个场景,使用循环往数组里面存放信息,为了保证我们往数组里存放数据成功了,我们每存放一次数据就将它打印一次,这个打印就是我们的调试信息,为了检查我们是否成功往数组存放信息的调试性代码
现在我们就来看这样一个场景,如何使用#ifdef或者是#if defined,如下:
#include <stdio.h>#define DEBUG
//debug的意思是调试int main()
{int arr[5] = { 0 };for (int i = 0; i < 5; i++){//往数组存放数据arr[i] = i + 1;
#ifdef DEBUG//等价于#if defined(DEBUG)//打印一下数据,看看数据是否存放进去了printf("%d ", arr[i]);
#endif}return 0;
}
如果我们定义过DEBUG这个符号,那么就会执行调试性语句printf("%d ", arr[i]),这里我们定义了DEBUG这个符号,那么代码就会编译中间的调试性语句,我们来看看代码运行结果是否是这样的:
可以看到数据被打印出来了,说明这个调试性的语句参与编译了,现在我们把定义DEBUG这个符号的语句注释掉,看看数组中的数据还会不会被打印:
可以看到数组中的数据没有被打印了,也就是中间调试性的语句没有被编译和执行,于是我们就通过#ifdef或者#if defined实现根据符号是否被定义,来确实是否编译代码了
4.判断符号是否没有被定义
这里我们就简单介绍一下判断符号是否没有被定义的两个方法,不再举例了,因为它和上面的判断符号是否被定义用法差不多
- 使用#if !defined:这个条件编译语句就是在上面我们讲过的#if defined的defined前加上一个!,表示否定,所以这个条件编译语句就是判断符号是否没有被定义,格式还是和#if defined一致,这里就不再赘述了
- 使用#ifndef:这个条件编译语句就是在上面我们讲过的#ifdef的def前面加上一个n,表示no,也是否定含义,所以这个条件编译语句就是判断符号是否没有被定义,格式还是和#ifdef一致,这里就不再赘述了
这里我们就不再举例使用了,因为我们后面讲到包含头文件还会用到它们,在那里的使用就更加常见了,我们耐心往下看
四、头文件的包含
头文件的包含的本质就是拷贝,当我们包含一个头文件后,会直接将头文件的内容拷贝过来,接下来我们就来学习头文件的包含方式,以及嵌套包含头文件时,如何解决代码冗余的问题
1.库头文件的包含
库头文件里面包含了C语言帮我们实现的功能,我们只需要包含库头文件就可以使用相应的功能和函数,如标准输入输出头文件stdio.h,我们在包含这种库头文件时,一般会使用尖括号<>来进行包含,如下:
#include <stdio.h>
当我们使用<>来进行包含头文件时,程序会直接去我们IDE的标准路径下去查找,如果找不到就提示编译错误
2.本地头文件的包含
本体头文件就是我们自己写的头文件,比如add.h,这种手动实现的头文件,我们在包含这种本体头文件时常常使用双引号""来进行包含,如下:
#include "add.h"
那么程序就会先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件
那么问题来了,我们可不可以使用双引号"“这种方式来包含库头文件,答案是可以,那么为什么我们还要使用<>来包含库头文件,而不是统一使用双引号来包含头文件
这可能是我们平常写代码没有涉及到的原因,我们平常写代码最多创建3到5个头文件,所以使用双引号包含库头文件也没什么影响,但是如果在一个大型工程中,创建了3到5万个头文件呢?
如果使用”“包含库头文件,那么每次都要去当前文件夹里找这个头文件,但是我们知道实际上不可能找得到,然后再去标准路径找这个头文件,我们平常使用的头文件较少,可能影响不大, 但是在大型工程中,这多余的步骤将会造成不小的开销
所以我们还是要养成良好的习惯,包含库头文件使用尖括号<>,包含本地头文件使用双引号”"
最后我们来总结一下使用尖括号<>包含头文件和使用双引号""包含头文件的不同:
- 尖括号<>包含头文件的查找策略:直接去标准路径下去查找,如果找不到就提⽰编译错误
- 双引号""包含头文件的查找策略: 先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件
3.嵌套包含头文件的解决方法
在最开始我们提到了,我们包含头文件的本质就是进行代码的拷贝,将头文件的所有内容拷贝进我们的源文件,那么我们如果嵌套包含,也就是可能多次包含了同一个头文件多次,势必会造成代码的冗余
我们先来看看包含一个头文件多次可能的场景,如图:
在上图场景中,我们每个功能实现都包含了头文件add.h,最后进行汇总时势必会包含三次头文件add.h,造成代码的冗余
可能有的同学就提出疑问了,一般头文件也不会有太多内容呀,就算多拷贝几次也还好啊,其实不然,我们一般会在本地头文件包含我们需要使用的库头文件,就拿最常使用的头文件stdio.h来举例,我们来看看这个库头文件有多少行代码
方法就是正常包含头文件stdio.h,然后使用ctrl加单击的方法就可以点进这个头文件了,我们拉到最后发现它居然有两千多行代码,如下图:
那么如果此时我们多次包含了这个头文件,势必会造成代码冗余,所以我们要想办法来解决这个问题
使用条件编译指令
我们可以使用刚刚学习的条件编译指令解决,具体就是#ifndef或者#if !defined这两个指令,这里我们就选择#ifndef,比较好写
由于头文件的包含就是代码的拷贝,所以我们可以根据这个特点来设计一个功能,就是:一旦包含头文件,我们就判断是否定义了某个符号,如果没有定义我们就定义一下它,然后执行后面的头文件包含,如果这个符号已经被定义了那么就跳过头文件的包含不执行,如下:
#ifndef __TEST_H__#define __TEST_H__//头⽂件的内容#endif //放到头文件最后
这个思路是不是很妙呢?这就是我们避免头文件嵌套包含的第一种方法
使用预处理指令#pragma
这种方法就更为简单了,我们可以在VS2022上创建一个头文件,我们发现头文件中自动包含了一条语句,如下:
我们可以看到,一创建头文件,这条语句就出现了,这就是今天要介绍的第二个方法,在头文件开头写下预处理指令#pragma once,那么就可以解决头文件嵌套包含的问题,是不是特别简单呢?
当然,在VS上帮我们自动写上这条指令了,如果在其它编译器上创建头文件没有这条语句,那么我们直接加上就可以了,也十分简单
五、其它预处理指令
除了我们本文介绍的预处理指令,其实还有非常多的预处理指令,把它们讲完也不太现实,所以我们就只讲了一些常用的预处理指令
如果还想要学习更多的预处理指定就需要大家自己去自行了解了,这里我就推荐一本书,想要深入学习预处理指令就可以参考一下这本书:《C语⾔深度解剖》
六、C语言更新结束感言
那么我们C语言的学习就到这里就结束了,一路走来也真是不容易,不知道大家是否有收获呢?如果有的话也不枉我这么努力的更新
从下一篇文章我们就开始学习数据结构了,在里面我们会手动实现那些数据结构,可以体会到二级指针和递归的暴力美学,狠狠期待一下吧!
那么今天就到这里,感谢观看,有疑问欢迎提出
bye~
相关文章:

【C语言】预处理(预编译)详解(下)(C语言最终篇)
文章目录 一、#和##1.#运算符2.##运算符 二、预处理指令#undef三、条件编译1.单分支条件编译2.多分支条件编译3.判断符号是否被定义4.判断符号是否没有被定义 四、头文件的包含1.库头文件的包含2.本地头文件的包含3.嵌套包含头文件的解决方法使用条件编译指令使用预处理指令#pr…...

[Linux] linux 软硬链接与动静态库
标题:[Linux] linux 软硬链接与动静态库 个人主页水墨不写bug (图片来源于网络) /** _oo0oo_* o8888888o* 88" . "88* (| -_- |)* …...
GitHub Actions的 CI/CD
GitHub Actions 是一个强大的 CI/CD 工具,适用于自动化各种开发任务。GitHub Actions 的原理是基于事件驱动的自动化流水线工具,通过定义触发条件和执行步骤,可以让项目在特定条件下自动运行一系列操作,比如构建、测试、部署等。 …...
doris 表结构批量导出
导出 test 数据库中的 table1 表:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test --tables table1 导出 test 数据库中的 table1 表结构:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test --tables tab…...

linux查看文件命令
查看文件命令 显示命令 cat 语法:cat 【选项】 文件 选项 命令含义n显示行号包括空行b显示行号不包括空行s压缩空行为一行A显示隐藏字符 cat -n 文件:显示行号包括空行 cat -b 文件 cat -s 文件 cat -A 文件 more和less是 分页查看 tac和rev都…...

【2023工业图像异常检测文献】DiAD: 基于扩散模型的多类异常检测方法
DiAD: A Diffusion-based Framework for Multi-class Anomaly Detection 1、Background 当前主流的三种异常检测方法: 基于合成的方法:在正常图像上合成异常,通过训练模型识别这些合成的异常来提高检测和定位能力。基于嵌入的方法ÿ…...

三相继电保护机 继电器保护校验仪 微机继电保护测试仪
性能特点 电压电流输出灵活组合 输出达4相电压3相电流,可各种组合实现常规4相电压3相电流型输出模式,既可兼容传统的各种试验方式,也可方便地进行三相变压器差动试验和厂用电快切和备自投试验。 操作方式装置直接外接笔记本电脑或台式机进行…...

MyEclipse中讲解Git使用——结合GitLab
1、什么是Git Git是分布式版本控制系统 Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。 2、在myeclipse安装Git插件 下载该文件,将egit.zip解压放入 myeclipse安装路径>dropins>下重新启动myeclipse EGit -…...
pdf转为txt文本格式并使用base64加密输出数据
第一步,pom.xml中引入jar包 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.24</version></dependency> 第二步 package org.example.test.example.changefile;…...

SpringBoot篇(运维实用篇 - 临时属性)
目录 一、临时属性设置 1. 简介 2. 属性加载优先级 那是否还有其他的配置方式呢? 3. 知识小结 二、开发环境中使用临时属性 1. 如何操作 2. 知识小结 3. 思考 三、配置文件分类 1. 简介 2. 4个级别 3. 为什么设计多种配置文件? 一个典型的应…...

MySQL定时异机备份
场景:将A机器MySQL数据库部分表每日定时备份到B机器上 (只适用于Linux) 实现方式算是比简单了,就是用mysqldump生成文件,使用scp命令传输到另一台机器上。 1. 编写备份shell脚本 在A机器新建脚本 (当然没有vim的话vi…...

MMA: Multi-Modal Adapter for Vision-Language Models
两个观察 图1所示。各种基于transformer的CLIP模型中不同层的数据集级识别精度。这个实验是为了确定样本属于哪个数据集。我们用不同的种子运行了三次,并报告了每层识别精度的平均值和标准差。 X E m b e d XEmbed XEmbed是指变压器块之前的文本或图像嵌入层&#x…...
uniapp通过id获取div的宽度,高度,位置等(应该是 任意平台都通用 )
uniapp通过id获取div的宽度,高度,位置等(应该是 任意平台都通用 ) <template><view class"" id"domId"></view> </template>// 如果获取的dome高度等不对,还需要加上延迟…...

Python Transformer 模型的基本原理:BERT 和 GPT 以及它们在情感分析中的应用
Transformer 模型的基本原理:BERT 和 GPT 以及它们在情感分析中的应用 近年来,Transformer 模型在自然语言处理(NLP)领域取得了巨大成功,为任务如翻译、生成文本、问答和情感分析带来了显著的性能提升。本文将介绍 Tr…...

【云原生】Kubernets1.29部署StorageClass-NFS作为存储类,动态创建pvc(已存在NFS服务端)
文章目录 在写redis集群搭建的时候,有提到过使用nfs做storageclass,那时候kubernetes是1.20版本,https://dongweizhen.blog.csdn.net/article/details/130651727 现在使用的是kubernetes 1.29版本,根据之前的修改方式并未生效,反而提示:Error: invalid argument "Re…...
使用 Pandas 进行时间序列分析的 10个关键点
使用Pandas进行时间序列分析的10个关键点(由于篇幅限制,这里调整为10个,但实际操作中可能涉及更多细节)如下: 1. 创建时间序列数据 时间序列数据是指在多个时间点上形成的数值序列。在Pandas中,可以使用t…...
使用 Mermaid 语言描述 AGI 系统架构图
使用Mermaid语言描述AGI系统架构图 一、整体架构概述 以下是一个简化的AGI(Artificial General Intelligence,通用人工智能)系统架构的Mermaid描述。该系统主要包括数据收集与预处理、模型训练、推理与决策以及交互接口等模块,各…...
绘制线性可分支持向量机决策边界图 代码解析
### 绘制线性可分支持向量机决策边界图 def plot_classifer(model, X, y):# 超参数边界x_min -7x_max 12y_min -12y_max -1step 0.05# meshgridxx, yy np.meshgrid(np.arange(x_min, x_max, step),np.arange(y_min, y_max, step))# 模型预测z model.predict(np.c_[xx.ra…...

No.23 笔记 | WEB安全 - 任意文件漏洞 part 5
本文全面且深入地探讨了文件上传漏洞相关知识。从基础概念出发,清晰地阐述了文件上传漏洞的定义及其产生的本质原因,同时列出了该漏洞成立的必要条件。详细说明了文件上传漏洞可能对服务器控制权、网站安全以及业务运营带来的严重危害。 文中还深入解析了…...
EasyPlayer.js网页播放器,支持FLV、HLS、WebSocket、WebRTC、H.264/H.265、MP4、ts各种音视频流播放
EasyPlayer.js功能: 1、支持解码H.264视频(Baseline, Main, High Profile全支持,支持解码B帧视频) 2、支持解码H.265视频(flv id 12) 3、支持解码AAC音频(LC,HE,HEv2 Profile全支持) 4、支持解码MP3音频以及Speex音频格式 5、可…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...

Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...