【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;;
	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结语

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle对空值(NULL)的 聚合函数 排序

除count之外sum、avg、max、min都为null&#xff0c;count为0 Null 不支持加减乘除&#xff0c;大小比较&#xff0c;相等比较&#xff0c;否则只能为空&#xff1b;只能用‘is [not] null’来进行判断&#xff1b; Max等聚合函数会自动“过滤null” null排序默认最大&#xf…

【STL】map和set的原理及其使用

文章目录 关联容器键值对setset的介绍set的使用set的构造函数声明1&#xff1a;函数声明2&#xff1a;函数声明3&#xff1a; set的迭代器begin和endrbegin和rend set的容量empty()size&#xff08;&#xff09; set的修改操作inserteraseclearfindcount mapmap的介绍map的构造…

拼多多怎么推广才有效果

拼多多店铺的有效推广需要综合考虑多个方面&#xff0c;包括优化店铺信息、商品详情、参与平台活动、利用社交媒体、精准营销和客户服务等。具体如下&#xff1a; 拼多多推广可以使用3an推客。3an推客&#xff08;CPS模式&#xff09;给商家提供的营销工具&#xff0c;由商家自…

Go Web 开发【Gin 框架快速开发】

1、Gin Web 快速开发 1.1、环境准备 1.1.1、导入 gin 依赖 这里就叫 gin 依赖了&#xff0c;在 Goland 命令行中输入下面的命令&#xff1a; go get -u github.com/gin-gonic/gin 1.1.2、设置代理 如果下载失败&#xff0c;最好设置一下代理&#xff0c;在 cmd 命令行中输…

功能测试_分类_用例_方法

总结 测试分类 按阶段分类 是否查看源代码分类 是否运行分类 是否自动化 其他分类 软件质量模型 开发模型-瀑布模型 测试过程模型 v w 测试用例八大要素 用例编号 用例标题 …

海外仓系统:为什么对小型海外仓企业尤为重要,该怎么看待wms系统

相对于大型海外仓企业来说&#xff0c;小型海外仓受到资金和规模的限制&#xff0c;在库存管理、订单处理能力上面临的问题尤其大。而这正是海外仓系统擅长的地方&#xff0c;现代的海外仓系统逐渐发展以云端部署方式为主&#xff0c;这也为小型海外仓企业提供了很多便利。 1、…

基于Pytorch深度学习——GPU安装/使用

本文章来源于对李沐动手深度学习代码以及原理的理解&#xff0c;并且由于李沐老师的代码能力很强&#xff0c;以及视频中讲解代码的部分较少&#xff0c;所以这里将代码进行尽量逐行详细解释 并且由于pytorch的语法有些小伙伴可能并不熟悉&#xff0c;所以我们会采用逐行解释小…

Java中的Lambda表达式

Lambda表达式的标准格式 格式&#xff1a;&#xff08;形式参数&#xff09;->{代码块} 形式参数&#xff1a;如果有多个参数&#xff0c;参数之间用逗号隔开 如果没有参数&#xff0c;留空即可 ->&#xff1a;由英文中画线和大于符号组成&#xff0c;固定写法。代表着…

学习中遇到的问题

1.UFUNCTION() 不是所有函数都能加UFUNCTION()修饰&#xff0c;涉及UE反射机制。 2.初始化用{} 初始化列表 3.创建C文件时修改了路径 这时.cpp文件会报错&#xff0c;只需删掉前面多余路径即可 4.函数的移除 1.虚幻5.1 UUserWidget不再包含OnLevelRemovedFromWorld() 转而使用…

微信CRM管理系统、企业个人微信号管理对接接口

接口地址&#xff1a; POST/login/getLoginQrCode appId参数为设备ID&#xff0c;首次登录传空&#xff0c;会自动触发创建设备&#xff0c;掉线后重新登录则必须传接口返回的appId&#xff0c;注意同一个号避免重复创建设备&#xff0c;以免触发官方风控 取码时传的appId需要…

python邮件发送

第一种方式 一&#xff1a;发送的邮件要设置授权码&#xff0c;通过邮箱邮箱授权码去验证&#xff0c;让邮件服务器帮我们去转发邮件到要接收的邮件&#xff0c;代码中的授权码&#xff0c;是需要登录126邮箱&#xff08;我这里是以126邮件发送的&#xff0c;具体的以自己为准…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-PWR电源控制

PWR简介 PVD可用在电池供电或安全要求比较高的设备&#xff0c;如果供电电压在逐渐下降&#xff0c;在电压过低的情况下可能会导致内外电路出现不确定的错误。为了避免不必要的错误&#xff0c;可以在电源电压过低的情况下&#xff0c;提前发出警告并关闭较为危险的设备 关闭的…

循环神经网络模块介绍(Pytorch 12)

到目前为止&#xff0c;我们遇到过两种类型的数据&#xff1a;表格数据和图像数据。对于图像数据&#xff0c;我们设计了专门的卷积神经网络架构(cnn)来为这类特殊的数据结构建模。换句话说&#xff0c;如果我们拥有一张图像&#xff0c;我们 需要有效地利用其像素位置&#xf…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(三)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 继续接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 当我们点击 submit 提…

【JavaEE 初阶(一)】初识线程

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多线程知识 目录 1.前言2.进程3.线程4.线程和进程的区别5.Thread创建线程5.1继承Thread创建线程5.2实现R…

非平衡数据处理-SMOTE Tomek算法(互联网最全)

作者Toby&#xff0c;来源公众号&#xff1a;Python风控模型&#xff0c;非平衡数据处理-SMOTE Tomek算法 之前Toby老师讲了非平衡数据处理相关知识&#xff0c;具体内容和链接如下。 imbalanced data机器学习非平衡数据处理 Python非平衡数据处理_SMOTE-ENN 方法 非平衡数…

【SSM进阶学习系列丨分页篇】PageHelper 分页插件导入集成实践

文章目录 一、说明什么是分页PageHelper介绍 二、导入依赖三、集成Spring框架中四、编写Service五、编写Controller六、编写queryAllByPage页面展示数据 一、说明 什么是分页 ​ 针对分页&#xff0c;使用的是PageHelper分页插件&#xff0c;版本使用的是5.1.8 。 ​ 参考文档…

虚拟机网络实现桥接模式

虚拟机网络实现桥接模式 虚拟化软件&#xff1a;VMware 17 Linux&#xff1a;rocky8_9 主机&#xff1a;Win10 文章目录 虚拟机网络实现桥接模式1. 桥接模式介绍2. 查看Win本机的网络信息&#xff08;以笔记本电脑以WiFi联网为例&#x…

【Canvas与艺术】录王昌龄出塞诗“秦时明月汉时关”

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>使用HTML5/Canvas绘制秦时明月汉时关</title><style type&q…

【论文阅读】Sparse is Enough in Scaling Transformers

Sparse is Enough in Scaling Transformers 论文地址摘要1 介绍2 相关工作模型压缩。模型修剪模型蒸馏。稀疏注意力。张量分解。稀疏前馈。 3 Sparse is Enough3.1 稀疏前馈层3.2 稀疏 QKV 层3.3 稀疏损失层。 4 长序列的稀疏性4.1 长序列架构4.2 内存效率的可逆性4.3 泛化的循…