【C语言】预处理器

news2024/11/27 14:33:55

目录

1. 预处理器的工作原理

2. 预处理指令

3. 宏定义

3.1 简单的宏(对象式宏)

3.2 带参数的宏(函数式宏)

3.3 #define替换规则

3.4 #和##

3.5 带副作用的宏参数

3.6 宏和函数对比

3.7 命名约定

3.8 #undef

3.9 预定义宏

4. 文件包含

5. 条件编译

5.1 简单的条件编译

5.2 多个分支的条件编译

5.3 判断是否被定义

5.4 嵌套指令

6. 其他预处理指令

6.1 #error指令

6.2 #line指令

6.3 #pragma指令


1. 预处理器的工作原理

预处理器的行为是由预处理指令(由#字符开头的一些命令)控制的。

预处理器的输入是一个C语言程序,程序可能包含指令。预处理器会执行这些指令,并在处理过程中删除这些指令。预处理器的输出是另一个C程序:原程序编辑后的版本,不再包含指令。预处理器的输出被直接交给编译器,编译器检查程序是否有错误,并将程序翻译为目标代码(机器指令)。

2. 预处理指令

  • 宏定义:#define指令定义一个宏,#undef指令删除一个宏定义
  • 文件包含:#include指令导致一个指定文件的内容被包含到程序中
  • 条件编译:#if、#ifdef、#ifndef、#elif、#else和#endif指令可以根据预处理器可以测试的条件来确定是将一段文本块包含到程序中还是将其排除在程序之外
  • 其他预处理指令:#error、#line、#pragma

适用于所有指令的规则:

  • 指令都以#开始:#符号不需要在一行的行首,只要它之前只有空白字符就行。在#后是指令名,接着是指令所需要的其他信息。
  • 在指令的符号之间可以插入任意数量的空格或水平制表符。
  • 指令总是在第一个换行符处结束,除非明确地指明要延续。如果想在下一行延续指令,我们必须在当前行的末尾使用'\'字符。
  • 指令可以出现在程序中的任何地方。但我们通常将#define和#include指令放在文件的开始,其他指令则放在后面,甚至可以放在函数定义的中间。
  • 注释可以与指令放在同一行。实际上,在宏定义的后面加一个注释来解释宏的含义是一种比较好的习惯。

3. 宏定义

3.1 简单的宏(对象式宏)

#define 标识符 替换列表

宏的替换列表可以包括标识符、关键字、数值常量、字符常量、字符串字面量、操作符和排列。当预处理器遇到一个宏定义时,会做一个“标识符”代表“替换列表”的记录。在文件后面的内容中,不管标识符在哪里出现,预处理器都会用替换列表代替它。

#include <stdio.h>
 
#define MAX 100
#define STR "abcdef"
 
int main()
{
	printf("%d\n", MAX);//100
	int a = MAX;
	printf("%d\n", a);//100
	printf("%s\n", STR);//abcdef
	return 0;
}
#define INT_PTR int*
INT_PTR a, b;//a是int*类型,b是int类型

#define是宏定义,仅仅是直接替换。INT_PTR a, b; 进行宏替换后代码为:

int* a, b;

不是a和b都是int*类型的意思,而是a是int*类型,b是int类型。可以看成是如下代码:

int *a, b;

3.2 带参数的宏(函数式宏)

#define 标识符(x1,x2,…,xn) 替换列表

其中x1,x2,…,xn是宏的参数,参数列表可以为空。宏名和左括号之间不能有空格,如果有空格,预处理器会认为是在定义一个简单的宏,其中(x1,x2,…,xn)是替换列表的一部分。

带参数的宏经常用来作为简单的函数使用。

#define ADD(x,y)  ((x)+(y))
//ADD:宏名
//x y:宏的参数,参数是无类型
//((x)+(y)):宏体
#define ADD2(x,y)  (x)+(y)//err

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n", ADD(a, b));//((a)+(b))=10+20=30
	printf("%d\n", 10 * ADD2(a, b));//10*(a)+(b)=10*10+20=120
	return 0;
}

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

带参数的宏不仅适用于模拟函数调用,还经常用作需要重复书写的代码段模式。

如果不想写printf("%d\n", i);可以定义宏。

#define PRINT_INT(n) printf("%d\n", n)
PRINT_INT(i / j);

进行宏替换后代码为:

printf("%d\n", i / j);

3.3 #define替换规则

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

注意:

  1. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

3.4 #和##

#:把一个宏参数变成对应的字符串。

例如,#val会被与处理器处理为:"val"

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

#include <stdio.h>

int main()
{
	int a = 10;
	PRINT(a, "%d");
	//printf("the value of a is %d\n", a);

	int b = 20;
	PRINT(b, "%d");
	//printf("the value of b is %d\n", b);

	float f = 3.5f;
	PRINT(f, "%f");
	//printf("the value of f is %f\n", f);

	return 0;
}

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

#define RESULT(A,B)  A##B

#include <stdio.h>

int main()
{
	int value_a = 10;
	printf("%d\n", RESULT(value, _a));//10
	return 0;
}

3.5 带副作用的宏参数

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

int x = 1;
int y = x+1;//y=2 x=1 不带副作用
int y = ++x;//y=2 x=2 带有副作用(x自身也改变了)
#define MAX(x,y) ((x)>(y)?(x):(y))

#include <stdio.h>

int main()
{
	int a = 3;
	int b = 4;
	int m = MAX(++a, ++b);
	printf("m=%d a=%d b=%d\n", m, a, b);//m=6 a=4 b=6
	return 0;
}

如何计算((++a)>(++b)?(++a):(++b))?

  1. ++a:a先自增,a=4,然后++a本身=4
  2. ++b:b先自增,b=5,然后++b本身=5
  3. 4>5为假,不执行++a,直接执行++b
  4. ++b:b先自增,b=6,然后++b本身=6
  5. ((++a)>(++b)?(++a):(++b))的结果为6

3.6 宏和函数对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

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

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

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

宏的缺点:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏不能调试。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

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

#define MALLOC(num, type) (type*)malloc(num * sizeof(type))

//使用
MALLOC(10, int);//类型作为参数

//预处理器替换之后:
(int*)malloc(10 * sizeof(int));

宏和函数的一个对比:

属性函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些

操作符

优先级

宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

3.7 命名约定

一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者。习惯是把宏名全部大写,函数名不要全部大写。

3.8 #undef

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

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

3.9 预定义宏

C语言有一些预定义宏,每个宏表示一个整数常量或字符串字面量,这些宏提供了当前编译或编译器本身的信息。

名字描述
__LINE__被编译的文件中的行号
__FILE__被编译的文件名
__DATE__编译的日期(格式"mm dd yyyy")
__TIME__编译的时间(格式"hh:mm:ss")
__STDC__如果编译器符合C标准(C89或C99),那么值为1
#include <stdio.h>

int main()
{
	printf("编译的日期:%s,时间:%s\n", __DATE__, __TIME__);
	return 0;
}

4. 文件包含

#include指令告诉预处理器打开指定的文件,并且把此文件的内容插入到当前文件中。因此,如果想让几个源文件可以访问相同的信息,可以把此信息放入一个文件中,然后利用#include指令把该文件的内容带进每个源文件中。把按照此种方式包含的文件称为头文件(有时称为包含文件),头文件的扩展名为.h。

#include指令主要有两种书写格式。

第一种格式用于属于C语言自身库的头文件:

#include <文件名>

第二种格式用于所有其他头文件,也包含任何自己编写的文件:

#include "文件名"

这两种格式间的细微差异在于编译器定位头文件的方式。大多数编译器遵循的规则:

  • #include <文件名>:搜寻系统头文件所在的目录(或多个目录)
  • #include "文件名":先搜寻当前目录,然后搜寻系统头文件所在的目录(或多个目录)

5. 条件编译

C语言的预处理器可以识别大量用于支持条件编译的指令。条件编译是指根据预处理器所执行的测试结果来包含或排除程序的片断。

5.1 简单的条件编译

#if 常量表达式
    //语句
#endif

如果常量表达式为真,执行语句;如果常量表达式为假,不执行语句。

5.2 多个分支的条件编译

#if 常量表达式1
    //语句1
#elif 常量表达式2
    //语句2
#else
    //语句3
#endif

如果表达式1为真,不管表达式2的真假,执行语句1;

如果表达式1为假,且表达式2为真,执行语句2;

如果表达式1和2都为假,执行语句3。

5.3 判断是否被定义

#if defined(symbol)//等价于#ifdef symbol
    //语句1
#endif

#if !defined(symbol)//等价于#ifndef symbol
    //语句2
#endif

如果定义了symbol,执行语句1;如果没定义symbol,执行语句2。

5.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

6. 其他预处理指令

6.1 #error指令

#error 消息

其中,消息是任意的记号序列。如果预处理器遇到#error指令,它会显示一条包含消息的出错消息。对于不同的编译器,出错消息的具体形式也可能会不一样。格式可能类似:

Error directive: 消息

或者

#error 消息

遇到#error指令预示着程序中出现了严重的错误,有些编译器会立即终止编译而不再检查其他错误。

#error指令通常与条件编译指令一起用于检测正常编译过程中不应出现的情况。例如,假定我们需要确保一个程序无法在一台int类型不能存储100000的机器上编译。最大允许的int值用INT_MAX宏表示,所以我们需要做的就是当INT_MAX宏小于100000时调用#error指令:

#if INT_MAX < 100000
#error int type is too small
#endif

如果试图在一台以16位存储整数的机器上编译这个程序,将产生一条出错消息:

Error directive: int type is too small

#error指令通常会出现在#if—#elif—#else序列中的#else部分:

#if defined(WIN32)
...
#elif defined(MAC__OS)
...
#elif defined(LINUX)
...
#else
#error No operating system specified
#endif

6.2 #line指令

#line指令是用来改变程序行编号方式的(程序行通常是按1,2,3,…来编号的)。我们也可以使用这条指令使编译器认为它正在从一个有不同名字的文件中读取程序。

#line指令有两种形式。一种形式只指定行号:

#line n

n必须是介于1和32767(C99中是2147483647)之间的整数。这条指令导致程序中后续的行被编号为n、n+1、n+2等。

#line指令的第二种形式同时指定行号和文件名:

#line n "文件"

指令后面的行会被认为来自文件,行号由n开始。n和文件字符串的值可以用宏指定。

#line指令的一种作用是改变__LINE__宏(可能还有__FILE__)的值。更重要的是,大多数编译器会使用来自#line指令的信息生成出错消息。例如,假设下列指令出现在文件foo.c的开头:

#line 10 "bar.c"

现在,假设编译器在foo.c的第5行发现一个错误。出错消息会指向bar.c的第13行,而不是foo.c的第5行。为什么是第13行呢?因为指令占据了foo.c的第1行,因此对foo.c的重新编号从第2行开始,并将这一行作为bar.c的第10行。

乍一看,#line指令使人迷惑。为什么要使出错消息指向另一行,甚至是另一个文件呢?这样不是会使程序变得难以调试吗?

实际上,程序员并不经常使用#line指令。它主要用于那些产生C代码作为输出的程序。最著名的程序之一是yacc(Yet Another Compiler-Compiler),它是一个用于自动生成编译器的一部分的UNIX工具(yacc的GNU版本称为bison)。在使用yacc之前,程序员需要准备一个包含yacc所需要的信息以及C代码段的文件。通过这个文件,yacc生成一个C程序y.tab.c,并合并程序员提供的代码。程序员接着按照正常方法编译y.tab.c。通过在y.tab.c中插入#line指令, yacc会使编译器认为代码来自原始文件——也就是程序员写的那个文件。于是,任何编译y.tab.c时产生的出错消息会指向原始文件中的行,而不是y.tab.c中的行。其最终结果是:调试变得更容易,因为出错消息都指向程序员编写的文件,而不是由yacc生成的(那个更复杂的)文件。

6.3 #pragma指令

#pragma指令为要求编译器执行某些特殊操作提供了一种方法。这条指令对非常大的程序或需要使用特定编译器的特殊功能的程序非常有用。

#pragma 记号

其中,记号是任意记号。#pragma指令可以很简单(只跟着一个记号),也可以很复杂:

#pragma data(heap_size => 1000, stack_size => 2000)

#pragma指令中出现的命令集在不同的编译器上是不一样的。你必须通过查阅你所使用的编译器的文档来了解可以使用哪些命令,以及这些命令的功能。顺便提一下,如果#pragma指令包含了无法识别的命令,预处理器必须忽略这些#pragma指令,不允许给出出错消息。

C89中没有标准的编译提示(pragma),它们都是在实现中定义的。C99有3个标准的编译提示,都使用STDC作为#pragma之后的第一个记号。这些编译提示是FP_CONTRACT 、CX_LIMITED_RANGE 和FENV_ACCESS。

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

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

相关文章

支持向量机SVM详细原理,Libsvm工具箱详解,svm参数说明,svm应用实例,神经网络1000案例之15

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 SVM应用实例&#xff0c;基于SVM的股票价格预测 支持向量机SVM的详细原理 SVM的定义 支持向量机&#xff08;support vector machines, SVM&#xff09;是一种二分类模型&a…

Scala面向对象详解(第六章:Scala包、类和对象、封装、继承和多态、抽象、单例、特质)(尚硅谷笔记)

面向对象第 6 章 面向对象6.1 Scala 包6.1.1 包的命名6.1.2 包说明&#xff08;包语句&#xff09;6.1.3 包对象6.1.4 导包说明6.2 类和对象6.2.1 定义类6.2.2 属性6.3 封装6.1.5 访问权限6.2.3 方法6.2.4 创建对象6.2.5 构造器6.2.6 构造器参数6.4 继承和多态6.5 抽象类6.5.1 …

基于机器学习的二手车价格预测及应用实现(预测系统实现)

1.摘要 随着中国汽车工业的迅速发展&#xff0c;国内的汽车数量也在迅速增长。新车销售市场已经逐渐饱和&#xff0c;而二手车交易市场正在兴起。但是&#xff0c;由于中国的二手车市场尚未成熟&#xff0c;与发达国家相比仍存在较大差距。其中一个重要原因是二手车的市场价格难…

信息系统项目管理师试题精选(四)

【1】关于区块链的描述&#xff0c;不正确的是&#xff08; &#xff09;。A. 区块链的共识机制可有效防止记账节点信息被篡改B. 区块链可在不可信的网络进行可信的信息交换C. 存储在区块链的交易信息是高度加密D. 区块链是一个分布式共享账本和数据库【2】&#xff08; &#…

记录一次Android视频播放音画不同步问题的定位及分析

1.何为音画不同步 音画不同步很简单就是视频播放过程中声音和画面出现的时间点不一致&#xff0c;滞后或者提前。 2.音画不同步问题分析思路 2.1.音画不同步问题的证明 对于滞后或者提前很多的音画不同步可以直接认为发生了该问题&#xff0c;但是滞后或者提前不是很多的就…

Linux系统安装MySQL8.0版本详细教程【亲测有效】

首先官网下载安装包&#xff1a;https://downloads.mysql.com/archives/community/ 一、上传到安装服务器 二、解压 tar -xvf mysql-8.0.31-linux-glibc2.12-x86_64.tar.xz三、移动位置并重新命名 mv mysql-8.0.31-linux-glibc2.12-x86_64 /usr/local/mysql四、创建mysql用户…

信息安全基础概要(二)——安全保护等级,安全服务与安全机制

目录 一、OSI/RM七层模型 二、各个网络层次的安全保障 三、计算机信息系统安全保护等级划分准则(GB17859-1999) 四、信息安全体系结构——安全服务与安全机制 前篇&#xff1a; https://blog.csdn.net/superSmart_Dong/article/details/125690697 一、OSI/RM七层模型 广播…

每日一题——L1-070 吃火锅(15)

L1-070 吃火锅 分数 15 以上图片来自微信朋友圈&#xff1a;这种天气你有什么破事打电话给我基本没用。但是如果你说“吃火锅”&#xff0c;那就厉害了&#xff0c;我们的故事就开始了。 本题要求你实现一个程序&#xff0c;自动检查你朋友给你发来的信息里有没有 chi1 huo…

字节是真的难进,测开4面终上岸,压抑5个月,终于可以放声呐喊

这次字节的面试&#xff0c;给我的感触很深&#xff0c;意识到基础的重要性。一共经历了五轮面试&#xff1a;技术4面&#xff0b;HR面。 下面看正文 本人自动专业毕业&#xff0c;压抑了五个多月&#xff0c;终于鼓起勇气&#xff0c;去字节面试&#xff0c;下面是我的面试过…

Mysql 索引(二)—— InnoDB 与 MyISAM 索引方式的比较(聚簇索引 VS 非聚簇索引)

在上一部分了解到&#xff0c;主键索引的本质其实就是一棵B树&#xff0c;通过每一层的目录页来找到记录所在的page页。根据 page页是否保存了数据&#xff0c;我们可以将主键索引分为 聚簇索引 和 非聚簇索引。 1、MyISAM (1) 非聚簇索引 非聚簇索引的目录和数据记录是分开存…

NPE:记一次脑残NPE的排查过程

目录 碎碎念&#xff1a; 如下这行报NPE&#xff1a; 排查过程&#xff1a; 解解方案&#xff1a; 小结&#xff1a; 空指针出现的几种情况&#xff1a; 如何从根源避免空指针&#xff1a; 赋值时自动拆箱出现空指针&#xff1a; 1、变量赋值自动拆箱出现的空指针 2、…

接了ChatGPT的NewBing如何评价CodeGeeX

一篇《如何用 CodeGeeX 替代 GitHub Copilot》的文章在开发者社区登上热榜&#xff0c;开发者关注的AI生成代码工具CodeGeeX&#xff0c;这款插件产品目前支持在VSCode市场和Jetbrains IDEs下载使用&#xff0c;是国产对标Copilot目前安装量最大的开发者工具。 之所以引发开发…

JS 中 for in 和 for of 的区别

for in 和 for of 是js中常用的遍历方法&#xff1b;两者的区别如下&#xff1a; 文章目录一&#xff0c;遍历数组二&#xff0c;遍历对象三&#xff0c;总结一&#xff0c;遍历数组 1&#xff0c;for in 是ES5的语法标准&#xff0c;而for of则是ES6语法标准。 const arr1 …

CD19药物|适应症|市场销售-上市药品前景分析

CD19是免疫球蛋白Ig超家族的I型跨膜糖蛋白&#xff0c;仅在B细胞中表达。CD19在b细胞发育的多个阶段通过调节b细胞受体信号通路参与b细胞的命运和分化。CD19在B细胞上普遍表达&#xff0c;如在B细胞恶性肿瘤中的表达&#xff0c;涵盖了B细胞淋巴瘤的所有亚型&#xff0c;从惰性…

IP地理位置定位技术原理是什么

IP地理位置定位技术的原理是基于IP地址的网络通信原理和基础上的。它利用IP地址所包含的一些信息&#xff0c;如网络前缀和地址段&#xff0c;以及ISP的IP地址归属地数据库&#xff0c;来推测IP地址所对应的地理位置。具体来说&#xff0c;IP地址是由32位二进制数字组成的&…

AndroidStudio Push第一次代码到 Git

1、首先需要在远程创建一个空仓库&#xff0c;我使用 GiteeAS创建新项目&#xff0c;在项目文件夹根目录下命令行 git init 或者在AS 的工具栏找到VCS -> Enable Version Control Integration之后工具栏就出现 git 的操作图标了push 本地代码到远程如果直接 git push 就会出…

MeterSphere 如何连接Mongodb数据库

MeterSphere 如何连接Mongodb数据库 前言&#xff1a;最近有在使用Metersphere的时候遇到了需要连接Mongodb的需求&#xff0c;而Metersphere只支持以下四种通过数据库驱动连接&#xff1a;mysql、sql server、oracle、pgsql&#xff0c;现在给大家分享一种通过前置脚本连接mo…

数学(二)-- LeetCode[204] 计数质数

1 计数质数 1.1 题目描述 给定整数 n &#xff0c;返回 所有小于非负整数 n 的质数的数量 。 示例 1&#xff1a; 输入&#xff1a;n 10 输出&#xff1a;4 解释&#xff1a;小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 示例 2&#xff1a; 输入&#xff1a;n 0 输出&…

Redis学习之持久化(六)

这里写目录标题一、持久化简介1.1 持久化1.2 Redis持久化的两种形式二、RDB2.1 RDB概念2.2 save指令手动执行一次保存配置相关参数2.3 bgsave指令2.4 save配置自动执行2.5 RDB三种启动方式对比三、AOF3.1 AOF概念3.2 AOF执行策略3.3 AOF重写四、RDB和AOF区别2.1 RDB与AOF对比&a…

LQB04 蜂鸣器和继电器的操作。和代码

硬件图 编程实现 图中 &#xff0c;用Y5C控制ULN2003芯片。 所以要选通Y5C ; ULN2003芯片是个反相放大&#xff0c;IN口是1&#xff0c;OUT口出来是0&#xff1b;IN口是0&#xff0c;出来是1&#xff1b; 蜂鸣器和继电器&#xff0c;都是0点亮&#xff0c;发声&#xff1b;那…