黑马程序员——零基础学习iOS开发——09 预处理指令

来源:互联网 发布:数据对比分析方法 编辑:程序博客网 时间:2024/05/22 02:13

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------


一、预处理指令简介

1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译

2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不用分号

3.预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件

4.C语言提供的预处理指令主要有:宏定义、文件包含、条件编译


二、预处理指令——宏定义

1.不带参数的宏定义

1)一般形式

#define 宏名字符串

比如#define ABC10


右边的字符串也可以省略,比如#define ABC


2)作用

它的作用是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常用来定义常量。

接下来写个程序根据圆的半径计算周长

#include <stdio.h>  // 源程序中所有的宏名PI在编译预处理的时候都会被3.14所代替 #define PI 3.14  // 根据圆的半径计radius算周长 float girth(float radius) {     return 2 * PI *radius; }  int main () {     float g = girth(2);          printf("周长为:%f", g);     return 0; }

在第4行定义了一个叫PI的宏,在编译预处理之后,第8行中的2 * PI *radius就会变成2 *3.14 * radius


3)使用习惯与注意

1>宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误。

2>对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作。

3>在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查。

<span style="font-size:12px;">#define I 100int main (){    // 在做编译预处理的时候,不管语法对不对,第5行的I都会被替换为100。不过在编译的时候就会报第4行的错。    int i[3] = I;    return 0;}</span>


4)宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令

#define PI 3.14#undef PI// PI这个宏在第1行到第8行之间是有效的,第8行后就无效了


5定义一个宏时可以引用已经定义的宏名

#define R  3.0#define PI 3.14#define L  2*PI*R#define S  PI*R*R

2.带参数的宏定义

1)一般形式

#define 宏名(参数列表)字符串

2)作用

在编译预处理时,将源程序中所有宏名替换成字符串,并且将字符串中的参数宏名右边参数列表中的参数替换

#include <stdio.h>#define average(a, b) (a+b)/2int main (){    int a = average(10, 4);        printf("平均值:%d", a);    return 0;}

3行中定义了一个带有2个参数的宏average,第7行其实会被替换成:int a = (10 +4)/2;,输出结果为:。是不是感觉这个宏有点像函数呢?


3.使用注意

1)宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串

#define average (a, b) (a+b)/2int main (){    int a = average(10, 4);    return 0;}

2)带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数,以免导致计算结果不是我们想要的结果。

如果定义宏的时候不用小括号括住参数:

#include <stdio.h>#define D(a) 2*aint main (){    // 由于第三行字符串中a没有加括号,D(3+4)将被替换成 2*3+4,而我们想要的却是2*(3+4)    int b = D(3+4);        printf("%d", b);    return 0;}

4.与函数的区别

从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:

1)宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题

2)函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率


三、预处理指令——条件编译


在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。

1.基本用法

#if 条件1...code1...// 代码段1#elif 条件2...code2...// 代码段2#else...code3...// 代码段3#endif


1>如果条件1成立,那么编译器就会把#if #elif之间的code1代码编译进去(注意:是编译进去,不是执行,很平时用的if-else是不一样的)

2>如果条件1不成立、条件2成立,那么编译器就会把#elif #else之间的code2代码编译进去

3>如果条件12都不成立,那么编译器就会把#else #endif之间的code3编译进去

4>注意,条件编译结束后,要在最后面加一个#endif,不然后果很严重(自己思考一下后果)

5> #if #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义

2.举个例子

#include <stdio.h>#define MAX 11int main (){#if MAX == 0    printf("MAX是0");#elif MAX > 0    printf("MAX大于0");#else    printf("MAX小于0");#endif    return 0;}

在第3行定义了一个宏MAX,当然在开发中这个MAX可能被定义在其他头文件中,现在只是为了方便演示,就写到main函数上面了。注意第7到第13行的条件编译语句。

由于MAX11,所以#elif的条件成立,第10行代码将会被编译进去,其实编译预处理后的代码是这样的:

/*stdio.h文件中的内容将会代替#include <stdio.h>的位置*/  int main () {     printf("MAX大于0");     return 0; }

3.其他用法

1)#if defined()#if !defined()的用法


// #if 和 #elif后面的条件不仅仅可以用来判断宏的值,还可以判断是否定义过某个宏。比如:#if defined(MAX)     ...code...#endif// 如果前面已经定义过MAX这个宏,就将code编译进去。它不会管MAX的值是多少,只要定义过MAX,条件就成立。// 条件也可以取反:#if !defined(MAX)     ...code...#endif// 如果前面没有定义过MAX这个宏,就将code编译进去。

tips:#endif 很重要,如果不写后果很严重。是什么后果?自己想一下吧。

2)#ifdef#ifndef的使用

#ifdef的使用和#if defined()的用法基本一致

#ifdef MAX     ...code...#endif// 如果前面已经定义过MAX这个宏,就将code编译进去。

#ifndef又和#if !defined()的用法基本一致

#ifndef MAX     ...code...#endif// 如果前面没有定义过MAX这个宏,就将code编译进去。

四、预处理指令——文件包含

1.基本概念

其实我们早就有接触文件包含这个指令了,就是#include,它可以将一个文件的全部内容拷贝另一个文件中。

2.一般形式

1)第一种形式:#include <文件名>

直接到C语言库函数头文件所在的目录中寻找文件


2)第二种形式:#include "文件名"

系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找


3.使用注意

1)#include令允许嵌套包含,比如a.h包含b.hb.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.hb.h包含 a.h

2)使用#include指令可能导致多次包含同一个头文件,降低编译效率

例如:one.h文件中声明了one函数;two.h文件中文件包含了one.h,并且声明了two函数;
现在main函数要调用one和two这两个函数,但写main函数的人不一定知道two.h中已经包含了one.h,所以我们很可能会同时包含one.h和two.h。
   


预编译过后,main.c文件中的代码是这样的:

#include <stdio.h>// 第3行是原来的 #include "one.h"void one();// 第5、6行是原来的 #include "two.h"void one();void two();int main(){    one();    two();        return 0;}
这样的结果是,one函数被声明了两次,虽然编译器不会报错,但是降低了编译效率。

那么,如何解决这个问题呢?

一般,我们会这样写头文件的内容:



这样写头文件后,main.c文件的代码效果相当于:


能想到这样写之后的效果么?当预编译时,由于第4行代码做过了#define _ONE_H_ 这个操作,13至18行代码就不会被编译,这样就达到了我们想要的效果:即便two.h中嵌套了one.h,也不会导致one.h被重复包含。

tips:

一般,用于防止头文件被重复包含所写的#ifdef、#define 后面的字符串,我们保持与本文件名一致,前后加下划线。比如上面的 #define _ONE_H_


小结:

1.带参数的宏比函数具有更高的执行效率。

2.宏定义仅仅是文字替换,为了避免计算结果不正确,宏的参数要加上括号。

3.条件编译要记住写 #endif ,不然后果很严重。

4.可以嵌套包含(b.h包含a.h,c.h包含b.h),但是不能递归包含(a.h包含b.h,b.h包含a.h)。

4.为了防止我们写的头文件被重复包含,注意头文件的书写规范

#ifndef  _XXX_H_

#define _XXX_H_

…code…

…code…

#endif


To be continue……


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 大学体育选修课挂了怎么办 当天贴的砖踩了怎么办 刚贴的瓷砖踩了怎么办 长胶底板太轻怎么办 乒乓球拍胶皮不粘了怎么办 乒乓球拍子胶片太滑怎么办 新买的包黏黏的怎么办 卫星锅收不到台怎么办 养殖厂被卫星拍住怎么办 中六卫星无信号怎么办 晒出成片的斑怎么办 太阳晒出胳膊上长斑怎么办 宇航员在太空死后怎么办 太阳暴晒起的斑怎么办 太阳晒出来的斑怎么办 被认定D级危房怎么办 突然发现两个关系遥远怎么办 如果没有地球人类会怎么办 小锅盖被屏蔽了怎么办 美的冰箱故障通讯不合格怎么办 美的冰箱通讯不合格怎么办 文明6金币降到0怎么办 紫癜肾炎长期尿潜血怎么办 肾移植后血压高怎么办 尿道长了个肿瘤怎么办 吃了有病的鹅怎么办 狗狗得了乳腺瘤怎么办 孕28周还是臀位怎么办 怀孕五个月胎位不正怎么办 33周了胎位不正怎么办 足月胎儿不足5斤怎么办 绒癌观察期怀孕怎么办 宝宝囱门闭合晚怎么办 慢性硬脑膜下血肿复发怎么办 佝偻病导致囟门晚闭怎么办 儿童液体补多了怎么办 脑脊液鼻漏3年了怎么办 结石掉到膀胱里怎么办 肾结石引起的腰疼怎么办 肾结石小但很疼怎么办 狗狗得了尿结石怎么办