C预处理指令

C预处理指令

预处理

gcc -E xxx.c 查看C代码的预处理结果,显示在终端
gcc -E xxx.c -o xxx.i 把预处理的结果保存到文件中,以.i结尾的文件也被称为预处理文件。

1、文件包含指令

#include 预处理指令的功能是导入一个头文件到当文件中,它用两中使用方法:

方法1:#include <file_name.h>

​ 从系统指定的路径查找并导入头文件,一般用于导入标准库、系统、第三方库的头文件。

可通过设置操作系统的环境环境、编译器-I参数来指定头文件查找路径

方法2:#include “file_name.h”

​ 从系统当前路径查找并导入头文件,如果没有再从系统指定的路径查找并导入头文件,一般用于导入自定义头文件。

2、宏替换指令

#define 宏名 会被替换的内容

预处理阶段,预处理器会把宏定义的内容原样替换到使用宏名的位置

//宏常量
#define ARR_LEN 20      
int arr[ARR_LEN];

//宏表达式
#define STU_INFO "%s %c %hd %d"
typedef struct Student
{
    int id; 
    char name[20];
    char sex;
    float score;
}Student;
printf(STU_INFO,stu.id,stu.name,stu.sex,stu.score);

注意:由于宏常量和宏表达式可能使用在表达式中,所以在定义时在末尾不要加分号。
编译器预定义的宏
__FILE__ 获取当前文件名
__func__ 获取当前函数名
__LINE__ 获取当前行号
__DATE__ 获取当前日期
__TIME__ 获取当前时间//编译时的时间
__WORDSIZE 获取当前编译器的位数
// 我们DEBUG时,适合用来显示警告、错误信息。
#include <stdio.h>

int main(int argc,const char* argv[])
{
    printf("%s\n",__FILE__);
    printf("%s\n",__func__);
    printf("%d\n",__LINE__);
    printf("%s\n",__DATE__);
    printf("%s\n",__TIME__);                 
}
/*
test.c
main
8
Aug 14 2023
22:37:35
*/
标准库预定义的宏:
// limits.h 头文件中定义的所有整数类型最大值、最小值
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127 
#define UCHAR_MAX 255

#define SHRT_MIN  (-32768)
#define SHRT_MAX  32767
#define USHRT_MAX 65535

#define INT_MIN  (-INT_MAX - 1)
#define INT_MAX  2147483647
#define UINT_MAX  4294967295U

#define LLONG_MAX   9223372036854775807LL
#define LLONG_MIN   (-LLONG_MAX - 1LL)
#define ULLONG_MAX  18446744073709551615ULL

// stdlib.h 头文件定义两个结标志
#define EXIT_SUCCESS (0)
#define EXIT_FAILURE (-1)

// stdbool.h 头文件定义了bool、true、false sizeof=>1,4,4 c++=>1,1,1
#define bool    _Bool(c99)
#define true    1
#define false   0

// libio.h 头文件定义了NULL
#define NULL ((void*)0) 
3、宏函数

​ 宏函数不是真正的函数,而是带参数的宏替换,只是使用方法像函数而已。

​ 在代码中使用宏函数,预处理时会经历两次替换,第一次把宏函数替换成它后面的一串代码、表达式,第二次把宏函数中的参数替换到表达式中。

#define ARR_SIZE(a) sizeof(a)/sizeof(a[0])

int arr[] = {1,2,3,4};
int len = ARR_SIZE(arr);
注意事项:

1、宏函数后面的代码不能直接换行,如果代码确定太长,可以使用续行符换行。

#define 宏名(a,b,c,...) {  \
	代码1; \
	代码2; \
	 ...   \
}

2、为了防止宏函数出现二义性,对宏参数要尽量多加小括号。

假设如下定义:
#define SUM(a,b) a+b
//如果这样替换,那么替换后就变成了 3+4*12
int num = SUM(3,4)*12;

所以应该加上括号
#define SUM(a,b) (a+b)

再假设如下定义:
#define MUT(a,b) (a*b)
//像上面一样加上了括号 应该没问题了吧
//看看替换后  3+2*4 显然不是我们想要的结果
int num = MUT(3+2,4);

所以为了保险起见,我们给每个参数都套上括号

#define SUM(a,b) ((a)+(b))
#define MUT(a,b) ((a)*(b))

3、传递给宏函数的参数不能使用自变运算符,因为我们无法知道参数在宏代码中会被替换多少次。

#define MUT(a,b) ((a)*(a)*(b))
int n=1;
int num = MUT(n++,4));
printf("%d %d", num, n);
//(n++)*(n++)*(4) 
//8 3    1*2*3 并不是2*2*4
优缺点

优点:

1、执行速度快,它不是真正的函数调用,而是代码替换,不会经历传参、跳转、返回值。

2、不会检查参数的类型,因此通用性强。

缺点:

1、由于它不是真正的函数调用,而是代码替换,每使用一次,就会替换出一份代码,会造成代码冗余、编译速度慢、可执行文件变大。

2、没有返回值,最多可以有个执行结果。

3、类型检查不严格,安全性低。

4、无法进行递归调用。

条件编译

​ 条件语句(if、switch、for、while、do while)会根据条件选择执行哪些代码,条件编译就是预处理器根据条件选择哪些代码参与下一步的编译。

负责条件编译的预处理指令有:
#if #ifdef #ifndef #elif #else #endif
头文件卫士
#ifndef FILE_H // 判断FILE_H宏是否正在,不存在则条件为真
#define FILE_H // 定义FILE_H宏

// 头文件卫士能保证此处不重复出现

#endif//FILE_H // #ifndef的结尾
注释代码
#if 0|1
可注释大块代码,可以嵌套    
#endif 
版本、环境判断:
#if __WORDSIZE == 64
	typedef long int        int64_t;
#else
	typedef long long int       int64_t;
#endif


// 判断是否是Linux操作系统:
#if __linux__

#endif 

// 判断是否是Windows操作系统:
#if __WIN32 | __WIN32__ | __WIN64__

#endif 

// 判断gcc还是g++:

#if __cplusplus
    printf("g++编译器\n");
#else
    printf("gcc编译器\n");
#endif
DEBUG宏:

​ 专门用于调试程序的宏函数,这种宏函数在程序测试、调试、试运行阶段执行,这类函数会根据DEBUG宏是否定义确定执行的流程。

​ 可以配合之前提到的__LINE__等使用,方便我们调试。

宏函数的变长参数:
#define func(...) __VA_ARGS__
//这种用法必须配合,printf/fprintf/sprintf系列支持变长参数的函数使用。

总体来说就是将左边宏中…的内容替换到__VA_ARGS__所在位置

#define myprintf(...) printf( __VA_ARGS__)
myprintf("123testhehe");
//123testhehe
总结

预处理阶段的主要任务为:

1、宏替换:将宏名替换为对应宏定义

2、文件包含:将#include包含的文件插入到当前文件,这样在编译阶段才能正确处理所有指令

3、条件编译:决定后续哪些代码被编译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值