目录
一.代码运行是的两种环境二.翻译环境三.预定义符号四.#define
五.#define定义宏 与函数对比六.预处置指令七.条件编译八.头文件包含的方式总结
一.代码运行是的两种环境
1.翻译环境,在这个环境中源代码被转换为可执行的机器指令。
2.执行环境,它用于实际执行代码
下面主要讲解翻译环境。
二.翻译环境
从.c 文件到 .exe 文件需要经过编译器的翻译,而翻译又分为 编译和链接两个部分
编译又分为三个部分:
1.预编译:又叫预处置,在这个部分主要完成头文件的包含,#define的交换,注释的删除;
2.编译:主要完成语法分析,词法分析,词义分析,符号汇总(符号包括全局性的变量和函数),生成汇编代码;
3.汇编:生成二进制指令,形成符号表(符号表是由符号和其地址组成的);
链接:合并段表,合并符号表(在这个阶段会发现未定义的函数)。
见下图:
三.预定义符号
__FILE__ //停止编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //假设编译器遵循ANSI C,其值为1,否则未定义
四.#define
1.define 定义宏
宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出如今stuff中。
注意 name 需与后面的括号严密相连,不可以有空格,假设有任何空白存在,参数列表就会被解释为stuff的一部分。
注意当我们定义宏的时候,不要吝啬括号!
来看下面一个例子:
#define MOD(x,y) x*y
int main()
{
int m = MOD(2+3,2);
printf("%d\n", m);
return 0;
}对初学者来说,这段代码的答案很容易被认为式10,但事实并非如此,因为宏是在预处置阶段先交换掉,然后在停止计算,所以在没有括号的情况下,交换后是这样的:2+3*2=8;所以若是想要得到10这个结果,就要加上括号,即:
#define MOD(x,y) ((x)*(y))
2.带有副作用的宏参数
我们知像是前置++ ,后置++这种的运算符是会改变操作数的值属性的,那它假设应用到#define 定义的宏中会是怎么样呢?
我们来看下面这个例子:
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
int a = 4;
int b = 6;
int m = MAX(a++, b++);
printf("m=%d\n", m);
printf("a=%d b=%d\n", a, b);
return 0;
}最后的答案会是多少呢?
首先完成宏参数的交换:((a++)>(b++)?(a++):(b++))
后置++是先使用后++,因为4<6,所以执行后面的 b++,经过前面的++,此时a=5,b=7,所以先把7赋给m,然后b++,得到b=8;
即m=7 a=5 b=8
总结: 1.#define 定义的符号需要先原封不动的交换掉,所以建议在#define 后面不加 ' ; ' ;
2.#define 定义的宏不要吝啬括号,以免呈现出其不意的结果;
3.防止使用带有副作用的运算符。
五.#define定义宏 与函数对比
六.预处置指令
所有的预处置指令都是以井号(#)开头。它必需是第一个非空字符,为了加强可读性,预处置指令应从第一列开端。下面列出了所有重要的预处置指令:
七.条件编译
可以实现将一条语句(一组语句)编译或者放弃。
常见的条件编译指令:
1.
#if 常量表达式
//...
#endif
//常量表达式由预处置器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
例:
int main()
{
#if 1 //假设这个常量表达式为真,则执行后面的语句,反之则不执行
printf("haha\n");
#endif
return 0;
}运行结果:
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式 (注意这里是 elif ,而不是else if )
//...
#else
//...
#endif
例:
#define M 10
int main()
{
#if M==5
printf("mafumafu\n");
#elif M==10
printf("Eve\n");
#elif M==7
printf("Sou\n");
#elif M==2
printf("amatsuki\n");
#else
printf("soraru");
#endif
return 0;
}运行结果:
3.嵌套指令
#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
八.头文件包含的方式
1. 双引号式 #include "test.h" :先在源文件所在目录下查找,假设该头文件未找到,编译器 就像查找库函数头文件一样在规范位置查找头文件。
假设找不到就提示编译错误。
2.尖括号式 #include <stdio.h>: 查找头文件直接去规范途径下去查找,假设找不到就提示编 译错误。
所以说库里的头文件也可以用 双引号 包含 ,但并不建议这样做,因为双引号包含没有尖括号包含的查找的快。
嵌套文件包含
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会呈现两份comm.h的内容。这样就形成了文件内容的反复。
如何防止这种问题呈现?
有两种处置方式:
1.利用条件编译指令
#ifndef __TEST_H__ //假设没有定义 TEST_H__ 则执行下一句代码 定义 __TEST_H__
#define __TEST_H__
#endif2.利用预处置指令 #pragma once
《高质量C/C++编程指南》中的两个问题
1. 头文件中的 ifndef/define/endif是干什么用的?
防止头文件的反复引用。
2. #include <filename.h> 和 #include "filename.h"有什么区别?
文件的查找战略不同。
总结
到此这篇关于C语言进阶教程之预处置的文章就介绍到这了,更多相关C语言预处置内容请搜索网站以前的文章或继续阅读下面的相关文章希望大家以后多多支持网站! |