初识C语言·预处理详解

目录

1 预定义符号

2 define定义常量

3 #define定义宏

4 带有副作用的宏

5 宏替换的规则

6 宏和函数的对比

7 # 和 ##

i) #运算符

ii) ##运算符

8 命名约定

9 命令行定义

10 条件编译

条件编译1:

条件编译2:

条件编译3:

条件编译4:

11 头文件的包含


1 预定义符号

C语言里面设置了预定义符号,在预处理阶段就被处理,有以下符号:

__FILE__//进行编译的源文件
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间
__STDC__//是否支持ANSI C标准	

使用就是直接打印就好了,因为VS是不支持ANSI  C标准的,支持的话返回值就是1

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}

需要注意的就是LINE打印的时候占位符是%d,这些符号都是可以直接使用的,并且都在预处理阶段就处理完了。


2 define定义常量

define定义常量不是宏,宏和define定义常量是有一定的区别的,宏是带有参数的,定义常量就是给常量一个值,或者是一个表达式,在预处理阶段完全替换就行了。

比如:

#define MAX 1314
int main()
{
	int a = MAX;
	printf("%d ", a);
	return 0;
}

这是最简单的定义常量,定义好了预处理阶段直接替换就行,那么如果换成表达式呢?

比如我们在工作中有时候需要一个死循环,那么我们知道for循环后面三个空如果什么都不写就是自动判断为真,我们利用这一点,就可以写出如下简单的死循环:

#define forever for( ; ; )

当需要死循环的时候,加一个forever就行了

但是define定义常量的功能远不止于此,比如switch语句,每句后面都要加一个break,有的人就嫌麻烦,于是有了如下代码:

#define CASE break;case
int main()
{
	int num = 2;
	switch (num)
	{
	case 1:
		//操作1
	CASE 2:
		//操作2
	CASE 3:
		//操作3
	CASE 4:
		//操作4
	default:
		break;
	}
	return 0;
}

直接看可能有点抽象,在预处理阶段代码就会被替换成如下代码:

#define CASE break;case
int main()
{
	int num = 2;
	switch (num)
	{
	case 1:
		//操作1
		break;case 2:
		//操作2
			break;case 3:
		//操作3
				break;case 4:
		//操作4
	default:
		break;
	}
	return 0;
}

看起来可能有点不顺眼,但是代码逻辑确实是对的,这也是define定义常量的一个妙用。

当有的时候定义常量的时候代码有点长,影响整体的观看,就可以用到续行符。

#define PRINT printf("%s\n%s\n%d\n%s",__FILE__,__DATE__,__LINE__,__TIME__)

这是使用续行符之前,相较于目前写的代码是比较长的,那么可以做出如下改变:

#define PRINT printf("%s\n%s\n%d\n%s",\
					__FILE__,__DATE__,\
					__LINE__,__TIME__)

也就是在末尾加一个\就行,在VS2022里面最后一行不用加续行符,在Linux环境下的gcc是每行都需要加一个续行符的。

#define PRINT printf("%s\n%s\n%d\n%s",\ 
					__FILE__,__DATE__,\ 
					__LINE__,__TIME__)
int main()
{
	PRINT;
	return 0;
}

这段代码看似和上述无异,实际上这是一段错误代码,因为两个续行符之后加了一个空格,这就会导致报错,续行符后面是什么都不能加的,续行符后面只能是回车,可以理解为后面加了一个\n。

现在思考一个问题:

define常量的时候需不需要加上分号?

实际上有时候加上了,对代码影响不大,无非就是多了一条空语句而已。

#define M 100;
int main()
{
	int a = M;
	return 0;
}

预处理阶段就会变成int a = 100;;  也就是两个分号,但是假如我们用printf打印的时候,那问题可就大了:

printf("%d ", 100;);

代码就会变成这样,肯定就是有错误的,所以真正写的时候,是不推荐加上分号的。


3 #define定义宏

定义宏有个机制就是允许把参数替换到文本里面,这种实现方式叫做宏或者是定义宏

#define name( parament-list ) stuff

这是宏的一般形式,name和(parament-list)中间不能有任何空格,否则参数就会被解释为stuff的一部分。

现在尝试用宏实现计算一个数的平方:

#define DOUBLE_NUM(x) x * x
int main()
{
	int a = 3;
	printf("%d ", DOUBLE_NUM(a));
	return 0;
}

乍一看是没有问题的,但是如果是DOUBLE_NUM(a + 1)呢?

那么我们想要的结果是16,因为传的参数是4嘛,但是实际结果:

这是因为预处理阶段进行了完全替换,所以就变成了:

3 + 1*3 + 1

所以问题出在没有括号,在进行宏定义的时候我们尽量不要吝啬括号,不然很容易因为优先级导致出问题。

#define DOUBLE_NUM(x) ((x) * (x))
int main()
{
	int a = 3;
	printf("%d ", DOUBLE_NUM(a + 1	));
	return 0;
}

 最后的结果,严谨起见还是加上括号。如果不加括号,就会像这串代码一样:

#define DOUBLE_NUM(x) (x) + (x)
int main()
{
	int a = 3;
	printf("%d ", 10 * DOUBLE_NUM(a));
	return 0;
}

我们实际想要的结果是60,可是最后的结果是33,因为最后的结果我们没有加括号,由此可见加括号的重要性。

所以在宏定义有关于求值都应该加上括号,避免因为操作符优先级或者是临近操作符之间的作用导致不可预料的结果。

随着代码的写入,有时候宏用完了我们需要重新定义,可不能直接在原定义的下面直接加一个

#define重新定义,这是极其错误的,我们应该用到#undef:

#define MAX 100
int main()
{
	printf("%d ", MAX);
#undef MAX
	printf("%d ", MAX);
	return 0;
}

当我们把光标放在下面的MAX上的时候就会发现有错误信息,告诉你未定义标识符MAX,这是因为我们已经移除了#define MAX,那么我们想要再次使用这个标识符只需要重新定义一下就行了。

#define MAX 100
int main()
{
	printf("%d ", MAX);
#undef MAX
#define MAX 111
	printf("%d ", MAX);
	return 0;
}

4 带有副作用的宏

上面的宏定义因为括号已经导致了一些问题,实际上,还有一些看似正确的宏,会导致传的参数的值发生不可预测的变化。

int a = b + 1;
int a = b++;

比如赋值的时候,上述两种方式,第一种就是不带副作用的,第二种就是带副作用的,因为会导致b的值发生改变,往宏定义的方向去靠的话,如下:

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))
int main()
{
	int a = 3, b = 5;
	int m = MAX_NUM(a++, b++);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("m = %d\n", m);
	return 0;
}

判断a b m的值各是多少?

宏替换的时候,实际代码是int m = (a++) > (b++)? (a++):(b++);

因为3<5,所以a实际上使用了一次,那么就加一次,b同理,使用了两次,所以就加两次,但是b加一次之后整个表达式的值就已经成立了,所以m的值是6,a是4,b是7

由上面的代码,可得一个参数在宏定义里面出现多次可能会导致副作用,副作用就是宏之后,值永久被改变,所以一个参数尽量不在宏里面使用多次。


5 宏替换的规则

1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。

2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。

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

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

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

宏替换的时候无非就是查找-> 替换->插入->循环上述步骤直到检索完毕。


6 宏和函数的对比

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))

当实现比较两个数的大小的时候,如果是为了效率考虑,一般都是选择宏,比如用上述代码,那么为什么不考虑函数呢?

int Num_Max(int x, int y)
{
	return x > y ? x : y;
}

首先最直观的就是函数的返回值是有类型的,这也就意味着比较的时候我们只能比较用一类的数值,反观宏,因为没有类型,所以可以比较不同类型的数据。

第二,函数创建的时候涉及到函数栈帧的创建,创建空间的时候需要压栈出栈,是比较浪费时间的,我们可以利用汇编指令看看:

#define MAX_NUM(x,y) ((x) > (y)? (x) : (y))
int Num_Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{
	int a = 4, b = 6;
	int m = MAX_NUM(a, b);
	int n = Num_Max(a, b);
	return 0;
}

我们可以看到宏定义的汇编指令好像很多,函数对应的汇编指令好像少,可实际进行调试的时候,按F11逐语句的走就会发现函数走的语句是宏定义的是几倍,这是因为函数需要创建空间,返回值,传值,计算,这些都对应了汇编语句,⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。

所以宏的优势在于:

1 没有类型的局限性

2 占用的时间短

宏的劣势在于:

1 宏不能调试     

2 宏没有类型,所以相对来说不严谨   

3 每次使用宏相当于往程序里面加代码,除非宏比较短,不然会大幅度增加代码的长度 

4 宏会带来运算符优先级的问题

这是宏和函数的具体对比。


7 # 和 ##

i) #运算符

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

#的操作可以理解为是字符串化

先看一段代码:

int main()
{
	printf("hello""world");
	return 0;
}

这段代码也是没有问题的,最后两个字符串打印出来也是hello world。

那么,现在的问题是如果我们有一个整型a  = 10,想要打印the value of a is 10,

int main()
{
	int a = 10;
	printf("The value of a is %d\n", a);
	return 0;
}

这是一般写法,如果我们想要用宏定义实现呢?宏定义实现有一个难点就是%d那里怎么做,这里可以结合上面的printf来操作。

#define PRINT(val,format) printf("The value of val is "format"\n",val)
int main()
{
	int a = 10;
	PRINT(a, "%d");
	return 0;
}

因为宏定义的参数是没有类型的,所以我们为了所有参数都可以实现这个功能,就用双引号传占位符进去,但是打印的时候,会出现这么个情况:

我们发现val并没有被替换:

#define PRINT(val,format) printf("The value of " #val " is "format"\n",val)

当我们做了如上修改之后,也就是单独给val领出来,加个#运算符,#a就会被替换成"a",这样就可以实现我们想要的功能了。

ii) ##运算符

##运算符可以把两边的符号黏在一起,但是前提是粘在一起组成的新标识符是合法的,不然结果就是未定义的,所以##运算符也被称为记号粘合

当我们求两个值里面的最大值的时候,我们如果创建函数,那每一个类型我们都要对应一个函数来写,就非常的麻烦,采用宏的方式,就会简单很多:

#define NUM_MAX(type)\
type type##_max(type x,type y)\
{\
	return (x > y?x : y);\
}
NUM_MAX(int)
int main()
{
	int m = int_max(1, 5);
	printf("%d ", m);
	return 0;
}

创建好宏直接,NUM_MAX(int),就表示有个函数专门比较整型大小的,好奇心强点的就会去把##删了,因为感觉加不加都一样,但是一下就报错了,像这样:

没有加记号粘合的时候就会报错,说int_max是未定义的,这是为什么?函数就是这样,我们也不知道为什么,但是实际上这两个运算符用的比较少,我们以后看到了知道怎么用就行了,不必深究。


8 命名约定

我们在定义函数的时候和定义宏的时候取名是有所区别的,我们定义宏的时候一般都是全大写

定义,函数名一般都不全大写,一般首字母大写,或者是_后面的是首字母大写,这样具有区分度,当然,这是一般情况,有特殊情况再看咯。


9 命令行定义

许多C 的编译器提供了⼀种能⼒,允许在命令⾏中定义符号,⽤于启动编译过程。

在VS操作里面是无法实现的:

#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;
}

VS里面这段代码肯定是会报错的,但是在Linux环境下的gcc编译器就可以实现命令行定义,在实现编译的时候我们加上如下指令:

gcc -D ARRAY_SIZE=10 programe.c

也就是说我们给ARRAY_SIZE一个值,然后才开始编译,由此可得,命令行编译就是在编译之前给某个变量一个值,然后才会开始编译。

具体应用比如我们需要一个数组,根据不同因素需要指定数组的不同大小,我们不仅可以利用到柔性数组,也可以用到命令行定义,代码的局限性就又少了一些。


10 条件编译

在代码调试的时候,因为有了条件编译指令的存在,使得我们舍弃一段代码或者一条语句是非常方便的,比如:

条件编译1:

#define MAX
int main()
{
#ifdef MAX
	printf("hahahaha");
#endif
	return 0;
}

当我们不想要打印hahaha的时候,我们就在define前面注释一下就行了,一段语句这样做有点大动干戈的样子,但如果是一段代码呢?

#ifdef #endif是配套使用的,标识如果定义了MAX,就返回1,表示为真,那么下面的语句就执行。

条件编译2:

#if 常量表达式
#endif
#define M 100
int main()
{
#if M==2
	printf("666");
#endif
	return 0;
}

这是一般用法,也可以直接放一个M上去。

这里需要注意的是常量表达式由预处理器处理结果,所以如果出现这种代码:

#define M 100
int main()
{
	int a = 100;
#if M==a
	printf("666");
#endif
	return 0;
}

这种代码就是犯糊涂了的代码,看似结果应该是打印666的,但是并不会打印,因为a是局部变量,局部变量创建是在程序开始运行的时候才开始创建,而预处理阶段领先于main函数栈帧的创建,所以自然比较结果就是0。

条件编译3:

#if 1
	//操作1
#elif 0
	//操作2
#else
	//操作3
#endif

到这里相信给你的感觉是很熟悉的,因为确实和if语句很相似,只不过这是判断的是define定义的常量或者是常量而已,但是endif是万万不能掉的,掉了编译器都不会让你过的。

条件编译4:

#define M
int main()
{
#ifndef M
	printf("ddd");
#endif
	return 0;
}

其实条件编译1和条件编译4是一类的,无非就是判断标识符有没有被定义而已,一个用到#ifdef 也就是if define,另一个就是#ifndef,也就是if not define,所以这里不过多介绍了。

这几个给你感觉很像if语句吧?那么它们也会存在嵌套使用的,这里就留给你自行探索咯。


11 头文件的包含

头文件的包含有两种方法:

#include "stdio.h"
#include <stdio.h>

实际上这两种方法也是有差别的,如果是用的双引号包含的,那么寻找头文件的时候就会先从该工程的本地目录开始找头文件,如果没有就去标准位置里面找,找不到就报错,如果是尖括号包含的,寻找头文件的时候就会从标准位置开始寻找,找不到就报错。

这样看起来好像是双引号包含了尖括号,那我们实际使用的时候为什么不都使用双引号呢?因为效率相对来说低一些,我们明明知道头文件是在标准位置,结果我们还非要在根目录找一下浪费时间,效率自然就低了一下。

有的时候引用头文件不小心引用了多次,原本是会对编译造成比较大的压力的,因为多次包含头文件,预处理阶段就会多出来很多行代码,更别提嵌套引用的头文件了,那么如何解决这个问题呢?

答:使用条件编译

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

在头文件里面加这个就行,第一次是没有定义东西的,第二次引用之后,发现定义了,于是头文件的内容就不会加进去,也就达到了只引用一次头文件的效果。

或着是:

#pragma once

也可以避免头文件的多次引用,这是其他的预处理指令了,有兴趣的话可以多了解一下

#error
#pragma
#line

这些是一部分预处理指令,有兴趣君可自行探索。


感谢阅读!

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

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

相关文章

数字IC实践项目(9)— Tang Nano 20K: I2C OLED Driver

Tang Nano 20K: I2C OLED Driver 写在前面的话硬件模块RTL电路和相关资源报告SSD1306 OLED 驱动芯片SSD1306 I2C协议接口OLED 驱动模块RTL综合实现 总结 写在前面的话 之前在逛淘宝的时候偶然发现了Tang Nano 20K&#xff0c;十分感慨国产FPGA替代方案的进步之快&#xff1b;被…

CrystalDiskInfo:一款免费的硬盘健康检测软件

CrystalDiskInfo&#xff1a;一款免费的硬盘健康检测软件&#xff0c;可以显示出硬盘的使用时间、温度、剩余寿命和健康状态等。该软件支持多种语言和多种硬盘类型&#xff0c;使用简单&#xff0c;操作直观。 感觉真正有用的是读取到的硬盘通电时间&#xff0c;其他的估计意义…

Unity类银河恶魔城学习记录4-1,4-2 Attack Logic,Collider‘s collision excepetion源代码 P54 p55

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Entity.cs using System.Collections; using System.Collections.Generic; u…

2024年阿里云服务器活动价格表

2024年2月阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核…

LEETCDE 220. 存在重复元素 III

class Solution { public:long long size;bool containsNearbyAlmostDuplicate(vector<int>& nums, int indexDiff, int valueDiff) {//桶排序unordered_map<long,long> m;sizevalueDiff1;for(int i0;i<nums.size();i){//控制数值long long idxgetID(nums[i…

双非本科准备秋招(20.1)—— 并发编程之生产者消费者

生产者消费者 与保护性暂停中的不同&#xff0c;不需要产生结果和消费结果的线程一一对应。 生产者仅负责产生结果数据&#xff0c;不关心数据该如何处理&#xff0c;而消费者专心处理结果数据 JDK 中各种阻塞队列&#xff0c;采用的就是这种模式 代码实现&#xff1a; 首先…

RK3588平台开发系列讲解(AI 篇)什么是NPU

文章目录 一、什么是NPU二、什么是RKNPU沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解什么是NPU。 一、什么是NPU 📢什么是 NPU 呢? 在谈这个问题之前,可以先来看看什么是 CPU 和 GPU,CPU 就是中央处理器,中央处理器就好像是人类的大脑,主要负…

IntelliJ IDEA 2023.3发布,AI 助手出世,新特性杀麻了!!

目录 关键亮点 对 Java 21 功能的完全支持 调试器中的 Run to Cursor&#xff08;运行到光标)嵌入选项 带有编辑操作的浮动工具栏 用户体验优化 Default&#xff08;默认&#xff09;工具窗口布局选项 默认颜色编码编辑器标签页 适用于 macOS 的新产品图标 Speed Sear…

【开源】基于JAVA+Vue+SpringBoot的停车场收费系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

Stable Diffusion 模型下载:Samaritan 3d Cartoon(撒玛利亚人 3d 卡通)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 由“PromptSharingSamaritan”创作的撒玛利亚人 3d 卡通类型的大模型&#xff0c;该模型的基础模型为 SD 1.5。 条目内容类型大模型基础模型SD 1.5来源CIVITAI作者…

DFS深度优先搜索与回溯算法

目录 递归遍历的三步骤&#xff1a; DFS/回溯模板 练习 1.三角形路径和最大搜索 &#xff08;一&#xff09;前序DFS&#xff08;从上至下搜索&#xff0c;实际是暴力解法&#xff0c;测试超时&#xff09; &#xff08;二&#xff09;后序DFS&#xff08;自底向上搜索&am…

复制和粘贴文本时剥离格式的5种方法(MacWindows)

您可能每天复制和粘贴多次。虽然它是一个非常方便的功能&#xff0c;但最大的烦恼之一就是带来了特殊的格式。从网络上获取一些文本&#xff0c;您经常会发现粘贴到文档中时&#xff0c;它保持原始样式。 我们将展示如何使用一些简单的技巧在不格式化的情况下复制和粘贴。 1.…

ubuntu20安装mongodb

方法一&#xff1a;直接安装(命令是直接从mongo官网Install MongoDB Community Edition on Ubuntu — MongoDB Manual复制的&#xff09; cat /etc/lsb-release sudo apt-get install -y gnupg curl curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \sudo gp…

Qt 字符串类应用与常用基本数据类型

目录 操作字符串 查询字符串 Qt 常见数据类型 操作字符串 创建一个控制台项目 &#xff08;1&#xff09;QString提供一个二元的 “” 操作符&#xff0c;主要用于组合两个字符串。QString str1 "Hello World 传递给QString一个 const char* 类型的ASCII字符串 “He…

django线上教育学习平台大数据分析系统python

随着互联网技术不断地发展&#xff0c;网络与大数据成为了人们生活的一部分&#xff0c;而线上教育平台大数据分析作为网上应用的一个全新的体现&#xff0c;由于其特有的便捷性&#xff0c;已经被人们所接受。目前主流的线上教育平台大数据分析服务不仅不明确并且管理盈利较低…

Tkinter教程22:DataFrame数据加入到treeview树视图(含横纵滚动条+正反排序)

------------★Tkinter系列教程★------------ Tkinter教程21&#xff1a;Listbox列表框OptionMenu选项菜单Combobox下拉列表框控件的使用绑定事件 Tkinter教程20&#xff1a;treeview树视图组件&#xff0c;表格数据的插入与表头排序 Python教程57&#xff1a;tkinter中如何…

SpringCloud-Eureka原理分析

Eureka是Netflix开源的一款用于实现服务注册与发现的工具。在微服务架构中&#xff0c;服务的动态注册和发现是必不可少的组成部分&#xff0c;而Eureka正是为了解决这一问题而诞生的。 一、为何需要Eureka 在微服务架构中&#xff0c;服务之间的协同合作和高效通信是至关重要…

[计算机提升] 备份系统:系统映像

6.3 备份系统&#xff1a;系统映像 备份系统和还原系统是一套互补的操作。 操作系统的备份就是将操作系统当前的所有数据复制到硬盘的一个空闲区域&#xff0c;以防止系统崩溃或数据丢失。还原操作则是将先前备份的数据恢复到操作系统中&#xff0c;使系统回到之前的样子&…

17:定时器编程实战

1、实验目的 (1)使用定时器来完成LED闪烁 (2)原来实现闪烁时中间的延迟是用delay函数实现的&#xff0c;在delay的过程中CPU要一直耗在这里不能去做别的事情。这是之前的缺点 (3)本节用定时器来定一个时间&#xff08;譬如0.3s&#xff09;&#xff0c;在这个定时器定时时间内…

Python解决SSL不可用问题

参考&#xff1a;https://blog.csdn.net/weixin_44894162/article/details/126342591 一、问题描述&#xff1a; 报错概述&#xff1a; WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. ## 警告:pip配…
最新文章