C语言基础知识:宏定义

这篇具有很好参考价值的文章主要介绍了C语言基础知识:宏定义。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一.预处理

二.宏定义用法

①宏常量

②宏语句

③宏函数

④其它

1.#undef 是用来撤销宏定义的,用法如下:

2.使用ifndef防止头文件被重复包含和编译

三.宏定义相关作用符

①换行符 "\"

②字符串化符 "#"

③片段连接符"##"

四.宏函数的巧用

①类型传递

②传递数组

五.注意事项

① 运算符优先级问题

②宏参数重复调用

③分号吞噬问题

④递归调用问题

⑤宏参数预处理


一.预处理

编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是宏发挥作用的阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见
 

二.宏定义用法

①宏常量

我们最常使用到的#define的用法就是用#define来定义一个符号常量,而要修改时,只需修改#define这条语句就行了,不必每处代码都修改。

#include"stdio.h"
#define PI 3.14
#define STR "圆周率约等于"
int main()
{
	printf("%s %f",STR,PI); //预处理时会被替换为 printf("%s %f","圆周率约等于",3.14);
	return 0;
}

②宏语句

我们还可以用宏定义一条或多条语句。

#include"stdio.h"
#define Print printf("hello world!")
int main()
{
	Print;  //预处理时会被替换为 printf("hello world!");
	return 0;
}

③宏函数

我还可以用宏来定义函数,因为宏定义也可以带参数。

#include"stdio.h"
#define Print(str) printf("%s",str)
int main()
{
	Print("这是一个只有一条语句的宏函数!");
    //预处理时会被替换为 printf("%s","这是一个只有一条语句的宏函数!")
	return 0;
}

④其它

1.#undef 是用来撤销宏定义的,用法如下:

#define PI 3.141592654
 
…
 
// code
#undef PI
//下面开始 PI 就失效了

2.使用ifndef防止头文件被重复包含和编译

这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。 C语言在对程序进行编译时,会先根据预处理命令进行“预处理”。C语言编译系统包括预处理,编译和链接等部分。

#ifndef x //先测试x是否被宏定义过
#define x //如果没有宏定义下面就宏定义x并编译下面的语句
...
...
...
#endif //如果已经定义了则编译#endif后面的语句

条件指示符#ifndef检查预编译常量在前面是否已经被宏定义。如果在前面没有被宏定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行编译处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。
千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

所以还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

#ifndef <标识>

#define <标识>

......

#endif

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

#ifndef _STDIO_H_

#define _STDIO_H_

......

#endif

#ifndef xxx //如果没有定义xxx
#define xxx //定义xxx
#endif //结束如果
这个用法主要是在头文件中,主要是为了防止类重复的include,所以在类的头文件之前加上前面两个,用类名替代xxx,在最后加上最后一句。

三.宏定义相关作用符

①换行符 "\"

我们定义宏语句或者宏函数时不可能总是一条语句呀,那要是有很多条语句时怎么办?都写在一行吗?这样显然代码就不美观,可读性不好,所以有多条语句时,我们就在每行末尾(除了最后一行)加上"\",代表换行的意思。

#include"stdio.h"
#define Print   printf("这是第1条语句\n");\
 		    	printf("这是第2条语句\n");\
 		    	printf("这是第3条语句\n")
 		    	
#define Show(str1,str2,str3)\
{\
	printf("%s\n",str1);\
	printf("%s\n",str2);\
	printf("%s\n",str3);\	
}
int main()
{
	Print;  //无参数宏函数
	Show("first","second","third"); //带参数宏函数
	return 0;
}

②字符串化符 "#"

"#"是“字符串化”的意思,将出现在宏定义中的#是把跟在后面的参数转换成一个字符串。

#include"stdio.h"
#define Print(str)\
{\
	printf(#str"的值是%d",str);\	
}
int main()
{
	int x=3,y=4;
	Print(x+y); //此处等价于printf("x+y""的值是%d",x+y);
	            //#str等价于"x+y",所以#str不需要再用双引号引起来 
	return 0;
}

③片段连接符"##"

“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。
 

#include"stdio.h"
#define Add(n,value)\
{\
	num##n+=value;\
 } 
int main()
{
	int num1=1;
	int num2=10;
	Add(2,10); //等价于num2+=10; 这里把num和2连接成了num2 
	printf(" num1=%d\n num2=%d",num1,num2); 
	return 0;
}

四.宏函数的巧用

①类型传递

我们知道函数虽然可以传递参数,但是却不能把类型作为参数传递,有时我们为了实现函数的复用性,就要使用STL模板,但是我们这个时候还有另外一种选择,就是写成宏函数。
 

//一个开辟内存的函数

#define Malloc(type,size) (type*)malloc(sizeof(type)*size)

这个时候,我们只有把类型,容量作为参数传递进行,就可以开辟各种类型的内存了。

int *p=Malloc(int,100); //开辟int类型的内存
char *q=Malloc(char,100); //开辟字符类型的内存

②传递数组

由数组作为函数参数传递时,会失去其数组特性,也就是无法使用sizeof()函数计算出数组的大小,比如我们写一个排序函数,排序时我们不仅需要知道数组的首地址,还需要知道数组的大小,但是仅仅把数组名作为参数传递时,无法得知其数组大小,这时我们的函数就需要传递第二个参数,也就是数组的大小,于是函数就要写成Sort(int *a,int size).但宏函数就可以解决这个问题。

//下面用宏函数写一个插入排序算法

#include"stdio.h"
#define InsertSort(list)\
{\
	int s=sizeof(list)/4;\
	int i,j;\
	for(i=2;i<=s;i++)\
	{\
		list[0]=list[i];\
		for(j=i-1;list[j]>list[0];j--)\
				list[j+1]=list[j];\	
		list[j+1]=list[0];\		
	}\ 
}
int main()
{
	int num[]={0,2,5,7,3,1,8,0,8,22,57,56,74,18,99,34,31,55,41,12,9,4};
	InsertSort(num);
	for(int i=1;i<sizeof(num)/4;i++)	
		printf("%d ",num[i]);
	return 0;
} 

当然还有很多宏定义的巧妙用法,这里就不全部列举了。

五.注意事项

① 运算符优先级问题

#define MULTIPLY(x, y) x * y

这是一个很简单的乘法函数,当计算MULTIPLY(10, 10),结果是100,这个大家都知道,但是当你计算MULTIPLY(5+5, 10)时,你以为结果还是100吗?当然不是,MULTIPLY(5+5, 10)=5+5*10=55,所以结果是55,所以我们写宏函数时要特别注意运算符的优先级,这里稳妥一点的写法应该这样写

#define MULTIPLY(x, y) ((x)*(y))

②宏参数重复调用

#define MAX(a,b) ((a)>(b)?(a):(b))
int a=0;
int b =1;
int c =MAX(++a,++b);

这里很多人都以为是c=MAX(1,2)=2;而实际上上面代码等价于

int c =((++a)>(++b)?(++a):(++b));

可以看到实际上a b都各自加了两次,所以c=1>2?2:3=3,所以结果是3。

③分号吞噬问题

#include"stdio.h"
#define FUN(n)\
{\
	while(n>0)\
	{\
		if(n==3)\
			break;\	
	}\	
}
int main()
{
	int num=10;
	if(num>0)
		FUN(num);
	else
		num=-num;
	return 0;
}

看似代码没有问题,但是一编译就报错,编译器显示"error: ‘else’ without a previous ‘if’",原来是因为FUN函数是一个代码块,然后if(num>0) FUN(num); 就等价于if(num>0) {…}; 这不就是在大括号后面打分号了吗?这样else当然就缺少if了。
这个时候我们可以用do{…}while(0)来解决这个问题,写成如下就没问题了,因为while()后面正好需要一个分号。

#define FUN(n)\ 
do\
{\
	while(n>0)\
	{\
		if(n==3)\
			break;\	
	}\	
}while(0)

④递归调用问题

#define NUM (4 + NUM)

按前面的理解,(4 + NUM)会展开成(4 + (4 + NUM)),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,NUM只会展开成(4 + NUM),而展开之后NUM的含义就要根据上下文来确定了。

⑤宏参数预处理


宏参数中若包含另外的宏,那么宏参数在被代入到宏体之前会做一次完全的展开,除非宏体中含有#或##。

有如下宏定义:

#define A(y) X_##y
#define B(y) A(y)
#define SIZE 1024
#define S SIZE

A(S)会被展开成X_S。因为宏体中含有##,宏参数直接代入宏体。
B(S)会被展开成X_1024。因为B(S)的宏体是A(S),并没有#或##,所以S在代入前会被完全展开成1024,然后才代入宏体,变成X_1024。文章来源地址https://www.toymoban.com/news/detail-455853.html

到了这里,关于C语言基础知识:宏定义的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

    在这个环境中,源代码被转化为可执行的机器指令(二进制指令) 单文件简易版本 多文件简易版本 编译链接详细版本 VS2022集成IDE(windows下)的编译器叫cl.exe,链接器叫link.exe gcc编译器(windows下)的几个有关编译环境的命令 (1)符号表会把全局变量和具有外部链接的函数

    2023年04月11日
    浏览(29)
  • 【C语言初阶】带你轻松掌握指针基础知识(1)——指针的定义,类型,大小

    君兮_的个人主页 勤时当勉励 岁月不待人 C/C++ 游戏开发 Hello,这里是君兮_,最近刚回家有点懒,从今天开始恢复更新并开始更新新的刷题系列,我们先继续更新0基础入门C语言的内容,今天给大家带来的是指针方面的内容,但由于是初阶,一些高级的用法我们放在进阶篇再讲

    2024年02月12日
    浏览(32)
  • 【05】STM32·HAL库开发-C语言基础知识 | stdint.h介绍 | 位操作 | 宏定义的使用 | 条件编译 | extern声明 | typdef使用 | 结构体、指针、代码规范介绍。

      stdint.h 是从 C99 中引进的一个标准 C 库的文件,可以在MDK5的安装路径:D:MDK5.34ARMARMCCinclude中找到。   stdint.h 定义了很多类型别名,将有符号的char类型定义别名为int8_t等,使用此套别名有易于移植。   在MDK中需要配置才能支持使用S99标准, 默认是勾选的 。   只

    2024年02月08日
    浏览(33)
  • 深度学习预备知识-数据存储、数据预处理

    为了能够完成各种数据操作,我们需要某种方法来存储和操作数据。 通常,我们需要做两件重要的事: (1)获取数据; (2)将数据读入计算机后对其进行处理。 如果没有某种方法来存储数据,那么获取数据是没有意义的。 首先,我们介绍n维数组,也称为 张量 (tensor)

    2024年01月17日
    浏览(33)
  • 数据结构|基础知识定义

    1.值传递、地址传递、值返回、地址返回 1 值传递 :普通变量作为函数参数传递是单向的值传递,只是将实参的值复制一份给形参变量,形参的改变不会影响实参的值,因为所在内存空间不同 如果传递的是地址,被调函数使用指针接收,如果在被调函数中,没有更改指针指向

    2024年02月08日
    浏览(33)
  • 【C语言】预处理

    在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。 1.翻译环境 组成一个程序的每个源文件通过编译过程分别转换成目标代码 每个目标文件由链接器(linker)捆绑在

    2024年02月17日
    浏览(54)
  • scss 预处理器自定义ui框架(bem架构)

    bem架构 它是一种css架构 oocss 实现的一种 (面向对象css) ,BEM实际上是block、element、modifier的缩写,分别为块层、元素层、修饰符层,element UI 也使用的是这种架构 BEM 命名约定的模式是: 在项目中安装sass依赖 新建bem.scss文件(内容如下) 在vite.config.ts进行配置 在页面中进行

    2024年02月16日
    浏览(26)
  • 详解C语言—预处理

    目录 一、预处理 1、预定义符号介绍 2、预处理指令 #define #define 定义标识符:  #define 定义宏: #define 替换规则 3、预处理操作符# 4、预处理操作符## 5、带副作用的宏参数 6、宏和函数对比 二、命名约定 三、预处理指令 #undef 四、命令行定义 五、条件编译  1、单分支#if:

    2024年02月08日
    浏览(46)
  • C语言预处理详解

    上一篇博客中我们讲了C语言的编译与链接,在编译过程中有三个小阶段:预处理、编译、汇编。 本篇博客将详细讲述预处理部分的有关知识点 。 在C语言中,C语言本身设置了⼀些预定义符号,可以直接使⽤ ,预定义符号的处理也是在预处理期间进行的。 在这里介绍几个常

    2024年04月15日
    浏览(45)
  • C语言:预处理详解

    创作不易,来个三连呗! C语⾔设置了⼀些预定义符号, 可以直接使⽤ ,预定义符号也是在预处理期间处理的。 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

    2024年01月19日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包