一、程序如何从代码变成可执行程序
二、预编译详解
1.一些语言内置的预编译符号
//__FILE__ 进行编译的源文件
//__LINE__ 文件当前的行号
//__DATE__ 文件被编译的日期
//__TIME__ 文件被编译的时间
int main()
{
printf("%s %s %s line=%d", __FILE__, __DATE__, __TIME__, __LINE__);
return 0;
}
2.#define的使用
( 1 ) 可以定义标识符(简单来说就是用中间的标识符代替后面的代码)
#define MAX 100
#define forever for(;;)
#define PRINTF printf("%s %s %s %d", \
__FILE__, __DATE__, \
__TIME__, __LINE__)
//注意这里的\是续航符,当代码过长时,需要换行,就可以写\,后面必须紧跟换行
( 2 ) 可以用来定义宏(#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏)
举个例子:
#define ADD(x,y) x+y
int main()
{
printf("%d",ADD(1,1));
return 0;
}
看完上面的代码,我们再来看看下面这段代码的结果
#define ADD(x,y) x+y
int main()
{
printf("%d",5*ADD(1,1));//猜猜结果是啥?
return 0;
}
我们绝大多数人都会认为结果是5*(1+1)=10,但真的会有这么简单吗?
下面我们揭晓答案
那么这个结果是这么得到的呢?
我们将计算顺序变化一下,就会发现5*1+1不就是6吗?可是为什么会有这样的结果?
其实#define在预编译后,代码为
int main()
{
printf("%d",5*1+1);
return 0;
}
所以其实#define的功能仅仅只是替换,而不管优先级的问题,我们在写代码时,不要吝啬括号,写法如下
#define ADD(x,y) ((x)+(y))
int main()
{
printf("%d",5*ADD(1,1));
return 0;
}
如果还是不懂,可以看看下面#define的替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
三、#和##(较为少见,认识就行)
1.#
把一个宏参数变成对应的字符串
int main()
{
int a = 10;
printf("the value of a is %d\n", a);
int b = 20;
printf("the value of b is %d\n", b);
float c = 3.14f;
printf("the value of c is %f\n", c);
return 0;
}
问:如何将上面代码printf("the value of __ is %__")这种固定的写法用一种简单的方式实现?
要解决这个问题,我们要了解printf这个函数的一个特点
先来看看下面两行代码输出结果一样吗?
printf("hello world\n");
printf("hello ""world\n");
通过上诉代码,我们知道printf可以将多个字符串连起来输出,所以我们可以将printf("the value of __ is %__")中的字符串分开来考虑,下面我们看看代码
#define PRINTF(FORMAT, VALUE) \
printf("the value of "#VALUE" is "FORMAT,VALUE)
int main()
{
int a = 10;
PRINTF("%d\n", a);
int b = 20;
PRINTF("%d\n", b);
float c = 3.14f;
PRINTF("%f\n", c);
return 0;
}
2.##
可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
#define ADD(x,y) x##y
int ClassMax = 109;
int Class109 = 110;
int main()
{
int a = 109;
printf("%d\n", ADD(Class, 109));
//printf("%d\n", ADD(Class, a));错误写法---因为没有标识符Classa
printf("%d\n", ADD(Class, Max));
return 0;
}
四、宏和函数
宏和函数在一定程度上很相似,下面讲一下他们之间的优劣
优点:
- 宏比函数的运行效率更快(函数有创建栈帧,调用和返回的步骤,而宏仅仅是替换文本)
- 宏的参数没有类型,比较灵活(甚至还可以传类型,而函数不行)
缺点:
- 宏没有类型限制,不严谨
- 如果宏较大,那么代码就很变得冗余,增加了代码的长度
- 宏没法调试(宏在预编译的时候就会实现文本的替换,但我们无法观察到替换后的代码)
- 宏不考虑优先级的问题,容易出错
写法上的规范:宏名一般全部大写,函数名一般不是全部大写
五、#undef和条件编译
1、#undef
可以移除#define定义的常量,也可以移除宏
#define MAX 1
#define ADD(x,y) ((x)+(y))
int main()
{
printf("%d",MAX);//可以打印
#undef MAX
printf("%d",MAX);//不能打印
printf("%d", ADD(1, 1));//可以打印
#undef ADD
printf("%d", ADD(1, 1));//不能打印
return 0;
}
2、条件编译
#if (常量表达式)
//...
#elif (常量表达式)
//...
#else
//...
#endif
//如果定义了symbol
#if defined(symbol)
#ifdef symbol
#endif
//如果没定义symbol
#if !defined(symbol)
#ifndef symbol
#endif
//嵌套使用
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
六、头文件的包含
#include<stdio.h>
//查找头文件直接去标准路径下去查找
#include"test.h"
//先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
//准位置查找头文件。
//为了防止头文件的嵌套包含
//需要在头文件开头加上
#pragma once
//内容
//或
#ifndef __TEST_H__
#define __TEST_H__
//内容
#endif