概述
大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译。由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运行时进行操作效率高。在C语言中包括三类预处理指令,今天将一一介绍:
宏定义 条件编译 文件包含
宏定义
对于程序中经常用到的一些常量或者简短的函数我们通常使用宏定义来处理,这样做的好处是对于程序中所有的配置我们可以统一在宏定义中进行管理,而且由于宏定义是在程序编译之前进行替换相比定义成全局变量或函数效率更高。
// // main.c // Pretreatment // // Created by Kenshin Cui on 14-6-28. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> #define PI 3.14 //宏定义一般大写 #define R 10 #define S 2*PI*R //在另一个宏里面引用了上面的宏 int main(int argc, const char * argv[]) { float r=10.5; double area=PI*r*r; printf("area=%.2f\n",area); double a=S; printf("a=%.2f\n",a); printf("PI=3.14\n");//注意输出结果不是3.14=3.14而是PI=3.14,字符串中的PI并不会被替换 #undef PI //强制终止宏定义,否则它的范围一直到文件结束 int PI=3.1415926; double area2=PI*r*r; printf("area2=%.2f\n",area2); return 0; }
宏定义实际的操作就是在预处理时进行对应替换,这个阶段不管语法是否正确,而且对于字符串中出现的宏名不会进行替换。宏定义的功能事实上是非常强大的,除了简单的常量替换还可以传入参数:
// // 1.2.c // Pretreatment // // Created by Kenshin Cui on 14-7-17. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> #define SUM(a,b) a+b #define SUB(a,b) (a-b) #define MUL (a,b) (a*b) //这么定义是错误的,预处理器会认为宏名为”MUL“,替换内容为”(a,b) (a*b)“ int main(int argc, const char * argv[]) { int a=2,b=3,c,d; c=SUM(a, b); printf("c=%d\n",c); //结果:c=5 d=SUM(a, b)*2; printf("d=%d\n"); //结果:8,为什么不是10呢?因为替换后:d=a+b*2也就是2+3*2=8 int e=SUB(b, a)*2; printf("(b-a)*2=%d\n",e); //结果:2,如果SUB定义时不加括号这里应该是-1 return 0; }
上面我们可以看出带参数的宏功能很强大,有点类似于函数,同函数不同的是它只是简单的替换,不涉及存储空间分配,参数、返回值等问题,但是由于它在预处理阶段展开,所以一般效率较高。使用带参数的宏需要注意的就是结果最好用括号括起来否则很容易出现问题(在上面的SUM例子中我们应该已经看到了);还有一点就是带参数的宏定义时名称和参数之间不要有空格。
条件编译
条件编译其实就是在编译之前预处理器根据预处理指令判断对应的条件,如果条件满足就将对应的代码编译进去,否则代码就根本不进入编译环节(相当于根本就没有这段代码)。
// // main.c // Pretreatment // // Created by Kenshin Cui on 14-06-28. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> #define COUNT 1 int main(int argc, const char * argv[]) { //判断是否定义了 COUNT 宏 #if defined(COUNT) //等价于:#ifdef COUNT,相反如果判断没有定义过则可以通过#if !defined(COUNT)或者#ifndef COUNT printf("COUNT defined\n"); #endif //判断宏定义COUNT是否都与1 #if COUNT==1 showMessage("hello,world!\n"); #else say(); #endif return 0; }
文件包含
文件包含指令#include在前面也多次使用过,这里再次强调一下。首先使用#include“xxx”包含和使用#include <xxx>包含的不同之处就是使用<>包含时,预处理器会搜索C函数库头文件路径下的文件,而使用“”包含时首先搜索程序所在目录,其次搜索系统Path定义目录,如果还是找不到才会搜索C函数库头文件所在目录。
另外在使用#include的时候我们需要注意包含文件的时候是不能递归包含的,例如a.h文件包含b.h,而b.h就不能再包含a.h了;还有就是重复包含虽然是允许的但是这会降低编译性能,不妨看一下下面的例子:
上面有三段代码,在main.c和person.h中都包含了message.h而main.c自身又包含了person.h,这样程序在预处理阶段会对包含内容进行替换,替换后mian.c中包含了两个#include “message.h”虽然没有报错,但这会影响编译的性能,正确的做法应该是这样的:
其实就是用宏定义判断一个宏是否定义了,如果没有定义则会定义这个宏,这样以来如果已经包含过则这个宏定义肯定已经定义过了,即使再包含也不会重新定义了,下面的代码也就不会包含进去。
本文向大家介绍IOS开发之路--C语言基础知识,包括了IOS开发之路--C语言基础知识的使用技巧和注意事项,需要的朋友参考一下 概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift 这么看下去还有大量的内容需要持续补充,但是今天我们从最基础的C语言开始,
本文向大家介绍IOS开发之路--C语言构造类型,包括了IOS开发之路--C语言构造类型的使用技巧和注意事项,需要的朋友参考一下 概述 在第一节中我们就提到C语言的构造类型,分为:数组、结构体、枚举、共用体,当然前面数组的内容已经说了很多了,这一节将会重点说一下其他三种类型。 结构体 枚举 共用体 结构体 数组中存储的是一系列相同的数据类型,那么如果想让一个变量存储不同的数据类型就要使用结构体,结构
本文向大家介绍IOS开发之路--C语言数组和字符串,包括了IOS开发之路--C语言数组和字符串的使用技巧和注意事项,需要的朋友参考一下 概览 数组在C语言中有着特殊的地位,它有很多特性,例如它的存储是连续的,数组的名称就是数组的地址等。而在C语言中是没有String类型的,那么如果要表示一个字符串,就必须使用字符串数组。今天主要就介绍如下三个方面: 一维数组 多维数组 字符串 一维数组 一维数组操
本文向大家介绍IOS开发之路--C语言存储方式和作用域,包括了IOS开发之路--C语言存储方式和作用域的使用技巧和注意事项,需要的朋友参考一下 概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式、作用范围和销毁时间才可能正确的使用这门语言。今天将着重介绍C语言中变量作用范围、存储方式、生命周期、作用域和可访问性。 变量作用范围 存储方式 可访问性 变量作
入门书籍 C语言开发入门教程 视频链接:https://pan.baidu.com/s/1c1Yjr80 密码: idtn 答案链接:http://pan.baidu.com/s/1hsbk2tm 密码:g81c 源码链接:http://pan.baidu.com/s/1jHL7up4 密码:hafs 题库链接:http://pan.baidu.com/s/1c17604g 密码:vqfw 方案链
预处理指令是以 号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将 在编译器进行编译之前对源代码做某些转换。 下面是本章涉及到的部分预处理指令: 指令 说明 # 空指令,无任何效果 #include 包含一个源代码文件 #define 定义宏 #undef 取消已定义的宏 #i