目录
1.程序的翻译环境和执行环境
2.详解编译+链接
2.1 翻译环境
2.2 编译本身也分为几个阶段:
2.3 运行环境
3.预处理详解
3.1预定符号
3.2 #define
3.3 #undef
3.4 命令行定义
3.5 条件编译
3.6 文件包含
了解重点:
在ANSI C的任何一种实现中,存在两个不同的环境
编译 | 链接 | ||
预编译阶段 | 编译 | 汇编 | 链接 |
1.头文件#include 2.define定义符号、宏替换 | 语法分析 语义分析 符号汇总 | 将汇编代码转 换成二进制指令 | 1.合并段表 2.符号表的合并和 符号表的重定位 |
程序执行的过程:
这些预定义符号都是语言内置的
举个例子:
#include
int main()
{printf("%s\n",__FILE__);printf("%d\n",__LINE__);printf("%s\n",__DATE__);printf("%s\n",__TIME__);return 0;
}
3.2.1 #define 定义标识符
#define M 100
int main()
{int m = M;printf("%d\n",m);return 0;
}
3.2.2 #define 定义宏
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3));return 0;
}
再举例:乍一看,你可能觉得这段代码将打印16这个值
却打印出来7,为什么呢?
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3+1)); //这里不会先计算3+1,而是直接替换printf("%d\n",3+1*3+1);//所以输出7return 0;
}
所以写成以下代码:
#define SQUARE(x) ((x)*(x))//这里不会先计算3+1,因为添加了括号
int main()
{printf("%d\n",SQUARE(3+1)); printf("%d\n",(3+1)*(3+1));//所以输出16return 0;
}
3.2.3 #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
注意:
3.2.4 #和##
如何把参数插入到字符串中?
首先我们看看这样的代码:
写一个宏:要求:
当输入a时候输出:the value of a is 10
当输入b时候输出the value of b is 20
当输入c时候输出 the value of c is 30
#define PRINT(x) printf("the value of "#x" is %d\n",x);
int main()
{int a = 10;PRINT(a);int b = 20;PRINT(b);int c = 30;PRINT(c);return 0;
}
再完整一点
#define PRINT(x,format) printf("the value of "#x" is "format"\n",x);
int main()
{int a = 10;PRINT(a,"%d");int b = 20;PRINT(b,"%d");float c = 5.5f;PRINT(c,"%f");return 0;
}
##用来拼接字符
#define CAT(x,y) x##y
int main()
{int class101 = 300;printf("%d\n",CAT(class,101));//这里相当于printf("%d\n",class101);return 0;
}
3.2.5 带副作用的宏参数
x+1; //不带副作用
x++; //带有副作用
例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5;int b = 8;int m = MAX(a++,b++);printf("%d %d\n",a,b); //输出6 10//在宏中发生了变化return 0;
}
3.2.6 宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中越出较大的一个。
#define MAX(x,y) ((x)>(y)?(x):(y))
那为什么不用函数来完成这个任务?
【原因有二:】
1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
【当然和宏相比函数也有劣势的地方:】
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没法调试的。
3.宏由于类型无关,也就不够严谨。
4.宏可能会带来运算符优先级的问题,导致程容易出现错 。
【宏和函数额对比】
属性 | #define定义宏 | 函数 |
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
【命名约定】
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者那我们平时的一个习惯是:
这条指令用于移除一个宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
如下:没有定义PRINT,就不会打印hehe
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
如下:定义过了PRINT,就会打印hehe
#define PRINT
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
3.6.1 文件被包含式:
3.6.2 套文件包含
C语言初识(1) | C简介 | 主函数 | 数据类型
C语言初识(2) | 变量和常量
C语言初识(3) | 字符串 | 转译字符
C语言初识(4) | 顺序 | 选择 | 循环
C语言初识(5) | 函数 | 数组 | 操作符
C语言初识(6) | 关键字
C语言初识(7) | 指针| 结构体 | define
C语言初阶(8) | 选择结构 | if_else | switch
C语言初阶(9) | while | break | continue | getchar
C语言初阶(10) | 初识for循环 | 入门