一文带你搞懂C语言预处理宏定义

目录
  • 预定义符号
  • #define
    • #define 定义标识符
    • #define 定义宏
    • 替换规则
    • # 和##

预定义符号

这些预定义符号都是语言内置的

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

VS环境下未定义__STDC__ ,说明Visual Studio并未完全遵循ANSI C。

#define

#define 定义标识符

#define name stuff  //名称;内容
#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,       \
__DATE__,__TIME__ )

int main() {
	printf("%d\n", MAX);
	printf("%s\n", STR);
	do_forever;  //执行到这里会死循环
	return 0;
}

在define进行定义时,最好不要在后面加上分号 ;,替换时也会将分号替换,容易导致问题。

#define 1000;
int max = 0;
	if (3 > 1)
		max = MAX;
	else
		max = 0;
//报错

#define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

//int Max(int x, int y) {
//	return (x > y ? x : y);
//}
//若采用宏定义
#define Max(x,y) (x>y?x:y)
int main() {
	int a = 10;
	int b = 5;
	int max = Max(a, b);
	printf("%d\n", max);
	return 0;
}

最终得到较大值10。找到了宏的标识,直接进行符号替换。

  • 参数列表的左括号必须与name紧邻。
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

例如:

#define SQUARE(x) x*x

int main() {
	printf("%d\n", SQUARE(5));
	printf("%d\n", SQUARE(5 + 1));  //宏的参数是完全替换的,相当于 5 + 1 * 5 + 1  结果为11
	return 0;
}

做改进:

#define SQUARE(x) (x)*(x)  //这样就不容易出错
//最好最外层也加上括号

考虑如下计算:

#define Double(x) (x)+(x)
int main() {
	int a = 5;
	printf("%d\n", (10 * Double(a)));  //这里相当于 10 * (5) + (5)  结果为55
	return 0;
}

在宏替换内容上加上括号以保证得到预期的结果。

#define Double(x) ((x)+(x))

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

替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

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

注意:

宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

# 和##

如何把参数插入到字符串中?

#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE " is "FORMAT "\n", VALUE)

//这里#可以把其后面的参数替换为字符串嵌入

int main() {
	printf("hello world\n");
	printf("hello ""world\n");  //天然合为一个字符串(发现字符串是有自动连接的特点的)
	int a = 100;
	printf("The value a is %d\n", a);
	int b = 20;
	printf("The value b is %d\n", b);

	//那么考虑能否将同一个功能的打印操作合并
	PRINT("%d", a);  //这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中
	/*代码中的 #VALUE 会预处理器处理为:
		"VALUE" .*/

	return 0;
}

把宏对应的参数替换为字符串

##的作用

将分离的片段合成为一个符号

#define CAT(A,B) A##B
//把A和B组合成一个符号
int main() {
	int Windows11 = 2022;

	printf("%d\n", CAT(Windows, 11));

}

带副作用的宏参数

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

x+1;//不带副作用
x++;//带有副作用

考虑宏:

//考虑宏的副作用
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main() {
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);
	//z = ((x++) > (y++) ? (x++) : (y++));  x = 6  y = 10  z = 9
}

到此这篇关于一文带你搞懂C语言预处理宏定义的文章就介绍到这了,更多相关C语言预处理宏定义内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言宏定义容易认不清的盲区梳理

    目录 1.概念 3.宏不是函数 4.宏定义不是说明或语句 5.宏不是类型定义 6.与之相关的宏定义 7.总结 1.概念 #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本. 命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义. (1)简单的宏定义: #define<宏名> <字符串> #defineVALUE((sizeof(a))/sizeof(a[0])) (2) 带参数的宏定义 #defin

  • C语言宏定义的扩展定义讲解

    目录 1. 常量宏定义 2. 定义宏函数 3. 宏定义和#号结合 4. 宏定义和两个#结合 5. 宏定义和do…while()的结合 6. #ifdef…#else…#endif 7. #ifndef…#else…#endif 8. #if defined() … #else … #endif 1. 常量宏定义 使用c中的#define 来定义一个常量来表示一年有多少秒 #define SECONDS_PER_YEAR (606024*365)UL 求圆的周长: #define D® (r +

  • C语言#define定义宏的使用详解

    目录 1.宏是什么 2.宏的用法 3. 宏的注意事项 4. 宏和函数的区别 5.命名要求 6. 条件编译 常见条件编译指令及应用场景 1.宏是什么 #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro). 语法: #define name( parament-list ) stuff parament-list:是一个由逗号隔开的符号表. 2.宏的用法 #define SUM(x,y) ((x)+(y)) int main

  • C语言交换奇偶位与offsetof宏的实现方法

    目录 交换奇偶位 offsetof 宏 总结 交换奇偶位 题目内容:写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换. 注:二进制补码的最低位为第一位,最高位为第三十二位. 示例 1:输入:10输出:5解释:10的二进制补码为00000000000000000000000000001010,交换奇偶位后为00000000000000000000000000000101,该二进制补码为5的二进制补码,故输出为5. 思路:交换奇偶位,其实就当于将偶数位右移了一位,奇数位左移了一位.那现在的问题

  • C语言详细分析宏定义与预处理命令的应用

    目录 宏定义与预处理命令 预处理命令 - 宏定义 定义符号常量 定义傻瓜表达式 定义代码段 预定义的宏 函数 VS 宏定义 预处理命令 - 条件式编译 示例 宏定义与预处理命令 预处理阶段:处理宏定义与预处理命令: 编译期:检查代码,分析语法.语义等,最后生成.o或.obj文件: 链接期:链接所有的.o或.obj文件,生成可执行文件. 预处理命令 - 宏定义 定义符号常量 #define PI 3.1415926 #define MAX_N 10000 定义傻瓜表达式 #define MAX(a

  • C生万物C语言宏将整数二进制位的奇偶数位交换

    目录 题目分析 && 实现思路[位运算] 1.获取这个整数的奇数位和偶数位 2.使用移位运算使[奇变偶][偶变奇] 3.合并奇数位和偶数位 ⌨代码分析 1.代码展示 2.算法图解分析 总结与提炼 题目分析 && 实现思路[位运算] 首先来说一下本题的实现思路

  • 一文带你搞懂C语言预处理宏定义

    目录 预定义符号 #define #define 定义标识符 #define 定义宏 替换规则 # 和## 预定义符号 这些预定义符号都是语言内置的 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 VS环境下未定义__STDC__ ,说明Visual Studio并未完全遵循ANSI C. #define #defi

  • 一文带你搞懂C语言动态内存管理

    目录 一.malloc函数和free函数 二.calloc函数与malloc函数的异同 三.柔性数组 一.malloc函数和free函数 (1) 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针. 如果开辟成功,则返回一个指向开辟好空间的指针. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定. 如果参数 size为0,malloc的行为是标准是未

  • 一文带你搞懂Golang结构体内存布局

    目录 前言 结构体内存布局 结构体大小 内存对齐 总结 前言 结构体在Go语言中是一个很重要的部分,在项目中会经常用到,大家在写Go时有没有注意过,一个struct所占的空间不一定等于各个字段加起来的空间之和,甚至有时候把字段的顺序调整一下,struct的所占空间不一样,接下来通过这篇文章来看一下结构体在内存中是怎么分布的?通过对内存布局的了解,可以帮助我们写出更优质的代码.感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助. 结构体内存布局 结构体大小 结构体实际上就是由各种类型的数据组合而成

  • 一文带你搞懂Java中Object类和抽象类

    目录 一.抽象类是什么 二.初始抽象类 2.1 基本语法 2.2 继承抽象类 三.抽象类总结 四.Object类 4.1 初始Object 4.2 toString 4.3 equals 4.4 hashcode 一.抽象类是什么 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类. 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用.也是因为这个原因,通常在设计阶段决定要

  • 一文带你搞懂Java中Synchronized和Lock的原理与使用

    目录 1.Synchronized与Lock对比 2.Synchronized与Lock原理 2.1 Synchronized原理 2.2 Lock原理 3.Synchronized与Lock使用 Synchronized Lock 4.相关问题 1.Synchronized与Lock对比 实现方式:Synchronized是Java语言内置的关键字,而Lock是一个Java接口. 锁的获取和释放:Synchronized是隐式获取和释放锁,由Java虚拟机自动完成:而Lock需要显式地调用lo

  • 一文带你搞懂JS中六种For循环的使用

    目录 一.各个 for 介绍 1.for 2.for ... in 3.for ... of 4.for await...of 5.forEach 6.map 二.多个 for 之间区别 1.使用场景差异 2.功能差异 3.性能差异 三.for 的使用 for 循环在平时开发中使用频率最高的,前后端数据交互时,常见的数据类型就是数组和对象,处理对象和数组时经常使用到 for 遍历,因此下班前花费几分钟彻底搞懂这 5 种 for 循环.它们分别为: for for ... in for ... o

  • 一文带你搞懂Numpy中的深拷贝和浅拷贝

    目录 1. 引言 2. 浅拷贝 2.1 问题引入 2.2 问题剖析 3. 深拷贝 3.1 举个栗子 3.2 探究原因 4. 技巧总结 4.1 判断是否指向同一内存 4.2 其他数据类型 5. 总结 1. 引言 深拷贝和浅拷贝是Python中重要的概念,本文重点介绍在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理. 闲话少说,我们直接开始吧! 2. 浅拷贝 2.1 问题引入 我们来举个栗子,如下所示我们有两个数组a和b,样例代码如下: import numpy as np a = np.ar

  • 一文带你搞懂Maven的继承与聚合

    目录 一.继承 二.继承关系实施步骤 三.聚合与继承的区别 一.继承 我们已经完成了使用聚合工程去管理项目,聚合工程进行某一个构建操作,其他被其管理的项目也会 执行相同的构建操作.那么接下来,我们再来分析下,多模块开发存在的另外一个问题,重复配置的问题,我们先来看张图: ■ spring-webmvc.spring-jdbc在三个项目模块中都有出现,这样就出现了重复的内容 ■ spring-test只在ssm_crm和ssm_goods中出现,而在ssm_order中没有,这里是部分重复的内容

  • 一文带你搞懂Spring响应式编程

    目录 1. 前言 1.1 常用函数式编程 1.2 Stream操作 2. Java响应式编程 带有中间处理器的响应式流 3. Reactor 3.1 Flux & Mono 3.2 Flux Mono创建与使用 4. WebFlux Spring WebFlux示例 基于注解的WebFlux 基于函数式编程的WebFlux Flux与Mono的响应式编程延迟示例 总结 哈喽,大家好,我是指北君. 相信响应式编程经常会在各种地方被提到.本篇就为大家从函数式编程一直到Spring WeFlux做一次

  • 一文带你搞懂Java中的泛型和通配符

    目录 概述 泛型介绍和使用 泛型类 泛型方法 类型变量的限定 通配符使用 无边界通配符 通配符上界 通配符下界 概述 泛型机制在项目中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不仅如此,很多源码中都用到了泛型机制,所以深入学习了解泛型相关机制对于源码阅读以及自己代码编写有很大的帮助.但是里面很多的机制和特性一直没有明白,特别是通配符这块,对于通配符上界.下界每次用每次百度,经常忘记,这次我就做一个总结,加

随机推荐