【C语言】详解预处理

最好的时光,在路上;最好的生活,在别处。独自上路去看看这个世界,你终将与最好的自己相遇。💓💓💓

目录

•✨说在前面

 🍋预定义符号

 🍋 #define

  • 🌰1.#define定义常量

​  • 🌰2.#define定义宏

​编辑🔥带有副作用的宏参数

🔥宏替换的规则 

🔥宏和函数的对比

  • 🌰3.命名约定

 🍋 # 和 ##

   • 🌰1.#运算符

  • 🌰2.##运算符

🍋#undef

🍋命令行定义

🍋条件编译

🍋头文件的包含

  • 🌰1.头文件被包含的方式

🔥本地文件包含

🔥库文件包含

🔥嵌套文件包含

🍋其他预处理指令

• ✨SumUp结语


 

•✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,我上一篇文章我们详细解析了C语言代码编译和链接的过程,希望大家能够掌握并应用~

    

然而真正把每个细节都面面俱到是非常复杂的,今天这篇文章就给大家说说编译过程中的第一环-预处理的过程,希望大家好好学习给大家带来帮助,带来收获,感谢大家支持!

 

  博主主页传送门:愿天垂怜的博客

 🍋预定义符号

C语言中设置了一些预定义符号,这些符号可以被直接使用,预定义符号也是在预处理期间处理的。 

1 __FILE__ //进行编译的源文件
2 __LINE__ //文件当前的行号
3 __DATE__ //文件被编译的日期
4 __TIME__ //文件被编译的时间
5 __STDC__ //如果编译器遵循ANSIC,其值为1,否则未定义

示例:

而当我们想在VS上输出__STDC__时,会报错,说__STDC__未定义,这说明了VS并不完全遵循ANSIC(gcc上支持ANSIC)。

 🍋 #define

  • 🌰1.#define定义常量

基本语法:

#define name stuff

#define示例:

#include <stdio.h>#define MAX 1000
#define STR "hello bit"int main()
{int m = MAX;printf("%d\n", m);printf("%s\n", STR);return 0;
}

🔥续行符

如果定义的 stuff 太长,一行不太能放得下,这时可以分成几行去写,除了最后一行外,每行的后悔都要添加一个反斜杠(续行符)。

续行符示例:

#include <stdio.h>
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
int main()
{DEBUG_PRINT;return 0;
}

📌在#define定义标识符的时候,要不要在最后加上分号?

比如:

#define MAX 1000;
#define MAX 1000

其实是不建议的,因为替换的时候会将分号带回去,容易出错,如下面的代码:

#include <stdio.h>#define MAX 1000;
int main()
{int m = MAX;//相当于 m = MAX;; 会多一个分号printf("%d\n", m);return 0;
}

以及下面的代码:

#include <stdio.h>#define MAX 1000;
int main()
{int m = 0;if (1)m = MAX;//相当于m = MAX;;elsem = -1;return 0;
}

  • 🌰2.#define定义宏

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

下面是宏的声明方式:

#define name(parament-list) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中。

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

举例:求一个数x的平方

#define SQUARE(x) x * x

这个宏接收一个参数 x ,如果在上述声明之后,你将SQUARE(5)置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5 * 5

不过,我们在使用这个宏的时候可能会得到以下结果:

这说明这个宏是不够完善的,我们希望得到的是 a + 1 的平方,也就是36,而得到的确实11,我们将宏带入后得到:

printf ("%d\n",a + 1 * a + 1 );

可以发现,我们在将参数 a + 1 带入到 x 中,改变了式子的运算顺序,由于乘法的优先级高于加法,就会先算乘法,得到不符合我们预期的结果。

想要完善这个宏定义,我们可以在 x 上加括号:

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

这样我们就可以得到我们预期的结果了:

printf ("%d\n",(a + 1) * (a + 1));

我们再来看另外一个宏定义:

#define DOUBLE(x) (x) + (x)

在这个宏定义中我们再 x 上个都加了括号,但是还是会有新的问题,如:

乍一看,DOUBLE(5)应该被替换成 5 + 5 为 10 ,再× 10 应该得到100,而结果却是 55 。我们发现替换之后:

printf("%d\n", 10 * (5) + (5));

乘法运算的优先级高于加法,所以结果出现了 55 。

这个问题的解决办法是在宏定义表达式两边再加上一堆括号就可以了。 

#define DOUBLE(x) ((x) + (x))

总结:

所有用于对竖直表达式进行求值的宏定义都应该用这种方式加上括号,不要吝啬你的括号,避免在使用宏的时候由于参数中的操作符优先级的问题或邻近操作符之间不可预料的相互作用。 

🔥带有副作用的宏参数

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

举例:

x+1;//不带副作用
++x;//带有副作用

再比如,a=1,b=10的情况下:

a = b + 1;//a=11,b=10
a = ++b;//a=11,b=11

显然,a都得到11,而下面的写法b的值被改为了11,这写法就是带有副作用的,在得到某种结果时将另外一个参数也改了。

练习:写一个宏,求两个数的较大值。

#define MAX(X,Y) ((X)>(Y)?(X):(Y)) 

这个MAX宏可以说明带有副作用的宏参数会引起不必要的问题:

#include <stdio.h>#define MAX(X,Y) ((X)>(Y)?(X):(Y)) int main()
{int a = 3;int b = 5;int m = MAX(a++, b++);//带有副作用printf("%d\n", m);printf("%d\n", a);printf("%d\n", b);return 0;
}

上面的m、a、b的值分别是6、4、7,大家可以自己先分析一下。

我们将宏替换到main函数中:

int m = ((a++) > (b++) ? (a++) : (b++));

在判断的时候,由于是后置++,先判断3是否大于5,然后a和b分别变成了4和6,而返回的是b++,也就是返回的是6,然后b在此基础上又进行++,此时b进行了两次++,得到的结果是7。

我们知道,在预处理阶段会将所有的 #define 删除,并展开所有的宏定义,我们可以在VScode上得到 .i 文件进行验证:

🔥宏替换的规则 

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

🎉在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,他们首先被替换。

🎉替换文本随后被插入到程序中原本的位置。对于宏,参数名被他们的值所替换。

🎉最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就是重复上述的处理过程。

注意:
宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。

当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不会被搜索。

举例:

#include <stdio.h>#define MAX(X,Y) ((X)>(Y)?(X):(Y)) 
#define M 10int main()
{int a = 3;int b = 5;int m = MAX(a, MAX(10, 5));printf("M = %d\n", m);printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}

🎉根据宏替换的规则,在预处理阶段,首先在程序中搜索到MAX()宏,然后将MAX()宏的内容替换到程序中原本的位置。

🎉宏不支持递归,但一个宏可以作为另外一个宏的参数。

🎉printf函数字面量中的M不会被搜索也不会被替换成10。

🔥宏和函数的对比

宏通常被应用于执行简单的计算

比如在两个数中找出较大的数时,写成下面的宏,就会更有优势一些。

#define MAX(a,b) ((a)>(b)?(a):(b)) 

那为什么不用函数来完成这个任务?

原因有二:

🎉用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

举例:

#include <stdio.h>#define MAX(X,Y) ((X)>(Y)?(X):(Y)) int max(int x, int y)
{int ret = x > y ? x : y;return ret;
}int main()
{int a = 3;int b = 5;int M = MAX(3, 5);int m = max(3, 5);printf("M = %d\n", M);printf("m = %d\n", m);return 0;
}

观察上面宏和函数的反汇编代码:

对于max函数来说,需要花费三个地方的时间:调用函数(11条指令)、执行运算(7~8条指令)、函数返回(6条指令)。

🎉更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。反之宏可以适用于整型、长整型、浮点型等可以用 > 来比较的类型,宏的参数是与类型无关的。

和函数相比宏的劣势:

🎉每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

🎉宏是不方便调试的。

🎉宏由于与类型无关,也就不够严谨。

🎉宏可能会带来运算符优先级的问题,导致容易出现错。

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

举例:

#include <stdio.h>#define Malloc(n, type) (type*)malloc(n*sizeof(type)) int main()
{int* p = Malloc(10, int);//int* p = (int*)malloc(sizeof(int));return 0;
}

 宏函数对比:

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

  • 🌰3.命名约定

一般来说函数和宏的使用语法相似,所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:

🎉把宏名全部大写

🎉函数名不要全部大写

 但也不是说绝对就是这样,这只是一种习惯。比如 offsetof 也是一种宏,是用来计算结构体成员相较于结构体起始位置的偏移量,它就是小写的。

 🍋 # 和 ##

   • 🌰1.#运算符

#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。

#运算符所执行的操作可以理解为"字符串化"

我们可以看看下面这段代码:

int main()
{int a = 1;printf("the value of a is %d\n", a);int b = 20;printf("the value of b is %d\n", b);float f = 5.6f;printf("the value of f is %f\n", f);return 0;
}

不难发现,这三个printf语句是很相似的,不同的地方在于蓝色框内的字面量和格式说明符。 那我们能否将这相似的printf语句封装成函数呢?显然是不行的,函数没有办法处理格式说明符的问题。那能否封装成宏呢?这是可以的。

#include <stdio.h>#define PRINT(n, format) printf("the value of "#n" is "format"\n", n)int main()
{int a = 1;PRINT(a, "%d");int b = 20;PRINT(b, "%d");float f = 5.6f;PRINT(f, "%f");return 0;
}

由于printf函数中的n属于字面量,不会被替换,如果希望n被替换成程序中的字符,需要在n前面加上#,那么#n就会等价于"n",这里面的n是可以被替换的。

此时的#n等价为"n"而不是n,所以需要将printf原本的字面量拆分成多个部分,即

printf("the value of " "n" " is " format "\n"  ,n)

那么PRINT(a, "%d")就相当于:
printf("the value of " "a" " is " "%d" "\n" ,a) 

将相邻字符串拼起来,得到的就是:
pritnf("the value of a is %d\n" ,a) 

  • 🌰2.##运算符

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段中创建标识符,##被称为记号粘合。

这样的链接必须产生一个合法的标识符,否则其结果就是未定义的。

这里我们可以思考一下,写一个函数求两个数的较大值的时候,不同的数据类型就得写不同的函数。

比如:

int int_max(int x, int y)
{return x > y ? x : y;
}float float_max(float x, float y)
{return x > y ? x : y;
}

 那有没有什么办法能快速创建这个函数呢?就是像模具一样,不同类型的数据套用一下就可以得到不用的函数?此时我们就可以用宏

#include <stdio.h>#define GENERIC_MAX(type)	\type type##_max(type x, type y)	\{	\return x>y?x:y;	\}	\
//定义函数
GENERIC_MAX(int);//int_max;
GENERIC_MAX(float);//float_max;int main()
{int r1 = int_max(3, 5);printf("%d\n", r1);float r2 = float_max(3.1f, 4.5f);printf("%f\n", r2);return 0;
}

此时,为了不使得宏中的type被识别为数据类型,而需要将type和后面的_max结合产生为type_max这样的表示符,需要在type和_max之间加上##进行粘合。

🍋#undef

 指令#undef用于移除一个宏定义。

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

举例: 

#define M 100int main()
{int m = 10;int n = 5;int t = M + (m * n);//100+(10*5)=150
#undef Mint M = 5;int ret = M + t;//150+5=155printf("%d\n", ret);return 0;
}

再次使用M ,需要将原先的宏定义M 10移除。

🍋命令行定义

 许多C的编译器提供了一种能力,它允许在命令行中定义符号,用于启动编译过程。

比如:

当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大些)

举例:

#include <stdio.h>
int main()
{int array[ARRAY_SIZE];int i = 0;for (i = 0; i < ARRAY_SIZE; i++){array[i] = i;}for (i = 0; i < ARRAY_SIZE; i++){printf("%d ", array[i]);}printf("\n");return 0;
}

编译指令:

//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c

🍋条件编译

在编译一个程序的时候,我们如果需要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令。

比如:

调试性的代码,删除可惜,留着又碍事,所以可以选择性的进行编译。 

常见的条件编译指令:

1.
if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__//..
#endif2.多个分支的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

我们可以举几个常见的例子

#include <stdio.h>
#define __DEBUG__int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i < 10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 #endif }return 0;
}

我们可以看到上面的代码,代码中说如果定义了_DEBUG_,那么就执行pintf语句。也就是说,当我们希望观察数组是否赋值成功,我们可以定义一个_DEBUG_来执行printf语句,测试完成没有问题之后可以将_DEBUG_的定义部分注释掉。

还有比较常见的就是下面这种编译指令:

#include <stdio.h>
#define M 10
int main()
{
#if M>0printf("hehe\n");
#endifreturn 0;
}

当 #if 后面的表达式为真,就执行里面的代码,如果为假就不执行。我们经常也可以用这种方式省略掉暂时不需要的代码

#include <stdio.h>
#define M 10
int main()
{
#if 0printf("hehe\n");
#endifreturn 0;
}

那么printf语句就不会再执行了,相当于被注释掉了。

再比如,多分支指令:

#include <stdio.h>
#define M 1
int main()
{
#if M==0printf("hehe\n");
#elif M==1printf("haha\n");
#elif M==2printf("heihei\n");
#elseprintf("ok\n");
#endifreturn 0;
}

剩下的一些条件编译大家可以自行了解,这里介绍了一些常见的。

🍋头文件的包含

  • 🌰1.头文件被包含的方式

🔥本地文件包含
#include "filename.h"

查找策略:

先在源文件所在的目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置找头文件。

如果还找不到就提示编译错误。

举例:

Linux环境的标准头文件的路径:

 /usr/include

 VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

然而在更高级的版本安装的路径就会比较分散,注意要按照自己的安装路径去找。

🔥库文件包含
#include "filename.h"

查找策略:

直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也使用 " " 的形式包含?

答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也就不容易区分是库文件还是本地文件了。

在VS2022上,我们在定义了一个头文件之后可以发现在文件第一行有这么一句话:

#pragma once

这句指令是为了防止头文件的重复包含。而在Keil 5这样的软件有可能不支持这样的指令,更多情况下用到的是这样的方法:

🔥嵌套文件包含

我们已经知道, #include 指令可以使另一个文件被编译,就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。

一个头文件被包含10次,那就实际被编译10次,如果重复包含,对编译的压力就比较大。

像上面的这种情况,com.h、com.c就在app文件中被包含了两次。

在VS2022上,我们在定义了一个头文件之后可以发现在文件第一行有这么一句指令:

#pragma once

这句指令是为了就是防止头文件的重复包含。而在 Keil 5 或者别的一些编译器有可能都不支持这样的指令,更多情况下用到的是这样的方法:

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif 

举例:


 

以上是两种防止头文件重复包含的方法。 

注:

推荐《高质量C/C++编程指南》中附录的考试试卷(很重要)。

笔试题:

🎉头文件中的 ifndef / define / endif 是干什么用的?

🎉#include <filename.h> 和 #include "filename.h" 有什么区别?

🍋其他预处理指令

当然预处理中还有更多的预处理指令,如:

#error 
#pragma
#line
#pragma pack()
...

 这篇文章介绍的内容已经很多很详尽了,如果大家还感兴趣,可以自行学习。

• ✨SumUp结语

到这里本篇文章的内容就结束了,既然都看到这里了,如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

 

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

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

相关文章

ControlNet官方资源链接【ControlNet论文原文】【持续更新中~】

ControlNet官方资源链接 ControlNet论文原文&#xff1a;https://arxiv.org/abs/2302.05543ControlNet官方GitHub&#xff1a;https://github.com/lllyasviel/ControlNetControlNet 1.1官方GitHub&#xff1a;https://github.com/lllyasviel/ControlNet-v1-1-nightlyControlNe…

phpMyAdmin增加自定义IP登录教程

phpMyAdmin增加自定义IP登录教程 1、打开phpMyAdmin目录&#xff0c; 在此目录下是否有config.sample.inc.php文件&#xff0c;如果存在&#xff0c;那么将其改名为config.inc.php&#xff08;为避免修改失误所造成的损失&#xff0c;强烈建议先备份config.sample.inc.php文件…

UnityWebGL使用sherpa-ncnn实时语音识别

k2-fsa/sherpa-ncnn&#xff1a;在没有互联网连接的情况下使用带有 ncnn 的下一代 Kaldi 进行实时语音识别。支持iOS、Android、Raspberry Pi、VisionFive2、LicheePi4A等。 (github.com) 如果是PC端可以直接使用ssssssilver大佬的 https://github.com/ssssssilver/sherpa-ncn…

[嵌入式系统-62]:RT-Thread-内核:多核CPU SMP的支持与移植

目录 RT-Thread SMP 介绍与移植 1. 多核的优点 2. 多核启动 2.1 概述 2.2 CPU0 启动流程 2.3 次级 CPU 启动流程 3. 多核调度 3.1 任务特性 3.2 调度策略 4. SMP 内核接口 处理器间中断 IPI OS Tick 自旋锁 spinlock 任务绑定 4. SMP移植说明 编译环境准备 创…

配置网关,解决本地连接不上Linux虚拟机的问题

在Window环境下&#xff0c;使用远程终端工具连接不了VMware搭建的Linux虚拟机&#xff08;CentOS 7&#xff09;&#xff0c;并且在命令行ping不通该Linux虚拟机的IP地址。下面通过配置网关解决本地与Linux虚拟机连接问题&#xff1a; 1 查看虚拟机网关地址 在VMware虚拟机上…

数据库开发关键之与DQL查询语句有关的两个案例

案例 案例1 条件分页查询 查看项目经理提供给我们的需求文档 模糊匹配的含义是 只要包含"张"就可以 use dduo;-- 按照需求完成员工管理的条件分页查询 根据输入条件 查询第一页的数据 每页展示10条记录 -- 输入条件&#xff1a; -- 姓名&#xff1a; 张 -- 年龄&…

基于YOLOv8的水稻虫害识别系统,加入BiLevelRoutingAttention注意力进行创新优化

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;基于YOLOv8的水稻虫害识别&#xff0c;阐述了整个数据制作和训练可视化过程&#xff0c;并加入BiLevelRoutingAttention注意力进行优化&#xff0c;最终mAP从原始的 0.697提升至0.732 博主简介 AI小怪兽&#xff…

c语言从入门到函数速成(2)

温馨提醒&#xff1a;本篇文章适合人群&#xff1a;刚学c又感觉那个地方不怎么懂的同学以及以及学了一些因为自身原因停学一段时间后又继续学​​​c的学 好&#xff0c;正片开始&#xff01; 数组 概念&#xff1a;数组中存放的是1个或者多个数据&#xff0c;但是数组元素个…

由于找不到msvcr110.dll,无法继续执行代码的解决方法

在日常使用计算机的过程中&#xff0c;可能会遇到系统提示缺少msvcr110.dll文件的情况&#xff0c;这一问题往往导致某些应用程序无法正常运行。幸运的是&#xff0c;有多种方法可以有效应对这一困境&#xff0c;帮助您的计算机恢复顺畅运作。以下是解决计算机丢失msvcr110.dll…

JavaWeb--1.Servlet

Servlet&#xff08;基础&#xff09; 1、配置依赖&#xff1a; ​ 在pom.xml文件中加入相关依赖 <dependencies><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0&l…

Python数据分析案例43——Fama-French回归模型资产定价(三因子/五因子)

案例背景 最近看到要做三因子模型的同学还挺多的&#xff0c;就是所谓的Fama-French回归模型&#xff0c;也就是CAMP资本资产定价模型的升级版&#xff0c;然后后面还升级为了五因子模型。 看起来眼花缭乱&#xff0c;其实抛开金融资产定价的背景&#xff0c;从机器学习角度来…

HTML_CSS学习:常用文本属性

一、文本颜色 相关代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>文本颜色</title><style>div{font-size: 90px;}.atguigu1{color: #238c20;}.atguigu2{color: rgb(2…

【b站vue教程】1 宏观视角下的浏览器——前端大厂面试必刷:前后端必学的网络安全浏览器工作原理:从入门到精通全套【附带所有源码】

课程地址&#xff1a;【前端大厂面试必刷&#xff1a;前后端必学的网络安全浏览器工作原理&#xff1a;从入门到精通全套【附带所有源码】】 https://www.bilibili.com/video/BV1UL41157hP/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1、宏…

vue3中使用crypto-js库进行加密/解密

使用crypto-js库进行加密/解密 安装 npm install crypto-js 基本使用 <template><div>使用crypto-js库进行加密/解密</div> </template><script setup> import CryptoJS from crypto-js; import { onMounted } from vue;// 加密函数 const encr…

记某APP登录逆向解密过程

最近在学习APP逆向相关的知识&#xff0c;刚好拿到了一个APP目标&#xff0c;该APP登录过程存在加密&#xff0c;所以记录下逆向破解的过程。流程 先介绍下拿到该APP后续所做的一些工作流程 选择相应版本安装到测试机当中进行抓包&#xff0c;查看数据包分析登录请求包&#x…

中国目前比较有影响力的人物颜廷利:不能升命, 活着何用?

不能‘升命’&#xff0c; 活着何用&#xff1f;…&#xff08;升命学说&#xff09; 21世纪东方哲学家思想家、科学家、当代中国教育界知名教授、专业周易起名改名字、易经姓名学专家、目前比较有影响力的人物、现代国学大师泰斗杰出代表颜廷利教授在《升命学说》‘净化论’里…

Java 类与对象

目录 1 类是什么 1.1 面向对象 1.2 面向对象与面向过程 2 类定义和使用 2.1 类的定义格式 2.2 练习定义一个狗类 3 类的实例化 4 this引用 5 对象的构造与初始化 5.1 构造方法与初始化 5.2 默认初始化 5.3 就地初始化 1 类是什么 关于类是什么&#xff0c;我们需要对…

开源版本管理系统的搭建一:SVN服务端安装

作者&#xff1a;私语茶馆 1.Windows搭建SVN版本管理系统 点评&#xff1a;SVN本身非常简洁易用&#xff0c;VisualSVN文档支撑非常好&#xff0c;客户端TortoiseSVN非常专业。5星好评。 1.1.SVN概要和组成 背景介绍 Svn是一个开源版本管理系统&#xff0c;由CollabNet公司…

一、Mysql索引的底层数据结构与算法

Mysql索引的底层数据结构与算法 前言一、索引数据结构为什么 MySQL 的索引要使用 B 树而不是其他树形结构?比如 B 树?为什么InnoDB存储引擎选择使用Btree索引结构&#xff1f; 二、索引分类思考&#xff1a;以下SQL语句&#xff0c;那个执行效率高&#xff1f;为什么&#xf…

SQL如何利用Bitmap思想优化array_contains()函数

目录 0 问题描述 1 位图思想 2 案例实战 3 小结 0 问题描述 在工作中&#xff0c;我们往往使用array_contains()函数来进行存在性问题分析&#xff0c;如判断某个数是否在某个数组中&#xff0c;但是当表数据量过多&#xff0c;存在大量array_contains()函数时&#xff0c;…