详解C语言—预处理

news2024/11/18 14:00:18

目录

1、预处理

(1)预定义符号介绍

(2)预处理指令 #define

#define 定义标识符: 

#define 定义宏:

#define 替换规则

(3)预处理操作符#

(4)预处理操作符##

 (5)带副作用的宏参数

 (6)宏和函数对比

2、命名约定

3、预处理指令 #undef

4、命令行定义

5、条件编译 

(1)单分支#if:

(2)多分支#if:

(3)判断是否被定义

(4)嵌套指令

 6、文件包含

头文件被包含的方式:

嵌套文件包含:

小结


1、预处理

(1)预定义符号介绍

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的,程序员可直接使用。

#include <stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%d\n", __STDC__);//当前VS是不支持ANSI C
	return 0;
}

(2)预处理指令 #define

#define 定义标识符: 

#define 宏名 值或代码片段

#define 是用来定义宏的预处理指令。宏是一种在源代码中定义的符号,可以用来代表一个常量、一个表达式或一段代码片段。宏的定义通常在源代码文件的顶部,以便在编译之前被预处理器处理。  

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,  \
                          __DATE__,__TIME__ )  

#define 定义宏:

带参数的宏:宏也可以带有参数,允许你创建可重用的代码片段。例如:

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

 SQUARE 宏接受一个参数x,并返回x的平方。在使用时,你可以这样调用宏:int result = SQUARE(5);,它会被展开为 int result = ((5) * (5));

 接下来我们看一个例子:

#define SQUARE(x)  x * x

如果我们写成这样不带括号, 对函数传入参数 5 + 1 :SQUARE( 5 + 1 );
得到的结果将是 11,而不是36,计算过程:5 + 1 * 5 + 1 得到结果为11。

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

当我们加上括号,计算过程:(5+1)*(5+1) 得到结果为36.  

这样定义看似没有问题了,我们再看一个例子: 

#define SQUARE(x) (x) + (x)
int main()
{
    int a = 5;
    printf("%d\n" ,10 * DOUBLE(a));
    return 0;
}

 这种情况下计算过程为 10 * (5) + (5)); 得到结果为55,并不是我们想要的100。

这时再对参数整体添加一对括号即可解决问题:

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

通过这几个例子我们理解了为什么要加那些那些括号。 

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。  

#define 替换规则

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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

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

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

  • 解释:当预处理器搜索由#define定义的符号时,它并不会搜索或替换字符串常量中的内容。这是因为字符串常量在C中是不可更改的文本序列,不应受到#define定义的符号的影响。
  • #include <stdio.h>
    
    #define VALUE 42
    
    int main() {
        int x = VALUE;
        printf("The value of x is: %d\n", x);
        
        printf("This is a string with VALUE: \"%d\"\n", VALUE);
        
        return 0;
    }

    在上面的示例中,我们定义了一个名为VALUE的符号,它被定义为整数42。然后,我们在程序中使用了这个符号。请注意以下两个地方:

  • 在第5行,我们使用了VALUE来初始化整数变量x。在这里,#define定义的符号VALUE被替换为42,因此x的值是42

  • 在第7行,我们在字符串常量中包含了VALUE。在这里,VALUE并不会被替换为42,而是保持不变。因此,字符串中的内容是"This is a string with VALUE: \"VALUE\""

  • 所以,尽管#define定义的符号VALUE在程序中的某些地方被替换为其定义的值,但它并不会影响字符串常量中的内容。字符串常量中的文本保持不变,不受符号替换的影响。这是为了确保字符串常量的内容始终保持不变。

(3)预处理操作符#

我们由一个例子来引入讲解: 

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

如何通过#define实现上述代码中printf语句的相同功能呢?

通过前面的学习可以很轻松实现#define定义宏,代码如下: 

#define Print(n) printf("the value of n is %d",n)
int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	Print(a);
	return 0;
}

但我们的输出语句是the value of n is 20,并不是我们传入的参数 a ,

那怎么修改 #define 把参数插入到字符串?

我们通过添加双引号将“参数前后字符串”分隔成“独立的两个字符串”。再在参数前添加 # 使其在宏展开时被替换为参数 n

#define Print(n) printf("the value of "#n" is %d",n)
int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	Print(a);
	return 0;
}

如果我们要输出不同格式,那怎么修改#define呢?

添加一个参数代替格式化字符即可解决。

#define Print(n,format) printf("the value of "#n" is " format "\n",n)
int main()
{
	float f = 4.5f;
	printf("the value of a is %f\n", f);
	Print(f, "%f");
	return 0;
}

(4)预处理操作符##

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。

看下面的例子就明白了: 

#define CAT(x,y) x##y
int main()
{
	int Class110 = 110;
	printf("%d\n", Class110);
	printf("%d\n", CAT(Class, 110));
	return 0;
}

两种输出形式结果一样:  

 (5)带副作用的宏参数

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

例如:

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

我们看下面的例子:

#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
	int a = 5;
	int b = 6;
	int c = MAX(a++, b++);
	printf("a=%d\nb=%d\nc=%d\n", a, b, c);
	return 0;
}

 替换宏的文本:c=MAX( (a++)>(b++) ? (a++):(b++) ) 首先判断 ab 的大小,之后a与b自增:a=6 b=7b 大于 a 则 c 被赋值为MAX的返回值 b的值7 即( c=7 ), 赋值之后b进行自增,b=8

输出结果: 

 (6)宏和函数对比

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

比如:在两个数中找出较大的一个

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

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

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

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。

所以宏比函数在程序的规模速度方面更胜一筹

2. 更为重要的是函数的参数必须声明为特定的类型。

所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。

宏是类型无关的

宏的缺点:当然和函数相比宏也有劣势的地方:

  1. 宏由于类型无关,也就不够严谨。
  2. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  3. 宏是没法调试的。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
  5. 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。比如下面的例子:
#define Malloc(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p = (int*)malloc(126 * sizeof(int));
	int* p = Malloc(126, int);
	return 0;
}

 宏和函数详细对比: 

2、命名约定

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

一般习惯宏名全部大写,函数名不要全部大写。

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

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

3、预处理指令 #undef

这条指令用于移除一个宏定义。  

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

 示例:

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

int main()
{
	int c = MAX(3, 5);
	printf("%d\n", c);
#undef MAX
	c = MAX(5, -5);
	printf("%d\n", c);
	return 0;
}

 当我们运行时,程序报错

我们想要修改 c 的值,宏MAX已经失效了,所以程序报错“MAX”未定义。 

4、命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。
在gcc环境下演示:

 

 gcc 文件名.c -D SZ=10 -o 文件名,通过这种操作在命令行中定义符号。

 

5、条件编译 

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#if 是一个预处理指令,用于根据指定的条件编译代码块。
#if 常量表达式
 //...这里是条件为真时要编译的代码
#endif

在这个结构中,#if 后面可以跟一个常量表达式,如果这个表达式的值为非零(true),那么就会编译 #if 和 #endif 之间的代码,否则,这部分代码会被预处理器直接忽略,不参与编译。

常量表达式是在预处理阶段求值的,它通常包括常量、宏和运算符,但不能包括任何需要运行时计算的东西。

(1)单分支#if:

#define DEBUG 1

#if DEBUG
   // 这里是调试时要编译的代码
#else
   // 这里是非调试时要编译的代码
#endif
  • 在这个例子中,如果 DEBUG 宏被定义且值不为零,那么将会编译第一部分的代码,否则编译第二部分的代码。这种条件编译的方式常用于在调试和发布版本之间切换代码,或者根据不同的平台选择性地包含或排除特定的代码块。
  • 需要注意的是,#if 只是一种条件编译的方式,而且它只能根据常量表达式的真假来进行选择性编译,不能用于运行时的条件判断。如果需要在运行时进行条件判断,应该使用 if 语句。

 在gcc环境中,我们可以看到经过预处理后,没有满足条件的语句,所以没有语句参与编译。

 

(2)多分支#if:

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

在gcc环境中,我们可以看到经过预处理后,只有满足条件的 printf("hehe\n"); 参与编译。

if-else 什么条件执行什么代码,#if-#else-#endif 什么条件编译什么代码 

(3)判断是否被定义

#if defined(symbol)
#ifdef symbol

#if ! defined(symbol)
#ifndef symbol

这些都是条件编译的预处理指令,用于根据符号是否被定义来选择性地编译代码块。

#if defined(symbol)& #ifdef symbol( #if defined(symbol) 的简写)

这个指令用于检查是否已经定义了名为 symbol 的宏。

如果 symbol 已经被定义,那么 #if defined(symbol) 就被视为真(非零),相应的代码块会被编译。

如果 symbol 没有被定义,那么 #if defined(symbol) 就被视为假(0),相应的代码块会被忽略。

#if !defined(symbol)#ifndef symbol(#if !defined(symbol) 的简写)

这个指令用于检查是否没有定义名为 symbol 的宏。

如果 symbol 没有被定义,即条件为真,那么 #if !defined(symbol) 就被视为真(非零),相应的代码块会被编译。

如果 symbol 已经被定义,即条件为假,那么 #if !defined(symbol) 就被视为假(0),相应的代码块会被忽略。

示例:

define WIN 0//无论是0还是1,都是被定义

int main()
{
#if defined(WIN)
	printf("windows\n");
#endif


	return 0;
}

输出结果:

因为WIN被定义,所以运行时,printf("windows\n"); 参与编译:

我们还可以写成这种形式,效果与上面的代码一样。

#define WIN 1

int main()
{
#ifdef WIN
	printf("windows\n");
#endif

	return 0;
}

因为WIN被定义,所以运行时,printf("windows\n"); 参与编译: 

(4)嵌套指令

我们可以将#if defined&#ifdef 和#if嵌套使用: 

#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
  1. 首先,代码检查是否定义了宏 OS_UNIX,如果定义了,表示代码正在运行在类Unix操作系统上。如果没有定义,表示不是Unix系统。

  2. 如果 OS_UNIX 宏被定义,那么代码会进入第一个条件分支,执行与Unix系统相关的代码。

    如果宏 OPTION1 也被定义,就会调用 unix_version_option1() 函数。                                 如果宏 OPTION2 也被定义,就会调用 unix_version_option2() 函数。
  3. 如果 OS_UNIX 宏没有被定义,表示不是Unix系统,那么代码会检查是否定义了宏 OS_MSDOS,如果定义了,表示代码正在运行在MS-DOS操作系统上。如果没有定义,表示不是MS-DOS系统。

  4. 如果 OS_MSDOS 宏被定义,那么代码会进入第二个条件分支,执行与MS-DOS系统相关的代码。

    如果宏 OPTION2 也被定义,就会调用 msdos_version_option2() 函数。

 6、文件包含

头文件被包含的方式:

本地文件包:

#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。

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

/usr/include

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

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

库文件包含 :

#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

嵌套文件包含:

如果出现这样的场景:

  • comm.hcomm.c是公共模块。
  • test1.htest1.c使用了公共模块。
  • test2.htest2.c使用了公共模块。
  • test.htest.c使用了test1模块和test2模块。
这样最终程序中就会出现两份 comm.h 的内容。这样就造成了文件内容的重复。
如何解决这个问题?
答案:条件编译。
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__
或者:
#pragma once
就可以避免头文件的重复引入。

 

小结

希望这篇文章可以帮助你学习和复习预处理与条件编译相关知识,切记!!一定要动手操作!!

代码的理解从敲代码开始哦。只有自己实践过,知识才是属于你的!!!

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

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

相关文章

用 Pytorch 自己构建一个Transformer

一、说明 用pytorch自己构建一个transformer并不是难事,本篇使用pytorch随机生成五千个32位数的词向量做为源语言词表,再生成五千个32位数的词向量做为目标语言词表,让它们模拟翻译过程,transformer全部用pytorch实现,具备一定实战意义。 二、论文和概要 …

mac连接easyconnnect显示“本地环境出现异常”

mac连接easyconnnect显示“本地环境出现异常” 解决方法&#xff1a; 终端下输入&#xff1a;vim ~/.zprofile文件内加入如下内容&#xff0c;如下图&#xff1a; ####解决连接easyconnnect显示“本地环境出现异常问题 function EC_start(){/Applications/EasyConnect.app/Co…

学信息系统项目管理师第4版系列19_质量管理

1. 公差 1.1. 质量测量中公差是测量指标的可允许变动范围&#xff0c;而不是实际测量值与预期值的差 1.1.1. 【高22下选35】 1.2. 结果的的可接受范围 2. 控制界限 2.1. 统计意义上稳定的过程或过程绩效的普通偏差的边界 3. 3版 3.1. 质量控制新七工具 3.1.1. 【高19下…

cpp primer笔记070-算法函数

accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型&#xff0c;如果返回值是自定义类型&#xff0c;需要使用accumlate&#xff0c;则需要重载运算符&#xff0c;该接口的第三个参数返回的是一个需要处理的数据类型的一个变量。 std::vector<std…

蓝桥等考Python组别十四级001

第一部分&#xff1a;选择题 1、Python L14 &#xff08;15分&#xff09; 运行下面程序&#xff0c;输出的结果是&#xff08; &#xff09;。 d {A: 501, B: 602, C: 703, D: 804} print(d[B]) 501602703804 正确答案&#xff1a;B 2、Python L14 &#xff08;15分…

吃鸡高手必备工具大揭秘!提高战斗力,分享干货,一站满足!

大家好&#xff01;你是否想提高吃鸡游戏的战斗力&#xff0c;分享顶级的游戏作战干货&#xff0c;方便进行吃鸡作图和查询装备皮肤库存&#xff1f;是否也担心被骗&#xff0c;希望查询游戏账号是否在黑名单上&#xff0c;或者查询失信人和VAC封禁情况&#xff1f;在这段视频中…

System Generator学习——使用 AXI 接口和 IP 集成器

文章目录 前言一、目标二、步骤1、检查 AXI 接口2、使用 System Generator IP 创建一个 Vivado 项目3、创建 IP 集成设计&#xff08;IPI&#xff09;4、实现设计 总结 前言 在本节中&#xff0c;将学习如何使用 System Generator 实现 AXI 接口。将以 IP 目录格式保存设计&am…

「专题速递」回声消除算法、低功耗音频、座舱音频系统、智能音频技术、低延时音效算法、手机外放增强算法...

随着多媒体和通信网络技术的持续升级&#xff0c;以及新型音视频应用场景的不断涌现&#xff0c;音频处理技术正朝着更加智能化和沉浸化的方向迅猛发展。人们对音频听觉体验的要求也逐渐提高&#xff0c;无论是在何种场景下&#xff0c;都期望获得更加清晰的声音&#xff0c;并…

吃鸡高手必备!这些技巧帮你提高战斗力!

大家好&#xff01;作为一名吃鸡玩家&#xff0c;我们都想提高自己的战斗力&#xff0c;享受顶级游戏作战干货&#xff0c;装备皮肤库存展示和查询&#xff0c;并避免被骗游戏账号。在这里&#xff0c;我将为大家介绍一些实用的技巧和工具&#xff0c;让你成为吃鸡高手&#xf…

三相逆变器下垂控制双机

下垂控制的原理推荐看这篇知乎&#xff08;形象又生动&#xff09;&#xff1a;https://www.zhihu.com/question/41003509/answer/518837491 主拓扑图 控制主要模块 Droop子模块 监控有功结果 1、从两台逆变器输出的有功功率波形可以看到&#xff0c;在负载突变的时候&#xf…

卷积网络的发展历史-AlexNet

简介 2012 年&#xff0c;Krizhevsky 与 Hinton 推出了 AlexNet&#xff0c;引起了许多学者对深度学习的研究&#xff0c;可以算是深度学习的热潮的起始标志。在图像分类领域不得不提的就是ImageNet大规模视觉挑战赛(ILSVRC)&#xff0c;它被称为深度学习在图像分类任务研究方…

《Spring框架原理》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

杂记 define,typedef,static,memset,ifndef,递归,逻辑与,整型提升,算术转换

目录 常量&#xff0c;define typedef static ​编辑​编辑 #define定义常量和宏 指针 ​编辑 操作系统&#xff0c;网络 system执行系统命令 memset ifndef 递归 冒泡排序 单目操作符 逻辑与&& 隐式类型转换 整型提升 算术转换 有符号无符号所占的…

网络架构中PHY芯片可否不使用网络变压器/网络隔离变压器连接呢?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;常有人询问网络架构中&#xff0c;PHY芯片通常使用的网络变压器&#xff08;也叫网络隔离变压器&#xff09;可否节省不用&#xff0c;以降低成本&#xff0c;今天就相关问题做个探讨&#xff1b; 一 &#xff0c;P…

吃鸡达人必备!提高战斗力、分享干货、查询安全!

大家好&#xff01;作为吃鸡玩家&#xff0c;想必大家都希望能够提高自己的游戏战斗力&#xff0c;分享顶级游戏作战干货&#xff0c;并且方便进行作图、查询装备皮肤库存&#xff0c;更重要的是&#xff0c;防止被骗游戏账号进入黑名单。今天&#xff0c;我就给大家介绍一家专…

vue.js 生命周期

在页面首次加载执行顺序有如下&#xff1a; beforeCreate //在实例初始化之后、创建之前执行created //实例创建后执行beforeMounted //在挂载开始之前调用filters //挂载前加载过滤器computed //计算属性directives-bind //只调用一次&#xff0c;在指令第一次绑定到元素时调…

MySQL语句大总结

基础语法数据库约束复杂语法1&#xff1a;聚合查询&#xff08;所谓聚合计算聚合函数的结果&#xff09;2&#xff1a;联合查询什么是内连接&#xff1b;什么是外连接&#xff1f;3&#xff1a;子查询&#xff08;套娃,慎用&#xff09;4&#xff1a;合并查询 基础语法 建库 c…

如何使用 Hotshot 通过文字生成 GIF 动画

Hotshot 是一个基于人工智能的工具&#xff0c;可用于通过文字生成 GIF 动画。该工具使用最新的图像生成技术来创建逼真的动画&#xff0c;即使是复杂的文字描述也能做到。 hotshot访问地址 使用 Hotshot 生成 GIF 动画 要使用 Hotshot 生成 GIF 动画&#xff0c;您需要首先…

吃鸡高手亲授:玩转绝地求生,分享顶级游戏干货!

绝地求生&#xff08;PUBG&#xff09;自上线以来&#xff0c;成为了全球热门游戏。作为吃鸡行家&#xff0c;我将分享一些独家技巧和干货&#xff0c;帮助您提高游戏战斗力&#xff0c;享受顶级游戏作战体验&#xff01; 首先&#xff0c;让我们谈一谈战斗力升级。想要在吃鸡游…

卷积网络的发展历史-LeNet

简介 LeNet是CNN结构的开山鼻祖&#xff0c;第一次定义了卷积神经网络的结构。 LeNet模型包含了多个卷积层和池化层&#xff0c;以及最后的全连接层用于分类。其中&#xff0c;每个卷积层都包含了一个卷积操作和一个非线性激活函数&#xff0c;用于提取输入图像的特征。池化层…