配色: 字号:
《C语言程序设计》第8章 编译预处理和动态存储分配 写字字帖
2023-05-24 | 阅:  转:  |  分享 
  
第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、宏名和宏替换文本之间
用空格隔开。例如:#define SIZE 100以上标识符SIZE称为“宏名”,是用户定义的标识符,因此,不得与程序中的其他名字
相同。在编译时,在此命令行之后,预处理程序对源程序中的所有名为SIZE的标识符用100三个字符来替换,这个替换过程称为“宏替换”。
但要注意:不能认为“SIZE等于整数100”。#define命令行可以不包含“替换文本”,这种情况下仅说明标识符“被定义”。 8.
2.1 不带参数的宏定义2 替换文本中可以包含已定义过的宏名例8.1 计算圆面积。#include #defin
e PI 3.1415926#define R 3.0#define S PIRR /S的宏定义使用了前面的PI和R宏定义
/int main(){ printf(“圆的面积=%f”,S); return 0;}运行结果:圆的面积=28.274333分
析:该例中既有宏定义,又有宏定义的多重替换,这样求圆的面积,只需将宏名S进行展开后计算,输出即可。 8.2.1 不带参数的宏定义3
当宏定义在一行中写不下,需要在下一行继续时,只需在最后一个字符后紧接着加一个反斜线“\”。例如:#define LEAP_YEA
R year%4= =0\&&year%100!=0||year%400= =0第一列如果在“\”前或在下一行的开头留有许多空格
,则在宏替换时也将加入这些空格。4 同一个宏名不能重复定义,除非两个宏定义命令行完全一致。5 替换文本不能替换双引号中与宏名相同的
字符串。例8.2 宏名相同的字符串不能替换。#define BOOK “The Red and The Black”int mai
n(){ printf(“%s\n”,”BOOK”); return 0;}运行结果:BOOK8.2.1 不带参数的宏定义6 替换
文本并不替换用户标识符中的成分。例如,宏名YES,不会替换标识符YESORNO中的YES。7 用作宏名的标识符通常用大写字母表示,
这并不是语法规定,只是一种习惯,以便与程序中的其他标识符相区别。8 在C程序中,宏定义的定义位置一般写在程序的开头。 返回8.2.
2 带参数的宏定义 1 带参数的宏定义命令行形式如下:格式:#define 宏名(形参表) 替换文本如果定义带参数的宏,在对源程序
进行预处理时,将程序中出现宏名的地方均用替换文本替换,并用实参代替替换文本中的形参。例8.3 编写程序,使用带参数的宏定义。#in
clude #define MAX(a,b) a>b?a:b /定义带参数的宏 MAX /#defi
ne SQR(c) cc /定义带参数的宏 SQR /int main(){ int x=3,y=4; x=MAX(
x,y); y=SQR(x); printf(“x=%d,y=%d\n”,x,y); return 0;}运行结果:x=4,y=1
6对于带参的宏定义有以下问题需要说明:2. 带参宏定义中,宏名和形参表之间不能有空格出现。 例如把: #define MA
X(a,b) (a>b)?a:b写为: #define MAX (a,b) (a>b)?a:b将被认为是无参宏定义,宏名
MAX代表字符串 (a,b) (a>b)?a:b。宏展开时,宏调用语句: max=MAX(x,y);将变为: max=
(a,b)(a>b)?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=%d\n",sq);}运行结果:input a number: 3
sq=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) yymain(){ int a,sq; printf("input a number
: "); scanf("%d",&a); sq=SQ(a+1); printf("sq=%d\n",sq);}运行结
果:input a number:3sq=7分析:同样输入3,但结果却是不一样的。问题在哪里呢? 这是由于代换只作符号代换而不作其
它处理而造成的。宏代换后将得到以下语句: sq=a+1a+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=%d\n",sq);}运行结果:input a numbe
r: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("%d\n",SQ(i++));}SQ(int y){ return((y)(y));}例8.
9#define SQ(y) ((y)(y))main(){ int i=1; while(i<=5) printf(
"%d\n",SQ(i++));}例8.8运行结果:1491625例8.9运行结果:19258.2.2 带参数的宏定义7 宏定义也
可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。看下面的例子。例8.10#define SSSV(s1,s2,s3,v
) s1=lw;s2=lh;s3=wh;v=wlh;main(){ int l=3,w=4,h=5,sa,sb,sc,
vv; SSSV(sa,sb,sc,vv); printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",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函数和f
ree函数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(sizeo
f(float));这是一种常用的形式。此时将由系统来计算指定类型的字节数,采用这种形式将有利于程序的移植。 8.4.1 mall
oc函数和free函数2 free函数格式:free(voidptr);功能:释放ptr所指向的一块内存空间,ptr是一个任意类
型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。例8.11分配一块区域,输入一个
学生数据。main(){ struct stu { int num; char name;
char sex; float score; } ps; ps=(struct stu)mall
oc(sizeof(struct stu)); ps->num=102; ps->name="Zhang ping";
ps->sex=''M''; ps->score=62.5; printf("Number=%d\nName=%s
\n",ps->num,ps->name); printf("Sex=%c\nScore=%f\n",ps->sex,ps-
>score); free(ps);}运行结果:Number=102Name=Zhang pingSex=MScore=62
.500000 返回8.4.2 calloc函数 calloc 也用于分配内存空间。格式:(类型说明符)calloc(n,siz
e)功能:在内存动态存储区中分配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节中介绍的相同。
献花(0)
+1
(本文系小磊老师首藏)