【C语言】详解预处理

news2024/12/23 14:51:02

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

目录

•✨说在前面

 🍋预定义符号

 🍋 #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;;
	else
		m = -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 10

int 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 100

int main()
{
	int m = 10;
	int n = 5;
	int t = M + (m * n);//100+(10*5)=150
#undef M
	int M = 5;
	int ret = M + t;//150+5=155
	printf("%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__
	  //..
#endif

2.多个分支的条件编译
#if 常量表达式
	  //...
#elif 常量表达式
	  //...
#else
	  //...
#endif

3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

4.嵌套指令
#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 <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>0
	printf("hehe\n");
#endif

	return 0;
}

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

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

	return 0;
}

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

再比如,多分支指令:

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

	return 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.coloradmin.cn/o/1643791.html

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

相关文章

解决HTTP 403 Forbidden错误:禁止访问目录索引问题的解决方法

解决HTTP 403 Forbidden错误&#xff1a;禁止访问目录索引问题的解决方法 过去有人曾对我说&#xff0c;“一个人爱上小溪&#xff0c;是因为没有见过大海。”而如今我终于可以说&#xff0c;“我已见过银河&#xff0c;但我仍只爱你一颗星。” 在Web开发和服务器管理中&#x…

3-qt综合实例-贪吃蛇的游戏程序

引言&#xff1a; 如题&#xff0c;本次实践课程主要讲解贪吃蛇游戏程序。 qt贪吃蛇项目内容&#xff1a; 一、功能需求 二、界面设计 各组件使用&#xff1a; 对象名 类 说明 Widget QWidge 主窗体 btnRank QPushButton 排行榜-按钮 groupBox QGroupBox 难…

C/C++开发,opencv-ml库学习,ml模块代码实现研究

目录 一、opencv-ml模块 1.1 ml简介 1.2 StatModel基类及通用函数 1.3 ml模块各算法基本应用 二、ml模块的实现原理 2.1 cv::ml::StatModel的train函数实现原理 2.2 cv::ml::StatModel的predict函数实现原理 2.3 cv::ml::StatModel的save函数和load函数 一、opencv-ml模…

Nginx(搭建高可用集群)

文章目录 1.基本介绍1.在微服务架构中的位置2.配置前提3.主从模式架构图 2.启动主Nginx和两个Tomcat1.启动linux的tomcat2.启动win的tomcat3.启动主Nginx&#xff0c;进入安装目录 ./sbin/nginx -c nginx.conf4.windows访问 http://look.sunxiansheng.cn:7777/search/cal.jsp 3…

力扣 647. 回文子串

题目来源&#xff1a;https://leetcode.cn/problems/palindromic-substrings/description/ C题解1&#xff1a;暴力解法。不断地移动窗口&#xff0c;判断是不是回文串。 class Solution { public:int countSubstrings(string s) {int len s.size();int res 0;for(int i 0;…

【机器学习-21】集成学习---Bagging之随机森林(RF)

【机器学习】集成学习---Bagging之随机森林&#xff08;RF&#xff09; 一、引言1. 简要介绍集成学习的概念及其在机器学习领域的重要性。2. 引出随机森林作为Bagging算法的一个典型应用。 二、随机森林原理1. Bagging算法的基本思想2. 随机森林的构造3. 随机森林的工作机制 三…

Samsung三星NP930XCJ-K01CN笔记本原厂Win10系统安装包下载

三星SAMSUNG笔记本电脑原装出厂Windows10预装OEM系统&#xff0c;恢复开箱状态自带系统 链接&#xff1a;https://pan.baidu.com/s/1Y3576Tsp8MtDxIpJGDucbA?pwdt0ox 提取码&#xff1a;t0ox 三星原装W10系统自带声卡,网卡,显卡,指纹,蓝牙等所有驱动、三星出厂主题专用壁纸…

vivado 在硬件中调试串行 I/O 设计-属性窗口

只要在“硬件 (Hardware) ”窗口中选中 GT 或 COMMON 块、在“链接 (Link) ”窗口中选中链接 &#xff0c; 或者在“扫描 (Scan)”窗口中选中扫描 &#xff0c; 那么就会在“ Properties ”窗口中显示该对象的属性。对于 GT 和 COMMON &#xff0c; 包括这些对象的所有属性、…

未雨绸缪:25岁Python程序员如何规划职业生涯,避免35岁职业危机?

一、程序员如何避免中年危机&#xff1f; 为了避免在35岁时被淘汰&#xff0c;程序员在25岁时可以采取一系列策略来规划自己的职业发展和提升技能。以下是我给大家整理的一些建议&#xff1a; 1. 持续学习 科技行业更新换代迅速&#xff0c;程序员需要保持对新技术和工具的敏…

揭秘大模型应用如何成为当红顶流?

Kimi广告神话背后的关键词战略 如果你生活在中国&#xff0c;你可能不认识ChatGPT&#xff0c;但你一定知道Kimi。无论是学生党还是打工人&#xff0c;都无法避开Kimi的广告。 刘同学在B站上搜教学视频时&#xff0c;弹出了一则软广&#xff0c;上面写着&#xff1a;“作业有…

SQL 基础 | BETWEEN 的常见用法

在SQL中&#xff0c;BETWEEN是一个操作符&#xff0c;用于选取介于两个值之间的数据。 它包含这两个边界值。BETWEEN操作符常用于WHERE子句中&#xff0c;以便选取某个范围内的值。 以下是BETWEEN的一些常见用法&#xff1a; 选取介于两个值之间的值&#xff1a; 使用 BETWEEN来…

批处理优化

1.4、总结 Key的最佳实践 固定格式&#xff1a;[业务名]:[数据名]:[id]足够简短&#xff1a;不超过44字节不包含特殊字符 Value的最佳实践&#xff1a; 合理的拆分数据&#xff0c;拒绝BigKey选择合适数据结构Hash结构的entry数量不要超过1000设置合理的超时时间 2、批处理优…

​【收录 Hello 算法】第 3 章 数据结构

第 3 章 数据结构 Abstract 数据结构如同一副稳固而多样的框架。 它为数据的有序组织提供了蓝图&#xff0c;算法得以在此基础上生动起来。 本章内容 3.1 数据结构分类3.2 基本数据类型3.3 数字编码 *3.4 字符编码 *3.5 小结

课时115:sed命令_进阶实践_高阶用法2

2.2.4 高阶用法2 学习目标 这一节&#xff0c;我们从 暂存实践、其他实践、小结 三个方面来学习。 暂存实践 简介 我们可以在缓存空间和暂存空间中进行数据的简单读取&#xff0c;还可以对数据进行一些复杂性的编辑操作常见的高阶命令P 打印模式空间开端至\n内容&#xff0…

Unable to find assembler. Install ‘yasm‘ or ‘nasm.‘ To build without

Unable to find assembler. Install yasm or nasm. To build without 一、概述二、解决办法 一、概述 系统&#xff1a;Ubuntu 22.04 在编译一个项目的时候提示我汇编器有一个问题&#xff0c;一个ysam 或者 nasm未安装 二、解决办法 sudo apt install yasm

银行智能化数据安全分类分级实践分享

文章目录 前言一、数据安全智能分类分级平台建设背景二、数据安全分类分级建设思路和实践1、做标签– 数据安全标签体系2、打标签– 鹰眼智能打标平台 3.03、用标签– 全行统一“数据安全打标签结果”服务提供前言 随着国家对数据安全的高度重视,以及相关法律法规的出台,数据…

【Leetcode每日一题】 分治 - 排序数组(难度⭐⭐)(69)

1. 题目解析 题目链接&#xff1a;912. 排序数组 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 归并排序&#xff08;Merge Sort&#xff09;是一种采用“分而治之”&#xff08;Divide and Conquer&#xff09;策略…

HNU-人工智能-实验3-贝叶斯分类器

人工智能-实验3 计科210x 甘晴void 【感悟】本实验值得自己完成一遍 文章目录 人工智能-实验3一、实验目的二、实验平台三、实验内容3.0 基础知识3.1 条件概率&#xff08;选择题&#xff09;3.2 贝叶斯公式&#xff08;选择题&#xff09;3.3 朴素贝叶斯分类算法流程3.3.1 算…

【华为】AC三层旁挂直接转发

【华为】AC三层旁挂直接转发 实验需求实验拓扑配置AC和AP二层通信ACLSW1LSW2AP2获取到的管理地址AP3获取到的管理地址 AP上线配置WLAN业务ACLSW1&#xff08;作DHCP地址池&#xff09;业务成功下发 访问公网&#xff08;NAT&#xff09;LSW1AR1 配置文档ACLSW1LSW2AR1 实验需求…

原来spring也可以AI

最近大模型是相当的火&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;、图像识别、语音识别等领域的应用&#xff0c;那对于工程同学来说应该如何接住这波破天的富贵呢&#xff1f; 想啥来啥&#xff0c;前段时间LangChain给我们整了一套钢铁战甲&#xff0c;让…