初学宏时遇到的问题
1. 编译器对宏进行处理发生在什么时期
c语言编译过程
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FOwe38a3-1628512552586)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203253437.png)]](https://img-blog.csdnimg.cn/5c64bd686826446a9617059d6c13fc5a.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk2NjA4,size_16,color_FFFFFF,t_70)
预处理
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxnGALMo-1628512637857)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203434335.png)]](https://img-blog.csdnimg.cn/c8ddc38c0f8b4cf28ee7ad1b4b3831a4.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk2NjA4,size_16,color_FFFFFF,t_70)
C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见,预处理之后得到的仍然是文本文件。 编译
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYxeRwFt-1628512681761)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621203949104.png)]](https://img-blog.csdnimg.cn/61c753890fd64fe2af6994452fd62f67.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk2NjA4,size_16,color_FFFFFF,t_70)
汇编
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyfMoNcO-1628512552592)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621204157773.png)]](https://img-blog.csdnimg.cn/4a38dfd9afe74235ad2e067bea9ca168.png)
链接
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAbg5sFr-1628512552593)(C:\Users\yaohui\AppData\Roaming\Typora\typora-user-images\image-20210621204238302.png)]](https://img-blog.csdnimg.cn/3a956fc276644cf7ab9c80d0eaff6649.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk2NjA4,size_16,color_FFFFFF,t_70)
之后的过程,加载到内存等等行为,具体细节就涉及到操作系统相关知识。
所以,对宏的操作发生在程序处理的最初始的阶段—预处理阶段
2.头文件被多次引用带来的问题
- 变量, 函数等被重复定义,无法通过编译
经过测试发现,多次引用系统头文件是可以通过编译的,各系统头文件应该采取了解决措施。
- 如何采取解决措施
一般而言有两种解决办法
- 使用 #ifndef 条件编译
#ifndef __TEST_H__ /*如果该宏未被定义,则定义该宏和其他东西*/
#define __TEST_H__
//被定义的内容
#endif
- 使用#pragma once
#pragma once
// the code
两种方法的比较
#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心相同时,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。
#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名相同引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,此时就不能保证他们不被重复包含。当然,相比宏名相同引发的“找不到声明”的问题,重复包含更容易被发现并正。
但是#pragma once 并非100%可靠,假设你调用a.h和b.h ,且a.h下又包含了b.h,就可能导致重定义问题
3.宏可以出现在程序的哪些部分
弄清楚上面的问题后,这个问题也逐渐清晰起来
答案是可以定义在任何地方,头部,尾部均可,甚至可以放在函数内部,但是需要放在使用这个宏的前面
比如下面这段代码,将定义移入main函数内部也是没问题的,但是放在PrintX函数后面就会报错
#include<iostream>
using namespace std;
int PrintX();
int main(){
cout<<PrintX();
}
#define xx 100
int PrintX(){
return xx;
}
宏的用法
1. 宏常量
好处都懂,就是修改时只需要更改一处即可
2. 宏函数
以宏充当函数,里面的坑比较多,把握住 宏只是进行文本替换即可
#define MAX(X,Y) ( (X) > (Y) ? (X) : (Y) )
#define ABS(X) ( (X)<0 )? -(X): (X) )
之所以要加括号,是因为避免优先级等等进行文本替换时发生的问题
#define MUL(X,Y) X*Y
/*假设此时要调用宏函数 MUL(2+3,3+5),则会变成*/
MUL(2+3,3+5) 2+3*3+5
但是还是避免不了一种情况++ 或–运算符
#define SQUARE(X) (X)*(X)
/*调用 SQUARE(x++),假设x为2 如果是函数,应当输出9,x变成3,而宏展开后:*/
SQUARE(x++) (x++)*(x++)
3. #运算符
在类函数宏的替换体中,#号作为一个预处理符号,可以把记号替换为字符串 #x—> “x” (字符串化)
#define PRINTSQ(x) printf(#x"的平方为 %d",(x)*(x))
宏替换的过程:
printf("/*此处为具体的x*/""的平方为%d",(x)*(x))
之后字符串的串联功能将这两个合二为一
/*分析*/
#include<iostream>
#define Print(x) printf(#x"的平方为%d",(x)*(x))
using namespace std;
#define xx 100
int main(){
Print(xx);
}
/*执行结果为:xx的平方为10000 ,而不是 100的平方为10000
在xx被替换成100之前,xx已经被作为可替换的记号插入宏中了
换句话说,第一个宏比第二个宏要先被替换
*/
4. ##运算符
##也可以用于类函数宏的替换部分,而且可以用于对象宏的替换部分,充当粘合剂,将两个符号组合成一个符号
#define XNAMEX(n) x##n
#define PRINT_XN(n) printf("x"#n"=%d",x##n);
假设调用时
int XNAMEX(1)=12;
PRINT_XN(1);
则会输出 x1=12
5. 变参宏
printf 和scanf都接受数量可变的参数 ,stdvar.h也有类似工具
在宏中, …用于表示可变,将宏参数列表中最后的参数写成省略号,用 _ _ VA_ARGS _ _放在替换部分,表明省略号省略了什么
#define PR(x,...) printf(#x":",__VA_ARGS__)
PR(2,"x=%.2f,y=%.4f\n", 2.3 , 4.5);
6. 宏的注意事项
- 宏名不能有空格,但是替换字符串可以有空格
- 宏函数一般都是大写,与函数区分
- 宏即生成内联代码,没有函数调用时的开销,故选择函数or宏时,如果表达内容简单,则宏更有优势。所以只使用一次的宏也无法明显减少程序运行时间
- 宏的标识符和c语言规则一样,由大写字母,小写字母,数字和下划线组成,且首字母不能是数字
7. #include指令
- 文件名在尖括号中代表预处理器在标准系统目录里查找该文件
- 文件名在双引号中代表预处理器在当前目录里查找该文件,如未找到再查找系统目录
- 文件名也可以包含路径名
#include<iostream>
#include"complex.h"
#include"/usr/biff/complex.h"
8. #undef指令
用于取消已经定义的#define指令
使得被定义的#define生效的范围为,从定义开始到取消定义之间的区域
不取消定义的情况下,作用域为从定义开始到文件尾
#define _SIZE 200
#undef _SIZE
9. 条件编译指令
- #ifdef 如果已经定义了某宏 则xxx
- #else 否则xxx
- #endif 结束条件编译
- #ifndef 如果没有定义某宏,则xxx
#ifdef HELLO
#include"complex.h"
#define MAXM 10
#else
#define "String.h"
#define MAXM 20
#endif
/*-------------------------*/
#ifndef _COMPLEX_
#define _COMPLEX_
/* code */
#endif
-
#if 和#elif指令 如果表达式为真则xxx defined是一个预处理符 defined(XX)如果顶一个则返回1 #if SYS==1
#include<stdio.h>
#elif SYS==2
#include<stdlib.h>
#elif defined(_HELLO_)
#else
#include<string.h>
#endif
10. 系统中的预定义宏
宏 | 含义 |
---|
_ _DATE _ _ | 预处理的日期(Mmm dd yyyy) | _ _FILE _ _ | 当前源代码文件名的字符串字面量 | _ _ LINE_ _ | 当前源代码文件中行号的整形常量 | _ _STDC _ _ | 设为1表示实现遵循C标准 | _ _ STDC_HOSTED _ _ | 本机环境设置为1否则为0 | _ _STDC_VERSION _ _ | 支持C99设置为 199901L ;支持C11标准设置为201112L | _ _TIME _ _ | 翻译代码的时间 (hh:mm:ss) |
11. #line和#error
#line重置__ LINE__ 和 __ FILE__的行号和文件名,#error让预处理器发出一条错误信息
#line 100
#line 10 "hello.c"
#if SIZE==10
#error size too small
12. #pragma
#pragma指示使每个编译程序在保留C和C++语言的整体兼容性时提供不同机器和操作系统特定的功能。编译指示被定义为机器或操作系统特定的,并且通常每种编译程序是不同的。
具体细节参见下面博客
https://blog.csdn.net/lmhuanying1012/article/details/78549763?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162434109316780261977038%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162434109316780261977038&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-78549763.first_rank_v2_pc_rank_v29&utm_term=%23prama&spm=1018.2226.3001.4187
?
|