#define定义标识符详解

news2024/12/23 14:05:00

0.预定义符号

在讲解#define之前先给大家介绍几个预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C(标准C),其值为1,否则未定义

这些预定义符号都是编译器自带的,可以直接使用,我们可以用printf函数去打印。

#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__);
     return 0;
 }

1. 语法分析

                                #define     name     stuff

name代表的是stuff新的名字

stuff代表的是实现的内容

name的名字最好要全大写!!!!!

就比如你总会用到一个数字,但是你还需要将它换值,如果我们不用#define来,我们就需要每次都写这个值,改值的时候也要一次次去修改,这给我们带来了很多的麻烦,所以这个时候我们可以使用#define来定义这个数值,给他取个名字,代表这个数值,改值的时候就可以直接在#define这里修改,改一次就行,大大提高了写代码的效率。

记住这一点,#define可以将name替换成stuff。一定是替换,将name换成stuff

比如:

代码1
#include "stdio.h"
 int main()
 {
     int a = 10;
     int b = 20;
     int c = 30;
     if(a < 25)
         printf("aYES\n");
     if(b < 25)
         printf("bYES\n");
     if(c < 25)
         printf("cYES\n");
     return 0;
 }

代码2
#define n 25
 int main()
 {
     int a = 10;
     int b = 20;
     int c = 30;
     if(a < n)
         printf("aYES\n");
     if(b < n)
         printf("bYES\n");
     if(c < n)
         printf("cYES\n");
     return 0;
 }

代码1如果修改25需要修改3次,但是代码2通过#define定义后,只需要修改n后面的值就行,这就大大提高了修改的效率。

1.1 举例分析

1.1.1 代码1

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

我们来看第一个,我们用#define定义了MAX的数值是100,所以在主函数使用的时候

int a = MAX;就相当于 int a = 100; 也就是把MAX替换成了100

1.1.2 代码2


#include "stdio.h"
#define sz sizeof
 int main()
 {
     int a = sz(int);
     int b = sizeof (int);
     printf("%d %d",a,b);
     return 0;
 }

第二个代码,是用#define来定义一个sz,代表sizeof关键字,所以在使用过程中,sz(int)就可以替换成sizeof(int)

1.1.3 代码3

代码1
#include "stdio.h"
#define do_forever for(;;)
 int main()
 {
     do_forever
     {
         printf("1");
     }
     return 0;
 }
代码2

#include "stdio.h"
 int main()
 {
     for(;;)
     {
         printf("1");
     }
     return 0;
 }

这两个代码是等效的,代码1我们用#define定义了一个死循环,当编译的时候do_forever就会被替换成for(;;)

所以#define可以定义任何东西,但是在使用时候会被自动替换成定义后面的东西

1.1.4代码4

代码1
#include "stdio.h"
#define PRINT printf("file:%s\nline:%d\ndate:%s\ntime:%s\n",__FILE__,__LINE__,__DATE__,__TIME__)
 int main()
 {
     PRINT;
     return 0;
 }
代码2
#include "stdio.h"
#define PRINT printf("file:%s\n\
                     line:%d\n\
                     date:%s\n\
                     time:%s\n",__FILE__,__LINE__,__DATE__,__TIME__)
 int main()
 {
     PRINT;
     return 0;
 }
代码3
#include "stdio.h"
 int main()
 {
     printf("file:%s\nline:%d\ndate:%s\ntime:%s\n",__FILE__,__LINE__,__DATE__,__TIME__);
     return 0;
 }

首先看代码1,我们是#define定义了一个PRINT,它是后面的一大串打印内容的,大家是不是也会觉得很长。

所以我们优化看一下代码2,我们将其回车分成了4行,但是这里要记住,一定要在我们敲回车的地方➕一个\ ,告诉编译器这还是一个整体的内容,也就是将我们敲的回车转义了。如果不加这个\ ,整体代码就会出问题。

1.2 #define末尾是否应该添加' ; '

答案是最好不要加分号,那又是为什么呢?

我们知道#define替换,比如上面我们是将MAX直接替换成了100sz直接替换成了sizeofdo_forever直接替换成了for(;;)等等。

那我们如果加上了分号' ; '之后呢?

代码1
#define MAX 100;
 int main()
 {
     int a = MAX;
     return 0;
 }
代码2
#define MAX 100;
 int main()
 {
     int a = 100;;
     return 0;
 }
代码3
#define MAX 100;
 int main()
 {
     int a = MAX
     return 0;
 }

首先代码1代码2是等效的,我们可以看到加了分号之后,MAX是被替换成了100;,这里就有2个分号了,肯定是错误的。

那怎么去修改呢?

我们可以像代码3一样,在主函数那里也就是语句结尾,不加分号,等替换之后就有分号了,但是这跟咱们的书写习惯大大不同,所以最好不要加分号!

2. #define定义宏

2.1 语法分析

         #define nameparament-list ) stuff

1.#define定义宏和函数很相似
2.name相当于函数名

3.parament-list 就相当于函数参数
4.stuff就是相当于这个函数的功能

注意⚠️⚠️:这里的左括号一定要跟name紧挨着
                       否则会name就会被替换成parament-list ) stuff

比如:

代码1

#define MAX(x,y) (x) > (y) ? (x) : (y) 
int main()
{
    MAX(2,3);
    MAX(2,3);相当于:(2) > (3) ? (2) : (3);
}

代码2

#define MAX (x,y) (x) > (y) ? (x) : (y)
int main()
{
    MAX(2,3);
    MAX相当于:(x,y) (x) > (y) ? (x) : (y)
}

代码1左括号紧挨着name的,那我们就可以用MAX(x,y)完成我们想要实现的功能。

代码2没有紧挨着的,这时候会出现错误,因为此时是MAX相当于(x,y) (x) > (y) ? (x) : (y)
这时候MAX啥也不是,是个错误。

所以左括号必须跟name紧挨着

总而言之还是要记住#define是替换的作用。

2.2代码举例

2.2.1 代码1

#define Square(x) x * x

我们定义了一个Square(x),它的作用是求一个数的平方,参数是x

Square(5);

所以我们在引用的时候,给括号里数字5

5 * 5

就相当于5*5,可以求出来这个数的平方。

但是如果是这样的代码,结果又会是多少呢?

#include "stdio.h"
#define Square(x) x * x
int main()
{
    int a = 5;
    printf("%d\n" ,Square( a + 1) );
    return 0;
}

正确答案是11
我相信很多人都会得出36,但是我们一直在说#define是替换,所以上面代码又可以变成如下

#include "stdio.h"
#define Square(x) x * x
int main()
{
    int a = 5;
    printf("%d\n" ,a + 1 * a + 1 );
    return 0;
}

对吧,这时候Square(a+1)会被替换成 a+1*a+1,优先乘法运算,结果是11

也就是说我们#define定义的,真的就是替换,你给什么替换什么,不会加括号,所以为了算出正确结果,一定要加括号,在#define宏定义时候,要多加括号

正确代码:

#include "stdio.h"
#define Square(x) (x) * (x)
int main()
{
    int a = 5;
    printf("%d\n" ,Square( a + 1) );
    return 0;
}

2.2.2 代码2

#define DOUBLE(x) (x) + (x)

我们看这个#define定义的DOUBLE(x) 是求一个数的二倍,在这里我们给x加了括号避免了之前的错误,那下面代码输出的是什么呢?

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

我相信大多数人现在肯定是算出正确结果了,因为之前吃的亏
答案是55

和我们预想结果不一样呀,我们预想的是100,为什么不一样呢?

int a = 5;
printf("%d\n" ,10 * 5 + 5);

实际上,DOUBLE(a)是被替换成了5 + 5,但是前面有个10,还是乘法,就变成了10*5+5.那如何解决这个问题呢?还是加括号,代码如下

#define DOUBLE(x) ((x) + (x))

我们只需要整体加个括号就可以完美解决问题啦!

总结:

在#define定义宏的时候也就是类似函数,一定要多加括号,避免错误

3.#define的替换规则

3.1 规则1

  规则1 : 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号

               如果,它们首先被替换

这是什么意思呢?我们拿代码举例:

#include "stdio.h"
#define n 100
#define MAX(x,y) ((x) > (y) ? (x) : (y))
 int main()
 {
     int a = 20;
     int c = MAX(a,n);
     printf("max是%d\n",c);
     return 0;
 }

MAX(x,y)里,主函数传的参数里分别是a,n

a是我们定义的局部变量,而n是#define定义的符号,所以根据规则,要首先被替换。

就变成了这样:

int c = MAX(a,100);

n被替换成了100,之后再去替换#define定义的宏

int c = ((a) > (100) ? (a) : (100));

3.2 规则2

规则2:

再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上

述处理过程。

这一点没什么说的,反正就是你在#define定义的时候一定要看先发生的是谁就行。

4.‘#’和‘##’的作用

4.1‘#’作用

我们先看这样一段代码

char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

此时输出的是

我们可以看到两个输出的都是一样的,所以我们发现字符串它是有自动连接的特点,也就是说我打印完“hello ”这个字符串,但是发现后面还有,那就接着打印。

我们明确了这个之后,思考这样一个问题 ,如何把参数插入到字符串中?

看这样一段代码:

#include "stdio.h"
#define PRINT(n) printf("n的值是%d\n", n)
int main()
{
	int a = 20;
    PRINT(a);
	return 0;
}

我们用PRINT想打印传入的参数的值,但是这么打印得到的结果是

首先第一点我们是要求a的值,我希望打印出来可以告诉我a的值是20,那这个时候‘#’就派上用场了。

#include "stdio.h"
#define PRINT(n) printf(#n"的值是%d\n", n)
int main()
{
	int a = 20;
    PRINT(a);
	return 0;
}

我们首先要知道printf函数是可以打印字符串的,字符串有自动连接的特点
我们想把a打印到屏幕上,就需要在n的前面加上‘#’这个符号,#n会被替换成"a"

#define PRINT(n) printf(#n"的值是%d\n", n)

PRINT(a);
替换成
printf("a""的值是%d\n", a);

这样我们就打印出来想要的结果了

我们解决了名字的问题,但是如果输入个浮点数呢?别的类型呢?又该怎么办呢?

#include "stdio.h"
#define PRINT(n) printf(#n"的值是%d\n", n)
int main()
{
	int a = 20;
    PRINT(a);
    float b = 3.14;
    PRINT(b);
	return 0;
}

在这段代码里肯定是输出不来浮点数b的,因为%d被写死了,我们是否可以把打印的格式传参呢?

#include "stdio.h"
#define PRINT(n,format) printf(#n"的值是"format"\n", n)
int main()
{
	int a = 20;
    PRINT(a,"%d");
    float b = 3.14;
    PRINT(b,"%f");
	return 0;
}

我们是传入的字符串,所以在printf里可以直接写format,因为会被替换成

#define PRINT(n,format) printf(#n"的值是"format"\n", n)

 PRINT(a,"%d");
替换
 printf("a""的值是""%d""\n", n);

 PRINT(b,"%f");
替换
 printf("b""的值是""%f""\n", n);

这样就可以打印出来任何数据类型了。

4.2 ##的作用

 ##可以把位于它两边的符号合成一个符号,这个用途不大,但是很新奇。

比如:

#define Cat(x,y) x##y
 int main()
 {
     int helloworld = 100;
     printf("%d\n", Cat(hel,loworld));
     return 0;
 }

大家可以想一想输出的结果是多少?

我们一步一步来,先替换

Cat(hel,loworld)
替换成
hel##loworld

而##还可以连接左右的符号

hel##loworld
变成
helloworld

我们这个程序输出的就是helloworld的值,100.

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

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

相关文章

服务器感染了Locked勒索病毒后的正确处理步骤,勒索病毒解密

在服务器中感染了Locked勒索病毒后&#xff0c;应该采取以下一系列步骤来应对和清除病毒&#xff1a; 立即断开网络连接&#xff1a;防止病毒进一步传播感染。备份重要数据&#xff1a;在执行任何操作之前&#xff0c;一定要先备份服务器上的重要数据。这样可以防止在处理病毒过…

经典网络解析(四) ResNet | 残差模块,网络结构代码实现全解析

文章目录 1 设计初衷2.网络结构2.1 残差块2.2 中间的卷积网络特征提取块1 两层33卷积层2 先11卷积层&#xff0c;再33卷积层&#xff0c;再33卷积层 2.3 结构总览表格 3 为什么残差模块有效&#xff1f;3.1 前向传播3.2 反向传播3.3 恒等映射3.4 集成模型 4.代码实现 1 设计初衷…

如何学习嵌入式Linux?

今日话题&#xff0c;如何学习嵌入式Linux&#xff1f;嵌入式底层开发是一种重要的技术&#xff0c;它被广泛应用于各种嵌入式系统中。随着科技的不断发展&#xff0c;嵌入式系统已经成为了我们日常生活中不可或缺的一部分。这就使得嵌入式开发的重要性也凸显出来。刚好我这有一…

opencv for unity package在unity中打开相机不需要dll

下载OpenCV for Unity 导入后&#xff0c;里面有很多案例 直接打开就可以运行 打开相机

Linux: errno: EADDRNOTAVAIL; ipv6-bind;Cannot assign requested address

文章目录 解释一种情况tentative 的解释 解释一种情况 #define EADDRNOTAVAIL 99 /* Cannot assign requested address */ 有一种情况是&#xff1a;当IP6的地址处于tentative的时候&#xff0c;就会返回这个错误。下面的是bind的调用的时候会check地址的flag。如果是tentati…

Java学习星球,十月集训,五大赛道(文末送书)

目录 什么是知识星球&#xff1f;我的知识星球能为你提供什么&#xff1f;专属专栏《Java基础教程系列》内容概览&#xff1a;《Java高并发编程实战》、《MySQL 基础教程系列》内容概览&#xff1a;《微服务》、《Redis中间件》、《Dubbo高手之路》、《华为OD机试》内容概览&am…

一款Python认证和授权的利器

迷途小书童 读完需要 7分钟 速读仅需 3 分钟 1 简介 authlib 是一个开源的 Python 库&#xff0c;旨在提供简单而强大的认证和授权解决方案。它支持多种认证和授权协议&#xff0c;如 OAuth、OpenID Connect 和 JWT。authlib 具有灵活的架构和丰富的功能&#xff0c;使开发人员…

lS1028 + 六网口TSN 硬交换+QNX/Linux实时系统解决方案在轨道交通系统的应用

lS1028 六网口TSN 硬交换QNX/Linux实时系统解决方案在轨道交通系统的应用 以下是在轨道交通应用的实物&#xff1a; CPUNXP LS1028A架构双核Cortex-A72主频1.5GHzRAM2GB DDR4ROM8GB eMMCOSUbuntu20.04供电DC 12V工作温度-40℃~ 80℃ 功能数量参数Display Port≤1路支持DP1.3…

UniAccess Agent卸载

异常场景&#xff1a; UniAccess Agent导致系统中的好多设置打不开 例如:ipv4的协议,注册表,host等等 需要进行删除,亲测有效,及多家答案平凑的 借鉴了这位大神及他里面引用的大神的内容 https://blog.csdn.net/weixin_44476410/article/details/121605455 问题描述 这个进…

Android开发MVP架构记录

Android开发MVP架构记录 安卓的MVP&#xff08;Model-View-Presenter&#xff09;架构是一种常见的软件设计模式&#xff0c;用于帮助开发者组织和分离应用程序的不同组成部分。MVP架构的目标是将应用程序的业务逻辑&#xff08;Presenter&#xff09;、用户界面&#xff08;V…

由于找不到msvcr110.dll的5种解决方法

在使用电脑的过程中&#xff0c;我们可能会遇到一些问题&#xff0c;比如打开软件时提示找不到 msvcr110.dll 文件丢失。这通常意味着该文件已被删除或损坏&#xff0c;导致程序无法正常运行。本文将介绍几种解决方案&#xff0c;帮助您解决这个问题。 首先&#xff0c;我们需…

Linxu下c语言实现socket+openssl数据传输加密

文章目录 1. Socket连接建立流程2、SocketSSL的初始化流程3、初始化SSL环境&#xff0c;证书和密钥4、SocketSSL 的c语言实现4.1 编写SSL连接函数4.2 编写加密服务端server.c4.3 编写加密客户端client.c 5、使用tcpdump检验源码获取 在进行网络编程的时候&#xff0c;我们通常使…

分布式算法相关,使用Redis落地解决1-2亿条数据缓存

面试题&#xff1a;1~2亿数据需要缓存&#xff0c;请问如何设计个存储案例 回答&#xff1a;单机单台100%不可能&#xff0c;肯定是分布式存储&#xff0c;用redis如何落地&#xff1f; 一般业界有三种解决方案&#xff1a; 哈希取余分区 2亿条记录就是2亿个k&#xff0c;v&…

Linux学习-HIS部署(3)

Jenkins插件资源下载 Jenkins部署 Jenkins部署 #Jenkins主机安装OpenJDK环境 [rootJenkins ~]# yum clean all; yum repolist -v ... Total packages: 8,265 [rootJenkins ~]# yum -y install java-11-openjdk-devel.x86_64 #安装OpenJDK11 [rootJenkins ~]# ln -s /usr/l…

Css 美化滚动条

/*设置滚动条宽度为 6px*/ ::-webkit-scrollbar {width: 6px; } /*设置背景颜色&#xff0c;并设置边框倒角&#xff0c;设置滚动动画&#xff0c;0.2 */ ::-webkit-scrollbar-thumb {background-color: #0003;border-radius: 10px;transition: all .2s ease-in-out; } /*设置滚…

探索创意的新辅助,AI与作家的完美合作

在现代社会&#xff0c;文学创作一直是人类精神活动中的重要一环。从古典文学到现代小说&#xff0c;从诗歌到戏剧&#xff0c;作家们以他们的独特视角和文学天赋为我们展示了丰富多彩的人生世界。而近年来&#xff0c;人工智能技术的快速发展已经渗透到各行各业&#xff0c;文…

Leetcode191. 位1的个数

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 1 的个数&#xff08;也被称为汉明重量&#xff09;。 思路&…

人工智能AI 全栈体系(六)

第一章 神经网络是如何实现的 这些年神经网络的发展越来越复杂&#xff0c;应用领域越来越广&#xff0c;性能也越来越好&#xff0c;但是训练方法还是依靠 BP 算法。也有一些对 BP 算法的改进算法&#xff0c;但是大体思路基本是一样的&#xff0c;只是对 BP 算法个别地方的一…

mapper文件添加@Mapper注解爆红

如图所示 报错原因&#xff1a;缺少相关的依赖 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version> </dependency> 添加之后并刷新依赖…

C++: stack 与 queue

目录 1.stack与queue stack queue 2.priority_queue 2.1相关介绍 2.2模拟实现priority_queue --仿函数: --push --pop --top --size --empty --迭代器区间构造 2.3仿函数 3.容器适配器 stack模拟实现 queue模拟实现 学习目标: 1.stack和queue介绍与使用 2.pri…