预处理那些事(定义表示符,定义宏,#define)

介绍预处理之前 先铺垫一小端程序运行的小知识方便我们理解预处理

当我们一个程序运行的时候是通过很多步的

当然所有文件执行时都会有编译环境和运行环境

编译环境

编译也分为几部分

预编译 处理预定义内容

编译 语法分析 词法分析 语义分析 符号汇总

关于这个符号 相信会有人好奇 我们写的不都是代码嘛 ? 为什么会有符号呢

其实每一个函数都是符号

汇编 形成符号表 把所有的符号汇总在一起

链接合并段表 符号表的合并和符号表的重定义

执行程序

1 执行程序时 程序必须加载在内存中 有操作系统时 这一步通常由操作系统来操作

2 开始执行程序 开始调用main函数

3 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。

  程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4 程序终止  可能时意外终止也可能正常main函数结束

预定义符号

__FILE__  //进行编译的源文件
__DATE__  //文件被编译的日期
__LINE__  //当前行号
__TIME__  //文件编译的时间
__func__  //当前函数
int main()
{
       printf("文件 : %s\n函数 : %s \n行 : %d\n日期 : %s\n时间 : %s",__FILE__,__func__,__LINE__,__DATE__,__TIME__);
	return 0;
}

 #define 定义标识符

语法
#define name stuff

举几个例子

#define MAX 100
#define STD "student"
int main()
{
	int a = MAX;
	char *p=STD;
	add();
	return 0;
}

#define 定义的标识符 都是在预编译阶段完成的 并且不会占用内存 替换之后就会自动删除

我们看预编译之后的样子

值得注意的是 我在演示#define 定义标识符的时候用的都是大写并且结尾没有;

为什么都是大写呢?

为了防止我们自己写的标识符和库里重名 c语言就有了一个不成文的规矩 #define 定义时都用大写加下划线的格式

为什么后面不加;

因为#define 执行时是替换 如果后面有;  ;也会被替换

#define MAX 100;
int main()
{
	int a = 2;
	int c = 0;
	if (a == 2)
		c = MAX;//条件成立 替换为 c=100;;
	else

	return 0;
}

请注意上面这个代码就会出现问题

if后面并没有加{}默认执行1句 由于多了一个; 也就多了一句话 此时程序就会报错

所以我建议不要加上;

#define 定义宏

宏和函数其实是非常像的

#define name( parament-list ) stuff

要注意!!!

参数列表的左括号必须与name紧邻。

如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

举个例子

#define ADD(a) a*a
int main()
{
	printf("%d", ADD(10));
	return;
}

此时打印在屏幕上的就是数字100;

但其实这么写是不规范的 是眼睁睁看见自己写了一个BUG上去

比如

#define ADD(a) a*a
int main()
{
	printf("%d", ADD(9+1));
	return;
}

此时打印在屏幕上的就不是100 而是 19 

why !!!

 因为用宏的时候是替换 而上面替换就成了

#define ADD(a) a*a
int main()
{
	printf("%d", 9+1*9+1);
	return;
}

 怎么避免这种由于符号优先级引起的问题呢? 

那就是我们用宏的时候千万不要吝啬括号

#define ADD(a) ((a)*(a))
int main()
{
	printf("%d", ADD(9+1));
	return;
}

记住括号不要钱 加就完了 ! ! !

括号装甲过后 他就比较完美了

#define 替换规则

1、在调用宏的时候先对参数检查 看参数有没有由#define定义的符号 如果有 那么它(参数)先替换

2、替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

有没有感觉和函数更像了呢 但是要注意宏不能递归

看下面这段代码

void PRINT(int c)
{
	printf("=%d", c);
}
int main()
{
	int a = 15;
		PRINT(a);
		int b = 20;
		PRINT(b);
	return 0;
}

 

其实我想要的是a=15 b=20

但是我们打印只能打印数字 并不能把本体打印出来

这时候函数说  一就是一,二就是二,你这不是欺负老实人吗 ?

传进来是一个整型数据 我就打印一个整型数据

那我就耍无赖 我不管我就要两个都打印

这时宏说 我来吧 能力越大责任越大  宏带着##来了

开了一个小玩笑  接下来介绍怎么使用这个##

使用之前先接入一个小概念

int main()
{
	printf("hello""world");
}

 我们会发现字符串有自动连接的功能

恰巧#就会把参数变成一个字符串

#define PRINT(c)(printf(#c"=%d\n",c))
//              这里就变成了"c""=%d\n"
int main()
{
	int a = 15;
		PRINT(a);
		int b = 20;
		PRINT(b);
	return 0;
}

 看此时的效果

 

 此时函数就傻眼了 

宏能说什么呢?哈哈哈

说到吃两碗 只给一碗的钱就不得不说##的另外一个刁钻使用方式

#define PRINT(c,d) (c##d)
int main()
{
	int yiwan = 10;//不要介意拼音
	int liangwan = 20;//作者英文真的不好
	printf("%d",PRINT(yi,wan));//此时替换 就变成了yiwan
}

 明明2个参数 竟然合体了

这就是##的神奇之处啦

不要光看宏的优点 其实他也是有缺点的

带有副作用的宏参数

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 10;
	int b = 11;
	int c = MAX(a++, b++);
	//此时替换 为((a++)>(b++)?(a++):(b++))

	printf("%d\n", a); 
	printf("%d\n", b); 
	printf("%d\n", c);
	return 0;
}

正常情况我们预期输出的就是

 但是宏替换后

 既然宏和函数这么像 我们来对比一下吧

宏的优点

就比如上面求两个数中的较大值来说 用宏好呢还是函数好呢

其实像这种简单的计算用宏是比较好的

我们在调用函数时包括了 调用 执行 返回 3个过程

而宏是需要执行一步就可以  所以从速度上宏就占了优势

使用方式上的区别宏是可以传递任何类型的参数的

而函数只能传递规定的类型

总体来说 速度快 使用方便

宏的缺点

1.用宏的时候是一个替换过程  把一段代码插入到程序中 假如这个宏比较长 又要多次使用 就大大增加了代码量

2.宏是无法调试的  也就意味宏一旦出错很不容易去查找

3.由于比较自由 是无类型检查了 也容易出错

4.宏会因为符号优先级的问题带来一系列错误

有没有什么是宏能做到   函数做不到的呢

比如宏可以直接使用类型

#define MALLOC(num,type)(type*)malloc(num*sizeof(type))
int main()
{
	int* p = MALLOC(10,int);
	if (p == NULL)
	{
		perror("MALLOC");
		return 1;
	}
	int i;
	for (i = 0; i < 10; i++)
	{
		p[i] = i + 1;
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

 宏和函数的对比我简单做了一个表格供大家参考

 其实一些预编译指令

#undef 用来移除一个宏定义

#define MAX 100
int main()
{
	int c = MAX;
	printf("%d", c);
    #undef MAX
	printf("%d", MAX);
	return 0;
}

移除之后  第二个printf就会报错

 条件编译  使用方法和if else 极其相似

#if

#elif

#endif 配套使用

#define MAX 100
int main()
{
#if MAX==100
	printf("he he\n");
#elif MAX ==200
	printf("ha ha\n");
#endif
	return 0;
}

此时屏幕只打印hehe 而且当我们打开预编译时会发现 只有满足条件的那一条被编译了

 当然条件编译也是可以嵌套的

文件包含 也是一种预编译

我们调用头文件时 有编译器本身提供的 也有我们自己写的

使用<>和""来调用 那么这两个有什么不同?

确实是不同的 

"" 是先在本源文件搜索,如果搜索不到在去标准位置搜索

<>直接去标准位置搜索

既然文件包含也是预编译 那我们写程序时多重复几个一样的头文件呢?

会有什么后果 其实我们在重复写 编译器提供的文件时 是不会发生什么的

比如  只会编译一个stdio.h

 

假如是我们自己写的头文件 文件有100行代码 我们重复包含3个头文件

 

我们的代码预编译后就会多300行 怕不怕

那为什么编译器提供的头文件 就没有问题呢

因为编译器的头文件做了一定手脚

我们自己写的头文件 也可以做手脚

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__

这是一种比较老的方式了  但是方便理解

新的方式 

#pragma once

一句就可以

大家有没有感觉干货满满呢  可以的话给良心作者点个赞吧

 

 

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值