【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定义一个符号,如果我们没有注释或者删除这个符号,那么我们就可以编译里面的调试性代码,进行正常调试,如果我们注释或者删除这个符号,那么我们就不编译里面的调试性代码,不影响代码的正常运行
   在实现这个功能之前,我们先来学习如何判断一个符号是否被定义,有两种方式:

  1. 使用#if defined进行判断:从字面意思来也很容易理解,判断符号是否已经被定义,它的使用格式如下:
#if defined(符号)

    #if defined后面的小括号里面就要写上要判断是否被定义过的符号

  1. 使用#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.判断符号是否没有被定义

   这里我们就简单介绍一下判断符号是否没有被定义的两个方法,不再举例了,因为它和上面的判断符号是否被定义用法差不多

  1. 使用#if !defined:这个条件编译语句就是在上面我们讲过的#if defined的defined前加上一个!,表示否定,所以这个条件编译语句就是判断符号是否没有被定义,格式还是和#if defined一致,这里就不再赘述了
  2. 使用#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~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宠物空气净化器哪个牌子好?有没有噪音低的宠物空气净化器推荐?

如今随着社会竞争越来越激烈&#xff0c;不少人开始焦虑内耗&#xff0c;但为了能更好的生活&#xff0c;养宠物便成为不少人的排忧解乏的方法。 我也不例外&#xff0c;作为一名996社畜&#xff0c;天刚亮就出门&#xff0c;天黑很久才回家&#xff0c;所以选择养猫来陪我度过…

C++设计模式创建型模式———生成器模式

文章目录 一、引言二、生成器/建造者模式三、总结 一、引言 上一篇文章我们介绍了工厂模式&#xff0c;工厂模式的主要特点是生成对象。当对象较简单时&#xff0c;可以使用简单工厂模式或工厂模式&#xff1b;而当对象相对复杂时&#xff0c;则可以选择使用抽象工厂模式。 工…

创作三周年:在忙碌中寻找灵感与快乐

目录 机缘 收获 技能的提升 粉丝的积累 正向的反馈 同行的伙伴 日常 运动 旅行 生活 憧憬 结语 机缘 不知不觉已经成为创作者3年了&#xff0c;这一路走来&#xff0c;有过高峰和低谷&#xff0c;但始终让我坚持的&#xff0c;是最初那份简单的初心&#xff1a;我…

C#从零开始学习(用户界面)(unity Lab4)

这是书本中第四个unity Lab 在这次实验中,将学习如何搭建一个开始界面 分数系统 点击球,会增加分数 public void ClickOnBall(){Score;}在OneBallBehaviour类添加下列方法 void OnMouseDown(){GameController controller Camera.main.GetComponent<GameController>();…

分布式搜索引擎elasticsearch操作文档操作介绍

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c;…

软件系统安全保证措施,质量保证措施方案(Word原件套用)

系统安全保证措施是构建稳固防御体系的核心&#xff0c;旨在全方位保障信息系统的安全性。以下是对这七项措施的简要概述&#xff1a; 一、身份鉴别&#xff1a;采用多种认证方式&#xff0c;如密码、生物识别等&#xff0c;确保用户身份的准确无误&#xff0c;防止非法入侵。 …

玩转Docker | 使用Docker部署捕鱼网页小游戏

玩转Docker | 使用Docker部署捕鱼网页小游戏 一、项目介绍项目简介项目预览 二、系统要求环境要求环境检查Docker版本检查检查操作系统版本 三、部署捕鱼网页小游戏下载镜像创建容器检查容器状态下载项目内容查看服务监听端口安全设置 四、访问捕鱼网页小游戏五、总结 一、项目…

局域网 docker pull 使用代理拉取镜像

局域网 docker pull 使用代理拉取镜像 1、需求&#xff1a; 我有win主机&#xff0c;上面装有代理可连接dockerhub&#xff1b;我另有linux主机&#xff0c;直接pull因墙失败&#xff0c;想走win的代理访问dockerhub拉镜像&#xff1b;两台主机在同一个局域网中&#xff1b; …

c语言中结构体传参和实现位段

结构体传参 有两种方法: #include<stdio.h> struct S {int data[1000];int num; }; //结构体传参 void print1(struct S s) {printf("%d\n",s.num); } //结构体地址传参 void print2(struct S *ps) {printf("%d\n",ps->num); }int main() {pr…

2024年10月HarmonyOS应用开发者基础认证全新题库

注意事项&#xff1a;切记在考试之外的设备上打开题库进行搜索&#xff0c;防止切屏三次考试自动结束&#xff0c;题目是乱序&#xff0c;每次考试&#xff0c;选项的顺序都不同 这是基础认证题库&#xff0c;不是高级认证题库注意看清楚标题 高级认证题库地址&#xff1a;20…

HTML3D旋转相册

文章目录 序号目录1HTML满屏跳动的爱心(可写字)2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐

Depcheck——专门用于检测 JavaScript 和 Node.js 项目中未使用依赖项的工具

文章目录 Depcheck 是什麽核心功能&#x1f4da;检测未使用的依赖&#x1f41b;检测缺失的依赖✨支持多种文件类型&#x1f30d;可扩展性 安装与使用1. 安装 Depcheck2. 使用 Depcheck Depcheck 的应用总结项目源码&#xff1a; Depcheck 是什麽 来看一个常见错误场景&#x1…

Chrome和Firefox哪款浏览器的密码管理更安全

在当今数字化时代&#xff0c;浏览器已成为我们日常生活中不可或缺的工具。其中&#xff0c;谷歌Chrome和Mozilla Firefox是两款广受欢迎的浏览器。除了浏览网页外&#xff0c;它们还提供了密码管理功能&#xff0c;帮助用户保存和管理登录凭证。然而&#xff0c;关于哪款浏览器…

Camp4-L0:Linux 前置基础

书生浦语大模型实战营Camp4-L0:Linux前置基础 教程地址&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/linux任务地址&#xff1a;https://github.com/InternLM/Tutorial/blob/camp4/docs/L0/linux/task.md 任务描述完成所需时间闯关任务完成SSH连接与…

C++之多态的深度剖析

目录 前言 1.多态的概念 2.多态的定义及实现 2.1多态的构成条件 2.1.1重要条件 2.1.2 虚函数 2.1.3 虚函数的重写/覆盖 2.1.4 选择题 2.1.5 虚函数其他知识 协变&#xff08;了解&#xff09; 析构函数的重写 override 和 final关键字 3. 重载&#xff0c;重写&…

如何从iconfont中获取字体图标并应用到微信小程序中去?

下面我们一一个微信小程序的登录界面的制作为例来说明&#xff0c;如何从iconfont中获取字体图标是如何应用到微信小程序中去的。首先我们看效果。 这里所有的图标&#xff0c;都是从iconfont中以字体的形式来加载的&#xff0c;也就是说&#xff0c;我们自始至终没有使用一张…

Linux shell编程学习笔记87:blkid命令——获取块设备信息

0 引言 在进行系统安全检测时&#xff0c;我们需要收集块设备的信息&#xff0c;这些可以通过blkid命令来获取。 1 blkid命令的安装 blkid命令是基于libblkid库的命令行工具&#xff0c;可以在大多数Linux发行版中使用。 如果你的Linux系统中没有安装blkid命令&#xff0c;…

RuoYi-Vue 使用开发 人员管理-查询功能

说明&#xff1a;这里仅仅开发列表显示 与 查询功能&#xff0c;剩下的添加、修改等可能会遇到报错&#xff0c;后面有机会&#xff0c;会单独写一篇文章教学处理 1.了解开发需求 作为示例的二级开发&#xff0c;这里的人员管理&#xff0c;管理的是 部门信息&#xff0c;员工…

Tomcat 11 下载/安装 与基本使用

为什么要使用Tomcat&#xff1f; 使用Apache Tomcat的原因有很多&#xff0c;以下是一些主要的优点和特点&#xff1a; 1. 开源与免费 Tomcat是一个完全开源的项目&#xff0c;任何人都可以免费使用。它由Apache软件基金会维护&#xff0c;拥有一个活跃的社区&#xff0c;这…

Django入门教程——用户管理实现

第六章 用户管理实现 教学目的 复习数据的增删改查的实现。了解数据MD5加密算法以及实现模型表单中&#xff0c;自定义控件的使用中间件的原理和使用 需求分析 系统问题 员工档案涉及到员工的秘密&#xff0c;不能让任何人都可以看到&#xff0c;主要是人事部门进行数据的…