第8章-编译预处理和动态存储分配.ppt
第8章 编译预处理和动态存储分配 8.1 编译预处理8.2 宏定义 8.3 文件包含处理 8.4 动态存储分配 8.1 编译预处理 C语言允许在源程序中加入一些“预处理命令”(preprocessing directive),以改进程序设计环境,提高编程效率。这些预处理指令是由C标准建议的,但是它不是C语言本身的组成部分,不能用C编译系统直接对它们进行编译(因为编译程序不能识别它们)。所谓“编译预处理”就是在C编译程序对C源程序进行编译前,由编译预处理程序对这些编译预处理命令行进行处理的过程。在预处理阶段,预处理器把程序中的注释全部删除;对预处理指令进行处理,如把#include指令指定的头文件(如stdio.h)的内容复制到#include指令处;对#define指令,进行指定的字符替换(如将程序中的符号常量用指定的字符串代替),同时删去预处理指令。8.1 编译预处理经过预处理后的程序不再包括预处理指令了,最后再由编译程序对预处理后的源程序进行实际的编译处理,得到可供执行的目标代码。C语言与其他高级语言的一个重要区别是可以使用预处理指令和具有预处理的功能。C语言提供的预处理功能常用的主要有以下3种:1、宏定义;2、文件包含;3、条件编译。这些预处理命令组成的预处理命令行必须在一行的开头以”#”号开始,每行的末尾不得用“;”号结束,以区别于C语句、定义和说明语句。这些命令行的语法与C语言中其他部分的语法无关。根据需要,命令行可以出现在程序的任何一行的开始部位,其作用一直持续到源文件的末尾。8.2 宏定义 8.2.1 不带参数的宏定义8.2.2 带参数的宏定义 8.2.3 终止宏定义 8.2.1 不带参数的宏定义 1 不带参数的宏定义命令行形式格式:#define 宏名 替换文本或者:#define 宏名在define、宏名和宏替换文本之间用空格隔开。例如:#defineSIZE 100以上标识符SIZE称为“宏名”,是用户定义的标识符,因此,不得与程序中的其他名字相同。在编译时,在此命令行之后,预处理程序对源程序中的所有名为SIZE的标识符用100三个字符来替换,这个替换过程称为“宏替换”。但要注意:不能认为“SIZE等于整数100”。#define命令行可以不包含“替换文本”,这种情况下仅说明标识符“被定义”。8.2.1 不带参数的宏定义2 替换文本中可以包含已定义过的宏名例8.1 计算圆面积。#include#define PI 3.1415926#define R 3.0#define S PI*R*R/*S的宏定义使用了前面的PI和R宏定义*/int main()printf(“圆的面积=%f”,S);return 0;运行结果:圆的面积=28.274333分析:该例中既有宏定义,又有宏定义的多重替换,这样求圆的面积,只需将宏名S进行展开后计算,输出即可。8.2.1 不带参数的宏定义3 当宏定义在一行中写不下,需要在下一行继续时,只需在最后一个字符后紧接着加一个反斜线“”。例如:#defineLEAP_YEARyear%4=0&year%100!=0|year%400=0第一列如果在“”前或在下一行的开头留有许多空格,则在宏替换时也将加入这些空格。4 同一个宏名不能重复定义,除非两个宏定义命令行完全一致。5 替换文本不能替换双引号中与宏名相同的字符串。例8.2 宏名相同的字符串不能替换。#defineBOOK“The Red and The Black”int main()printf(“%sn”,”BOOK”);return 0;运行结果:BOOK8.2.1 不带参数的宏定义6 替换文本并不替换用户标识符中的成分。例如,宏名YES,不会替换标识符YESORNO中的YES。7 用作宏名的标识符通常用大写字母表示,这并不是语法规定,只是一种习惯,以便与程序中的其他标识符相区别。8 在C程序中,宏定义的定义位置一般写在程序的开头。返回8.2.2 带参数的宏定义 1 带参数的宏定义命令行形式如下:格式:#define宏名(形参表)替换文本如果定义带参数的宏,在对源程序进行预处理时,将程序中出现宏名的地方均用替换文本替换,并用实参代替替换文本中的形参。例8.3 编写程序,使用带参数的宏定义。#include#define MAX(a,b)ab?a:b/*定义带参数的宏 MAX*/#define SQR(c)c*c/*定义带参数的宏 SQR*/int main()int x=3,y=4;x=MAX(x,y);y=SQR(x);printf(“x=%d,y=%dn”,x,y);return 0;运行结果:x=4,y=16对于带参的宏定义有以下问题需要说明:2.带参宏定义中,宏名和形参表之间不能有空格出现。例如把:#define MAX(a,b)(ab)?a:b写为:#define MAX (a,b)(ab)?a:b将被认为是无参宏定义,宏名MAX代表字符串(a,b)(ab)?a:b。宏展开时,宏调用语句:max=MAX(x,y);将变为:max=(a,b)(ab)?a:b(x,y);这显然是错误的。8.2.2 带参数的宏定义3.在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用它们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。4.在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。例8.4 宏调用实参为表达式。#define SQ(y)(y)*(y)main()int a,sq;printf(input a number:);scanf(%d,&a);sq=SQ(a+1);printf(sq=%dn,sq);运行结果:input a number:3sq=16分析:上例中第一行为宏定义,形参为y。程序第七行宏调用中实参为a+1,是一个表达式,在宏展开时,用a+1代换y,再用(y)*(y)代换SQ,得到如下语句:sq=(a+1)*(a+1);这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再赋予形参。而宏代换中对实参表达式不作计算直接地照原样代换。8.2.2 带参数的宏定义5.在宏定义中,字符串内的形参通常要用括号括起来以避免出错。在上例中的宏定义中(y)*(y)表达式的y都用括号括起来,因此结果是正确的。如果去掉括号,把程序改为以下形式:例8.5#define SQ(y)y*ymain()int a,sq;printf(input a number:);scanf(%d,&a);sq=SQ(a+1);printf(sq=%dn,sq);运行结果:input a number:3sq=7分析:同样输入3,但结果却是不一样的。问题在哪里呢?这是由于代换只作符号代换而不作其它处理而造成的。宏代换后将得到以下语句:sq=a+1*a+1;由于a为3故sq的值为7。这显然与题意相违,因此参数两边的括号是不能少的。即使在参数两边加括号还是不够的,请看下面程序:8.2.2 带参数的宏定义例8.6#define SQ(y)(y)*(y)main()int a,sq;printf(input a number:);scanf(%d,&a);sq=160/SQ(a+1);printf(sq=%dn,sq);运行结果:input a number:3sq=160分析:本程序与前例相比,只把宏调用语句改为:sq=160/SQ(a+1);运行本程序如输入值仍为3时,希望结果为10。为什么会得这样的结果呢?分析宏调用语句,在宏代换之后变为:sq=160/(a+1)*(a+1);a为3时,由于“/”和“*”运算符优先级和结合性相同,则先作160/(3+1)得40,再作40*(3+1)最后得160。为了得到正确答案应在宏定义中的整个字符串外加括号,程序修改如例8.7 8.2.2 带参数的宏定义6 带参的宏和带参函数很相似,但有本质上的不同,除上面已谈到的各点外,把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。例8.8main()int i=1;while(i=5)printf(%dn,SQ(i+);SQ(int y)return(y)*(y);例8.9#define SQ(y)(y)*(y)main()int i=1;while(i=5)printf(%dn,SQ(i+);例8.8运行结果:1491625例8.9运行结果:19258.2.2 带参数的宏定义7 宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。看下面的例子。例8.10#define SSSV(s1,s2,s3,v)s1=l*w;s2=l*h;s3=w*h;v=w*l*h;main()int l=3,w=4,h=5,sa,sb,sc,vv;SSSV(sa,sb,sc,vv);printf(sa=%dnsb=%dnsc=%dnvv=%dn,sa,sb,sc,vv);运行结果:sa=12sb=15sc=20vv=60分析:程序第一行为宏定义,用宏名SSSV表示4个赋值语句,4 个形参分别为4个赋值符左部的变量。在宏调用时,把4个语句展开并用实参代替形参。使计算结果送入实参之中。返回8.2.3 终止宏定义 可以用#undef提前终止宏定义的作用域。例如:#define PI 3.14main()#undef PI以上PI的作用域从#define PI 3.14命令行开始,到#undef PI命令行结束。从#undef以后PI变成无定义,不再代表3.14了。8.3 文件包含处理 所谓文件包含,是指在一个文件中,去包含另一个文件的全部内容。C语言用#include命令行来实现文件包含的功能。格式:#include“文件名”或#include 在预编译时,预编译程序将用指定文件中的内容来替换此命令行。如果文件名用双引号括起来,系统先在源程序所在的目录内查找指定的包含文件,如果找不到,再按照系统指定的标准方式到有关目录中去寻找;如果文件名用尖括号括起来,系统将直接按照系统指定的标准方式到有关目录中寻找。说明:1 包含文件的#include命令行通常应书写在所用源程序文件的开头,故有时也把包含文件称作“头文件“。头文件名可以由用户指定,其后缀不一定用”.h”。2 包含文件中,一般包含有一些公用的#define命令行、外部说明或对(库)函数的原型说明。例如stdio.h就是这样的头文件。3 当包含文件修改后,对包含该文件的源程序必须重新进行编译连接。4 在一个程序中,允许有任意多个#include命令行。5 在包含文件中还可以包含其他文件。8.4 动态存储分配 8.4.1 malloc函数和free函数 8.4.2 calloc函数 8.4.1 malloc函数和free函数1 malloc函数格式:(类型说明符*)malloc(size)功能:在内存的动态存储区中分配一块长度为size字节的连续区域。函数的返回值为该区域的首地址。“类型说明符”表示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。假设short int型数据占2个字节,float型数据占4字节存储单元,则以下程序段将使pi指向一个short int类型的存储单元,使pf指向一个float类型的存储单元:short int*pi;float*pf;pi=(short*)malloc(2);pf=(float*)malloc(4);由于在ANSI C中malloc函数返回的指针为void*(无值型),故在调用函数时,必须利用强制类型转换将其转成所需的类型。上面的程序段中,调用malloc函数时括号中的*号不可少,否则就转换成普通变量类型而不是指针类型了。在动态申请存储空间时,若不能确定数据类型所占字节数,可以使用sizeof运算符来求得。例如:pi=(int*)malloc(sizeof(int);pf=(float*)malloc(sizeof(float);这是一种常用的形式。此时将由系统来计算指定类型的字节数,采用这种形式将有利于程序的移植。8.4.1 malloc函数和free函数2 free函数格式:free(void*ptr);功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。例8.11分配一块区域,输入一个学生数据。main()struct stu int num;char*name;char sex;float score;*ps;ps=(struct stu*)malloc(sizeof(struct stu);ps-num=102;ps-name=Zhang ping;ps-sex=M;ps-score=62.5;printf(Number=%dnName=%sn,ps-num,ps-name);printf(Sex=%cnScore=%fn,ps-sex,ps-score);free(ps);运行结果:Number=102Name=Zhang pingSex=MScore=62.500000 返回8.4.2 calloc函数 calloc 也用于分配内存空间。格式:(类型说明符*)calloc(n,size)功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。要求n和size的类型都为unsigned int。通过调用calloc函数所分配的存储单元,系统自动置初值0。例如:char*ps;ps=(char*)calloc(10,sizeof(char);以上函数调用语句开辟了10个连续的char类型的存储单元,由ps指向存储单元的首地址。每个存储单元可以存放一个字符。例如:ps=(struct stu*)calloc(2,sizeof(struct stu);其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。显然,使用calloc函数动态开辟的存储单元相当于开辟了一个一维数组。函数的第一个参数决定了一维数组的大小;第二个参数决定了数组元素的类型。函数的返回值就是数组的首地址。使用calloc函数开辟的动态存储单元,同样可以用free函数释放。其调用形式与8.4.1节中介绍的相同。