C/C++中的宏定义

在C程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来像函数。预处理器用复制宏代码的方式代替函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数、执行return等过程,从而提高了速度,避免函数调用,提高程序效率。使用宏代码最大的缺点是容易出错,预处理器在复制宏代码时常常产生意想不到的边际效应。

宏是一种预处理器指令,在预编译阶段将宏名替换为后面的替换体。

如何使用宏

  #define         MAX             960

  预处理指令 宏名        替换体(多行可用 \ 延续)

不带参数的宏

#include <stdio.h>
#include <stdlib.h>
#define _width 1024 //宏命名规则同变量名
#define ADDR "中华人民共和国河南省"
int main(void){
printf("width: %d\n", _width);
printf("我的祖籍: %s\n", ADDR);
return 0;
}

带参数的宏(不是真正的参数)

#include <stdio.h>
#include <stdlib.h>
#define SQUARE(x) x*x  
int main(void) {
int i = 10;
int j = SQUARE(i); // 宏展开 j = i*i;
int k = SQUARE(8 + 2); // 宏展开 8+2*8+2
printf("j: %d\n", j);
printf("k: %d\n", k);
system("pause");
return 0;
}

这里可以知道,使用宏比调用函数更加高效。因为,使用宏就像使用头文件一样,就比如使用<string.h>,使用头文件<string.h>进行预编译之后,直接可以使用字符串进行定义使用;宏也是如此,在定义宏之后,编译器直接进行了预编译,这时候调用它,就是直接进行替换。

而调用其他函数时,要给他在内存中单独分配空间,普通变量分布在栈区,动态内存分布在堆区,静态变量在全局数据区(全局数据区也包括全局变量),字符常量在常量区,二进制指令(也就是函数体)分布在代码区。执行这个函数时,要获取被调用函数指定的地址(被调用函数的地址有一个范围,起始地址就是函数的入口地址,被调用函数从起始地址开始一步步往下执行),之后程序会跳转到被调函数的第一条语句,一步步往下依次执行被调函数中的语句,直到函数执行结束。

如果宏函数与函数名称相同,优先使用宏定义函数

例如:

#include <stdio.h>
float div(float, float);
#define div(x, y) x / y
int main()
{
// use of macro div
// Note: %0.2f for taking two decimal value after point
printf("%0.2f", div(10.0, 5.0));
// removing defined macro div
#undef div
// function div is called as macro definition is removed
printf("\n%0.2f", div(10.0, 5.0));
return 0;
}
// div function definition
float div(float x, float y)
{
return y / x;
}

结果是:

2.00

0.50

预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令#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

和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参

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

#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。

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

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

相关文章

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(二)

《拉依达的嵌入式\驱动面试宝典》—C/CPP基础篇(二) 你好,我是拉依达。 感谢所有阅读关注我的同学支持,目前博客累计阅读 27w,关注1.5w人。其中博客《最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客》已经是 Linux驱动 相关内容搜索的推荐首位,感谢大家支持。 《拉…

批量导出工作簿中高清图片-Excel易用宝

我同事在工作簿中做了三个图表&#xff0c;现在需要将工作簿中的图标导出保存为高清图片&#xff0c;通过右键另存为保存的图片并非高清图片&#xff0c;其实要把Excel工作簿中的图表或图片对象导出为高清图片也很简单。 单击Excel易用宝 Plus&#xff0c;导出高清图片。 在导出…

测试工程师八股文05|功能测试、业务测试

一、基础概念 1、软件测试分类 1️⃣按照软件产生的阶段划分 单元测试&#xff1a;针对程序源代码进行测试【开发自测】集成测试&#xff1a;针对模块之间功能交互进行测试系统测试&#xff1a;对整个系统&#xff08;功能、非功能&#xff09;进行全面测试验收测试&#xff…

“AI全网络深度学习系统:开启智能时代的新篇章

嘿&#xff0c;大家好&#xff01;今天咱们来聊聊一个特别前沿的话题——AI全网络深度学习系统。这名字听起来是不是有点像科幻电影里的玩意儿&#xff1f;但其实&#xff0c;它已经悄悄地走进了我们的生活&#xff0c;而且正在改变我们的工作方式。 首先&#xff0c;咱们得搞清…

【Linux|计算机网络】HTTPS工作原理与安全机制详解

目录 1、HTTPS是什么&#xff1f; 2、概念准备 2.1.什么是加密、解密、密钥 2.2.为什么要加密 2.3.常见的加密方式 1.对称加密 2.非对称加密 2.4.数据摘要 && 数据指纹 2.5. 数字签名 3.HTTPS 的工作过程探究 方案 1 - 只使用对称加密 方案 2 - 只使用非对…

shell脚本自动发布Java应用

单体项目或定制化小应用&#xff0c;频繁发布会有些麻烦&#xff0c;用脚本实现git提交完代码自动发布&#xff0c;并完成jar包备份 1.前提条件&#xff1a;linux安装了JDK、Maven、Git 安装参考链接&#xff1a; jdk安装 https://blog.csdn.net/weixin_44904239/article/de…

搭建自己的wiki知识库(重新整理)

写在前面&#xff1a; 之前写过一篇&#xff0c;因为这次修改的内容比较多&#xff0c;所以不想在原基础上修改了&#xff0c;还是重新整理一篇吧。搭建wiki知识库&#xff0c;可以使用在线文档&#xff0c;如xx笔记、xx文档、xx博客、git仓库&#xff08;如&#xff1a;GitHu…

【Python网络爬虫笔记】10- os库存储爬取数据

os库的作用 操作系统交互&#xff1a;os库提供了一种使用Python与操作系统进行交互的方式。使用os库来创建用于存储爬取数据的文件夹&#xff0c;或者获取当前工作目录的路径&#xff0c;以便将爬取的数据存储在合适的位置。环境变量操作&#xff1a;可以读取和设置环境变量。在…

MySQL:表的内置函数

目录 一. 日期函数 二. 字符串函数 三. 数学函数​编辑 四. 其他函数 博客开始为各位读者介绍一个投递简历的平台&#xff1a;万码优才 专属于程序员的投递平台&#xff0c;大家快去试试吧&#xff01;&#xff01;&#xff01; 此篇博客讲解MySQL中关于表的内置函数。…

亚马逊云科技2024 re:Invent大会亮点:Nova大模型与AI基础设施全面升级

引言 作为云计算领域的年度盛会,亚马逊云科技(AWS)的re:Invent大会一直是业界瞩目的焦点。2024年的大会不负众望,推出了一系列重磅产品和服务,尤其是在人工智能和大模型方面的创新令人印象深刻。本文将为您深入解析此次大会的主要亮点,探讨AWS在AI时代的最新布局及其对行业的潜…

(九)机器学习 - 多项式回归

多项式回归&#xff08;Polynomial Regression&#xff09;是一种回归分析方法&#xff0c;它将自变量 xx 和因变量 yy 之间的关系建模为 nn 次多项式。多项式回归的目的是找到一个 nn 次多项式函数&#xff0c;使得这个函数能够最好地拟合给定的数据点。 多项式回归的数学表达…

米家智能设备接入苹果HomeKit,使用NAS部署『Homebridge』

米家智能设备接入苹果HomeKit&#xff0c;使用NAS部署『Homebridge』 哈喽小伙伴们好&#xff0c;我是Stark-C~ 说起HomeKit很多苹果用户都不陌生&#xff0c;作为苹果自己的智能家居生态控制系统&#xff0c;我们可以通过苹果自家应用【家庭】&#xff0c;无论是在家中还是远…

docker入门实践---虚拟机环境配置

文章目录 1.检查内核版本2.确定centos7可以上网3.关闭防火墙4.关闭防火墙5.更换阿里云6.安装gcc7.设置镜像仓库&#xff08;阿里云&#xff09;8更新软件包9.安装docket-ce10.启动docker11.普通用户权限设置 1.检查内核版本 2.确定centos7可以上网 3.关闭防火墙 下面的这个表示…

day11 性能测试(3)——Jmeter 断言+关联

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、复习 2、查看结果树 多个http请求原因分析 3、作业 4、Jmeter断言 4.1 响应断言 4.1.1 案例 4.1.2 小结 4.2 json断言 4.2.1 案例 4.2.2 小结 4.3 断言持续时间 4.3.1 案例 4.3.2 小结 4.…

卫生人员评审专家申报系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

TortoiseGit 图标覆盖设置

TortoiseGit 图标覆盖设置 图标覆盖设置隐藏图标覆盖切换样式 我们安装了小海龟后&#xff0c;它会在仓库目录下给所有图标覆盖上状态标记。 图标覆盖设置 右键菜单打开 &#xff1a;设置 》 图标覆盖。 隐藏图标覆盖 如果不想图标上出现小乌龟的状态标记。直接点这里可以…

Redis是什么?Redis和MongoDB的区别在那里?

Redis介绍 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、基于内存的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。以下是关于Redis的详细介绍&#xff1a; 一、数据结构支持 字符串&#xff08;String&#xff09; 这是Redis最…

家校通小程序实战教程10部门管理前后端连接

目录 1 加载后端的数据2 为什么不直接给变量赋值3 保存部门信息4 最终的效果5 总结 现在部门管理已经完成了后端功能和前端开发&#xff0c;就需要在前端调用后端的数据完成界面的展示&#xff0c;而且在录入部门信息后需要提交到数据库里&#xff0c;本篇我们介绍一下前后端如…

《自制编译器》--青木峰郎 -读书笔记 编译hello

在该书刚开始编译hello.cb时就遇到了问题。 本人用的是wsl&#xff0c;环境如下&#xff0c; 由于是64位&#xff0c;因此根据书中的提示&#xff0c;从git上下载了64位的cb编译器 cbc-64bit 问题一: 通过如下命令编译时,总是报错。 cbc -Wa,"--32" -Wl,"-…

Ubuntu安装Gitlab详细图文教程

1、环境准备 1.1、Ubuntu环境 Ubuntu24.04Sever版安装教程 1.2、更新系统 sudo apt update -y sudo apt-get update sudo apt-get upgrade 2、安装Nginx 2.1 安装nginx # 安装 apt install nginx -y 2.2 修改nginx配置⽂件 # 修改nginx配置 vim /etc/nginx/si…