【高级语法】预处理详解

1.预处理概述

  1. 意义
    (1)预处理就是将一些繁杂的代码(头文件、宏定义、条件编译、注释等)提前展开或清除,将核心的代码编译交给编译器来编译。
    (2)常见的预处理
/*头文件*/
#include <xxx.h>
#include "xxx.h"

/*宏定义*/
#define	xxx	yyy

/*注释*/
void func(void);	//xxxxxx

/*条件编译*/
#ifndef	xxx

#define	xxx

#endif
  1. 宏定义的预处理
    (1)宏定义被预处理后,原来的宏定义的代码消失,程序中所有被宏定义的字符被替换为原来的值;
    (2)因此在编译器的编译阶段已经不包含宏定义了;但typedef定义的代码不变,不属于预处理范围内;
  2. 头文件的预处理
    (1)头文件包含被预处理后,原来的包含头文件的代码消失,预处理会将所包含的头文件里面的内容全部展开到程序中;
    (2)#include <xxx.h>#include "xxx.h"的区别:前者是用来包含系统提供的头文件,C语言编译器(预处理器、汇编器、链接器等统称为编译器)会自动到系统指定的目录下寻找相关头文件;
    后者编译器则会先到当前目录下寻找头文件,若未找到再到系统目录下寻找该头文件,若都没有,则会报无此头文件;
    (3)编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下)
  3. 注释的预处理
    由于注释的作用是给程序员看的而不是cpu,因此编译器再预处理时会直接删掉注释来减少内存的占用;
  4. 条件编译的预处理
    (1)常见的两种条件判定分别是#ifdef/#ifndef xxx#if (xxx),二者区别:前者在预处理时会判断在#ifdef/#ifnedf xxx代码前,xxx是否已经被定义,若被定义/未定义则条件成立,执行相关的代码;
    (2)后者类似于C中的if语句,判断(xxx)中的值为truefalse,执行相关代码;
#include <stdio.h>	//1、预处理会从系统目录中寻找stdio.h文件,2、将该行代码删除,3、将stdio.h文件展开到该.c文件中
#include "test.h"	//1、预处理会先从当前目录中寻找test.h文件,若未找到则再到系统目录中寻找;

#define dp_int int *	//预处理会将后面所有代码的dp_int字符替换回int *,并将此行代码删除
typedef char* tp_char;	//预处理不会处理该行代码,也不会处理此后的tp_char字符

int main(void)
{
	int a = 0;
	d_int a, b;	//预处理会将该行代码替换为int *a, b;即d_int a替换为int *类型,将b替换为int类型:即int *a, b;
	t_char c, d;	//编译器会将c和d都替换为char *类型
	#ifndef dp_int	//预处理器会判断是否未定义dp_int
	printf("not defint d_int\n");	//若未定义则执行此行代码
	#else		//若未定义,则预处理器会直接删除这条判断相关的代码,若已经定义,则预处理器会删除上面未定义的代码,然后执行这条代码
	printf("define d_number\n");	//则执行此行代码
	#endif		//结束条件判断
	#if (a == 0)	
	printf("true\n");
	#else
	printf("false\n");
	#endif
	return 0;
}

2.宏定义详解

  1. 宏定义概述
    (1)宏定义的作用就是原封不动的替换,而且仅限于字符替换,即便替换后会导致后面的代码逻辑或语法错误,宏定义也并不关心;
    (2)宏定义中 标识符表示其后面的所有成分,即标识符后所有成分之间即便有空格也算作一个整体;
    (3)宏定义的替换可以递归替换,直到最后的成分不是宏为止;
#define n  1 0		
#define m n
int a[m] = {0};	
//预处理后
int a[1 0] = {0};	//1、后面编译器编译时必然会报语法错误,但预处理对于宏定义的处理只时原封不动替换,对于其他并不关心
					//2、尽管1和0之间有空格,但n仍表示为 ”1 0“
					//3、n替换 ”1 0“,m替换n,最终m代表 “1 0”
  1. 宏定义示例
    (1)MAX宏,求两者中最大者:注意宏定义时所有的独立元素都要加(),否则直接替换后可能会导致运算出错
#define	 max(a, b)    (((a) > (b))? (a) : (b))
int func(void)
{
	int x = 2, y = 3;
	max(x+2, 3);
	reutrn 0
}

(2)宏定义求一年中有多少秒:注意类型的转换,因为C中默认时int类型,而秒数超出int范围

#define second (365*24*60*60UL)		\\将60转为unsigned long类型
#define second (365*24*60*60)UL		\\错误,编译器无法通过

(3)谨记宏定义的含义就是完全替换

#define FUNC (((a) > (b)) ? (a) : (b))
int func(int a, int b)
{
	return a > b? a: b;
}
int main(int args, char *argv[])
{
	int a = 1, b = 3
	int c = FUNC(++a, b);
	printf("a = %d\nb = %d\nc = %d\n", a, b, c);	//输出为3 3 3
	func(++a, b);
	printf("a = %d\nb = %d\nc = %d\n", a, b, c);	//输出为2 3 2
}
  1. 带参宏与带参函数
    (1)带参宏预处理时会将宏原地展开,运行时直接在原地运行;而带参函数运行时会调用子函数,通过函数名(指针)跳转到子函数地址去执行,调用开销大,若子函数较小时,效率不如带参宏;
    (2)带参函数运行时,结果的返回值是在定义函数时就必须设定的,因此在返回结果时,编译器会自动检查返回值的类型是否正确;
    (3)带参宏计算的结果返回值不包含数据类型,预处理时仅进行字符替换,因此也不会报任何警告错误,但运行结果可能会因为数据的类型不匹配等导致结果错误;
/**********带参函数************/
#define max(a, b) (((a) > (b))? (a) : (b))
int func(int a, int b)			//由于a 和 b的类型与int 不匹配,因此编译器警告
{
	 if(a > b)
	 	 return a;
	 else
	 	 return b;
}
int main(void)
{
	 float a = 3.14;
	 float b = 6.18;
	 float c = func(a, b);		//由于形参和实参的类型 不匹配,因此编译器警告
	 int d = max(a, b);			//由于带参宏返回值不附带数据类型,因此编译器不报任何异常
	 printf("d = %f\n", d);		//此处结果是错误的		
}
  1. 宏定义来实现条件编译debug
(#define #undef #ifdef)


#define DEBUG 			//定义一个宏
#undef DEBUG 			//取消这个宏定义

#ifdef DEBUG 			//若定义了这个宏
#define DEBUG(format,...)  printf("file:"__FILE__", line:%d, "format"\n", __LINE__, ##__VA_ARGS__)	//用debug代替 print函数
#else				//若没定义
#define DEBUG(format...)		//debug就是空
#endif

int main(void)
{
	char str[] = "hello";
	DEBUG("%s", str);
}
//输出结果:file:main.c, line:16, test, hello 
  1. offsetof宏与container_of宏
    (1)offsetof宏:用来计算结构体某成员指针与结构体指针的偏移量:
#define offsetof(type, member)  (size_t)&(((type*)0)->member)

// ((type*)0):将0地址强制类型转换为指针类型,指向一个type类型的结构体,即0地址为结构体的首地址( int *0 <==> int *p = 0 //p中存放着某个int变量的地址,将2赋给p即将2赋给int变量的地址即2中存放着int变量);
//1、(((type*)0)->member):得到通过结构体指针得到member成员的空间(参数名);
//2、&(((type*)0)->member):得到member成员的地址;
//3、(size_t)&(((type*)0)->member):将member成员的地址转为int类型即偏移量
//4、因为type结构体的首地址为0,因此member成员的地址就是member成员地址的偏移量

(2)container_of宏:通过结构体某成员的指针计算结构体指针:

#define container_of(ptr, type, member) ({ \
     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
     (type *)( (char *)__mptr - offsetof(type,member) );})
     //ptr:某成员的指针;type:结构体类型;member:某成员名

//1、( ((type *)0)->member ):得到某成员在内存的空间;
//2、const typeof( ((type *)0)->member ):获取member的类型	//typeof是根据变量的值获取变量类型的类型
//3、const typeof( ((type *)0)->member ) *__mptr:定义一个名为__mptr,类型为指向member的类型的指针;
//4、const typeof( ((type *)0)->member ) *__mptr = (ptr):将member的指针赋给之前定义的__mptr变量
//5、offsetof(type,member):获取当前成员的指针相对于结构体指针的偏移;
//6、( (char *)__mptr - offsetof(type,member) ):用当前的指针地址-指针偏移 == 结构体的起始地址(此时为char *类型的地址)
//7、(type *)( (char *)__mptr - offsetof(type,member) ):将当前的char*类型地址转化为结构体type类型。
  1. 内联函数和inline关键字
    (1)内联函数就是在定义函数的前面加inline关键字,来实现该函数既可以原地展开,又可以通过编译器来检查参数和返回值的类型。
    (2)一般内联函数都是用在代码比较少的小型函数上。
int func(int a, int b)  //由于a 和 b的类型与int 不匹配,因此编译器警告
{
  if(a > b)
    return a;
  else
    return b;
}
int main(void)
{
	 float a = 3.14;
	  float b = 6.18;
	  float c = func(a, b);		//此函数运行时会在原地展开运行而非跳转,同时编译器也会报警告 
 	printf("c = %f\n",c);  
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值