C语言中的宏函数与宏定义

C语言中的宏函数与宏定义

从开始写C语言到生成执行程序的流程大致如下:

预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。

宏定义是比较常用的预处理指令,即使用“标识符”来表示“替换列表”中的内容。标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容

常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。

1、无参宏定义
1.1 无参数宏定义的格式:
#define 标识符 替换列表
替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常量,或称符号常量。

1.2 使用说明:

  1. 可以不在行首,但只允许它前面有空格符。例如:

#define PI 3.1416 //正确,该行#前允许有空格
int a;#define N 5 //错误,该行#前不允许有空格外的其他字符
2) 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号

#define N =5 //虽语法正确,但预处理器会把N替换成=5
int a[N]; //错误,因为宏替换之后为 int a[=5];
#define N 5; //虽语法正确,但会把N替换成5;
int a[N]; //语法错误,宏替换后,为int a[5;];错误
3) 由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”。例如:

#define N 3+2
int r=N*N;

宏替换后为:
int r=3+2*3+2; //r=11
如果采用如下形式的宏定义:

#define N (3+2)
int r=N*N;

则宏替换后,为:
int r=(3+2)*(3+2); //r=25
4) 当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行。例如:

#define USA “The United
States of
America”

printf(“%s\n”,USA);

//输出结果为:The United States of America
该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使用续行符 \ 把该串分若干行来写,除最后一行外,每行行尾都必须加续行符 \。注意:续行符后直接按回车键换行,不能含有包括空格在内的任何字符,否则是错误的宏定义形式。

5)宏可以嵌套,但不参与运算:

#define M 5 // 宏定义
#define MM M * M // 宏的嵌套
printf(“MM = %d\n”, MM); // MM 被替换为: MM = M * M, 然后又变成 MM = 5 * 5
实际的 5 * 5 相乘过程在编译阶段完成,而不是在预处理器工作阶段完成,宏不进行运算,它只是按照指令进行文字的替换操作,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段发现。

6)宏定义必须写在函数之外,作用域从 #define 开始,到源程序结束。如果要提前结束它的作用域则用 #undef 命令:

#define M 5 // 宏定义
printf(“M = %d\n”, M); // 输出结果为: M = 5
#undef M // 取消宏定义
printf(“M = %d\n”, M); // error:… main.c:138:24: Use of undeclared identifier ‘M’
7)可以用宏定义表示数据类型,可以使代码简便:

#define STU struct Student // 宏定义STU
struct Student{ // 定义结构体Student
char *name;
int sNo;
};
STU stu = {“Jack”, 20}; // 被替换为:struct Student stu = {“Jack”, 20};
printf(“name: %s, sNo: %d\n”, stu.name, stu.sNo);
8)#define 与 typedef 的区别:

两者都可以用来表示数据类型:

#define INT1 int
typedef int INT2;

两者是等效的,调用也一样:
INT1 a1 = 3;
INT2 a2 = 5;
但当如下使用时,问题就来了:

#define INT1 int *
typedef int * INT2;
INT1 a1, b1;
INT2 a2, b2;
b1 = &m; //… main.c:185:8: Incompatible pointer to integer conversion assigning to ‘int’ from ‘int *’; remove &
b2 = &n; // OK
INT1 a1, b1; 被宏代换后为: int * a1, b1;

即定义的是一个指向int型变量的指针 a1 和一个int型的变量b1.

INT2 a2, b2;表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int *的,

两个都是指向int型变量的指针

所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。

2、带参宏定义
2.1 带参数宏定义的格式:
#define 标识符(参数1,参数2,…,参数n) 替换列表
例如,求两个参数中最大值的带参宏定义为:

#define MAX(a,b) ((a)>(b)?(a) : (b))
int c=MAX(5,3);

//预处理器会将带参数的宏替换成如下形式:
int c=((5)>(3)?(5) : (3));
故计算结果c=5。
2.2 使用说明:

  1. 标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义,

#define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //错误的带参宏定义格式
#define SUM (a,b) a + b //定义有参宏
printf(“SUM = %d\n”, SUM(1,2)); //调用有参宏。Build Failed!
因为 SUM 被替换为:(a,b) a + b
和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参

  1. 宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义

#include <stdio.h>
#define MUL(a,b) (a*b)
int main (void)
{
int c;
c=MUL(3,5+1);
printf(“c=%d\n”,c);
return 0;
}

//修改后
#include <stdio.h>
#define MUL(a,b) ((a)*(b))//修改处1
int main (void)
{
int c;
c=MUL(3,(5+1);//修改处2
printf(“c=%d\n”,c);
return 0;
}
3、带参宏定义与函数调用的区别
带参宏定义 函数调用
调用发生时间 预处理阶段 程序运行期间
参数类型检查

在预处理阶段,对带参宏调用中的参数不做检查。即宏定义时不需要指定参数类型,适用于多种数据类型。 函数参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。
参数是否需要空间 宏替换,仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,不需要分配空间。 需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中。
运行速度 宏替换仅是简单文本替换,不做任何语法或逻辑检查。速度较快 函数在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法,函数在运行阶段参数需入栈和出栈操作,速度相对较慢。
代码长度

宏替换是文本替换,即如果需替换的文本较长,则替换后会影响代码长度 函数不会影响代码长度
故使用较频繁且代码量较小的功能,一般采用宏定义的形式,比采用函数形式更合适。

#define getchar() getc(stdin)
故调用该宏时,需要加括号,即传空参数:getchar()。

4、头文件中常用的宏定义
1)防止头文件被重复包含

#ifndef cTest_Header_h
#define cTest_Header_h
//头文件内容
#endif
在我们常用的 stdio.h 头文件中也可以见到很多宏定义,如:

备注:#ifndef 和 #endif 要一起使用,如果丢失#endif,可能会报错。

#define BUFSIZ 1024 //缓冲区大小
#define EOF (-1) //表文件末尾
#ifndef SEEK_SET
#define SEEK_SET 0 //表示文件指针从文件的开头开始
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1 //表示文件指针从现在的位置开始
#endif
#ifndef SEEK_END
#define SEEK_END 2 //表示文件指针从文件的末尾开始
#endif
知识补充:

#ifndef是"if not defined"的简写,是宏定义的一种,它是可以根据是否已经定义了一个变量来进行分支选择,一般用于调试(防止头文件的重复包含和编译)

第一种:

    #ifndef x#define x程序段1#else程序段2#endif//终止if

//先测试x是否被宏定义过 //如果x没有被宏定义过,定义x,并编译程序段 1; //如果x已经定义过了则编译程序段2的语句,“忽视”程序段 1

第二种:

语句1 #ifndef 标识1

语句2 #define 标识1

语句3 #endif

             语句4 ……语句5 ……

//如果标识1没有被定义,则重定义标识1,即执行语句2、语句3;如果标识1已经被定义,则直接跳过语句2、语句3,直接执行语句4、语句5、……

第三种:

//例如要编写头文件test.h,在头文件开头写上两行:

    #ifndef _TEST_H#define _TEST_H //一般是文件名的大写头文件结尾写上一行:#endif

当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会执行#ifndef _TEST_H和#endif之间的代码,当第二次包含test.h时前面已经定义了_TEST_H,条件为假,#ifndef _TEST_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义。

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯有的。标识的命名规则一般是头文件名全大写,前面加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

#ifndef _STDIO_H

#define _STDIO_H

#endif

5、宏中#和##的用法
使用#把宏参数变为一个字符串,例如,如果 a 是一个宏的形参,则替换文本中的 #a 则被系统转化为 “a”。而这个转化的过程成为 “字符串化(stringizing)”

#define SUM(a,b) printf(#a " + “#b” = %d\n",((a) + (b))) //宏定义,运用 # 运算符
SUM(1 + 2, 3 + 4); //宏调用
//输出结果:1 + 2 + 3 + 4 = 10

调用宏时,用 1 + 2 代替 a,用 3 + 4 代替b,则替换文本为:
printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),
接着字符串连接功能将四个相邻的字符串转换为一个字符串:
“1 + 2 + 3 + 4 = %d\n”
用##把两个宏参数贴合在一起,又称为“预处理器的粘合剂(Preprocessor Glue)”

#define NAME(n) num ## n //宏定义,使用 ## 运算符
int num0 = 10;
printf(“num0 = %d\n”, NAME(0)); //宏调用

NAME(0)被替换为 num ## 0,被粘合为: num0。
可变宏在这里不进行介绍

参考:

宏定义(无参宏定义和带参宏定义),C语言宏定义详解

详解宏定义(#define)___Sunshine_的博客-CSDN博客_宏定义

C语言宏定义、宏函数、内置宏与常用宏_Apollon_krj的博客-CSDN博客_c语言宏函数

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

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

相关文章

流密码之线性反馈移位寄存器,以习题:n=4的LFSR,输出序列满足ki-4+ki-3+ki=0,初始状态为1000,求最终输出序列 为例

文章重点 关于线性反馈移位寄存器的一些知识点本期就不详细介绍了,本期重点在于讲解习题,以使大家能顺利应对平时作业及期末考试。 习题 习题1如下:n=4的LFSR。输出序列满足ki-4+ki-3+ki=0。 初始状态为1000。求不同时刻的状态及最终输出序列。 分析思路 思路:我们知道…

HTTP有什么缺陷,HTTPS是怎么解决的

缺陷 HTTP是明文的&#xff0c;谁都能看得懂&#xff0c;HTTPS是加了TLS/SSL加密的&#xff0c;这样就不容易被拦截和攻击了。 SSL是TLS的前身&#xff0c;他俩都是加密安全协议。前者大部分浏览器都不支持了&#xff0c;后者现在用的多。 对称加密 通信双方握有加密解密算法…

python自学3

第一节第六章 数据的列表 列表也是支持嵌套的 列表的下标索引 反向也可以 嵌套也可以 列表的常用操作 什么是列表的方法 学习到的第一个方法&#xff0c;index&#xff0c;查询元素在列表中的下标索引值 index查询方法 修改表功能的方法 插入方法 追加元素 单个元素追加 多…

YOLO v9训练自己数据集

原以为RT-DETR可以真的干翻YOLO家族&#xff0c;结果&#xff0c;&#xff01;&#xff01;&#xff01;&#xff01; 究竟能否让卷积神经网络重获新生&#xff1f; 1.数据准备 代码地址&#xff1a;https://github.com/WongKinYiu/yolov9 不能科学上网的评论区留言 数据集…

教育知识与能力保分卷一(中学)

2.在教育学的发展过程中&#xff0c;代表马克思主义的教育学著作是&#xff08;A &#xff09;。 A.凯洛夫的《教育学》 B.赞可夫的《教学与发展》 C.杜威的《民主主义与教育》 D.昆体良的《论演说家的教育》 8.小贺在一次期…

电脑不小心格式化了,怎么恢复?

在这个数字化时代&#xff0c;电脑已经成为我们日常生活和工作中不可或缺的工具。然而&#xff0c;有时我们可能会不小心格式化电脑硬盘&#xff0c;导致重要数据的丢失。那么&#xff0c;电脑不小心格式化了&#xff0c;怎么恢复&#xff1f; 别着急&#xff0c;在本篇攻略中&…

开源模型应用落地-qwen1.5-7b-chat与vllm实现推理加速的正确姿势(八)

一、前言 就在前几天开源社区又发布了qwen1.5版本,它是qwen2模型的测试版本。在本篇学习中,将集成vllm实现模型推理加速,现在,我们赶紧跟上技术发展的脚步,去体验一下新版本模型的推理质量。 二、术语 2.1. vLLM vLLM是一个开源的大模型推理加速框架,通过PagedAttention…

记一次openfeign反序列化异常复盘

前言 之前业务部门有2个通用响应类&#xff0c;一个是负责和前端交互的响应类AjaxResult,一个是负责和后端RPC接口交互的响应类RpcResult。一开始这两个响应类的值字段都一样&#xff0c;形如下 private Boolean success;private String message;private Integer code;private…

掌握PDF全面指南:Python开发者的高效编程技巧

掌握PDF全面指南&#xff1a;Python开发者的高效编程技巧 简介PDF基础知识PDF的结构常见用途PDF在开发中的挑战 PDF处理库介绍PyPDF2ReportLabPDFMiner辅助库 读取和分析PDF文件使用PyPDF2读取PDF文件提取PDF中的文本和元数据分析PDF结构和内容 编辑和修改PDF文件合并多个PDF文…

如何制作一个分销商城小程序_揭秘分销商城小程序的制作秘籍

打造赚钱神器&#xff01;揭秘分销商城小程序的制作秘籍 在这个数字化高速发展的时代&#xff0c;拥有一个属于自己的分销商城小程序&#xff0c;已成为众多商家和创业者的必备利器。它不仅能够快速搭建起自己的在线销售渠道&#xff0c;还能够利用分销模式&#xff0c;迅速裂…

C# 对文件、文件夹的操作

在使用后面的代码前&#xff0c;需要&#xff1a; using System; using System.IO;C# 对文件的操作 判断文件是否存在 string filePath "E:\\new folder\\test\\myfile.xls"; if (File.Exists(filePath)) {// 如果文件存在 } else {// 如果文件不存在 }复制文件 …

中级前端面试整理-上篇

JS的基本类型 JavaScript中的基本类型是指&#xff1a;那些存储在内存中的值类型&#xff0c;他们是不可以变的、意味着一旦创建&#xff0c;值类型就不能改变。 JS的7种基本类型 布尔类型(Boolean): 表示true或者false数字类型(Number): 表示整数、浮点或者NaN字符串类型(St…

了解拒绝服务攻击:攻击类型、影响和防御措施

拒绝服务攻击&#xff08;Denial of Service&#xff0c;简称DoS&#xff09;是一种广泛存在的网络安全威胁&#xff0c;旨在使目标系统无法提供正常的服务&#xff0c;使其服务不可用或严重受限。在本文中&#xff0c;我们将深入探讨拒绝服务攻击的不同类型、其对网络系统和业…

安全特性 悬垂指针

英文名称 Dangling point&#xff0c;它还有一个兄弟叫 wild point - 野指针。 简单的对Dangling point做一个类比&#xff1a;我换手机号码了&#xff0c;但是没有通知老板&#xff0c;老板通讯录存的是我的旧号码。然后老板打电话有两种可能&#xff1a;打不通电话或者电话打…

pytorch 自定义函数

pytorch 自定义函数 介绍&#xff1a;https://zhuanlan.zhihu.com/p/344802526 主要构建 static method forward 和 backward 比如 layernorm: 参考&#xff1a;https://github.com/zhangyi-3/KBNet/blob/main/basicsr/models/archs/kb_utils.py 导数的推导&#xff1a;http…

Linux常用命令(超详细)

一、基本命令 1.1 关机和重启 关机 shutdown -h now 立刻关机 shutdown -h 5 5分钟后关机 poweroff 立刻关机 重启 shutdown -r now 立刻重启 shutdown -r 5 5分钟后重启 reboot 立刻重启 1.2 帮助命令 –help命令 shutdown --help&#xff1a; ifconfig --help&#xff1a;查看…

SpringBoot 接口报错该如何解决?

在Spring Boot应用中&#xff0c;接口报错可能由多种原因引起&#xff0c;包括但不限于业务逻辑错误、异常处理不当、依赖库问题、配置错误等。解决接口报错的过程需要分析具体的错误信息、排查可能的原因&#xff0c;并采取相应的调试和修复措施。 以下是解决Spring Boot接口…

AWS ECR(AWS云里面的docker镜像私库)

问题 上一篇文章&#xff0c;在AWS云上面部署了k8s集群&#xff0c;这次接下来&#xff0c;需要在一个docker镜像私库。 步骤 创建docker镜像私库 打开AWS ECR主页&#xff0c;创建一个docker镜像私库&#xff0c;如下图&#xff1a; 设置私有镜像库名称&#xff0c;直接创…

AI短视频矩阵运营软件|抖音视频矩阵控制工具

【罐头鱼AI传单功能介绍】 罐头鱼AI传单是一款专为短视频矩阵运营而设计的智能软件&#xff0c;旨在帮助用户高效管理和运营多个抖音账号&#xff0c;并提供一系列强大的功能来优化视频内容创作和发布流程。QQ:290615413以下是软件框架&#xff0c;详细介绍其功能和特点&#…

第一弹:Flutter安装和配置

目标&#xff1a; 1&#xff09;配置Flutter开发环境 2&#xff09;创建第一个Flutter Demo项目 Flutter中文开发者网站&#xff1a; https://flutter.cn/ 一、配置Flutter开发环境 Flutter开发环境已经提供集成IDE开发环境&#xff0c;因此需要配置开发环境的时候&#xf…