对于带参的宏定义有以下问题需要说明:
1。 带参宏定义中,宏名和形参表之间不能有空格出现。
例如把:
#define MAX(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);
这显然是错误的。
2。 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有
具体的值。要用它们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函
数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行
“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。
3。 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
【例 9。5】
#define SQ(y) (y)*(y)
main(){
int a;sq;
printf(〃input a number: 〃);
scanf(〃%d〃;&a);
sq=SQ(a+1);
printf(〃sq=%dn〃;sq);
}
上例中第一行为宏定义,形参为 y。程序第七行宏调用中实参为 a+1,是一个表达式,在
宏展开时,用 a+1 代换 y,再用(y)*(y) 代换 SQ,得到如下语句:
sq=(a+1)*(a+1);
这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再赋予形参。而宏代
换中对实参表达式不作计算直接地照原样代换。
4。 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。在上例中的宏定义中
(y)*(y)表达式的 y 都用括号括起来,因此结果是正确的。如果去掉括号,把程序改为以下形
式:
【例 9。6】
#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:3
sq=7
同样输入 3,但结果却是不一样的。问题在哪里呢? 这是由于代换只作符号代换而不作
其它处理而造成的。宏代换后将得到以下语句:
sq=a+1*a+1;
由于 a 为 3 故 sq 的值为 7。这显然与题意相违,因此参数两边的括号是不能少的。即使
在参数两边加括号还是不够的,请看下面程序:
【例 9。7】
#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);
}
本程序与前例相比,只把宏调用语句改为:
sq=160/SQ(a+1);
运行本程序如输入值仍为 3 时,希望结果为 10。但实际运行的结果如下:
input a number:3
sq=160
为什么会得这样的结果呢?分析宏调用语句,在宏代换之后变为:
sq=160/(a+1)*(a+1);
a 为 3 时,由于“/”和“*”运算符优先级和结合性相同,则先作 160/(3+1)得 40,再作 40*(3+1)
最后得 160。为了得到正确答案应在宏定义中的整个字符串外加括号,程序修改如下:
【例 9。8】
#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);
}
以上讨论说明,对于宏定义不仅应在参数两侧加括号,也应在整个字符串外加括号。
5。 带参的宏和带参函数很相似,但有本质上的不同,除上面已谈到的各点外,把同一
表达式用函数处理与用宏处理两者的结果有可能是不同的。
【例 9。9】
main(){
int i=1;
while(iname=〃Zhang ping〃;
ps…》sex='M';
ps…》score=62。5;
#ifdef NUM
printf(〃Number=%dnScore=%fn〃;ps…》num;ps…》score);
#else
printf(〃Name=%snSex=%cn〃;ps…》name;ps…》sex);
#endif
free(ps);
}
由于在程序的第 16 行插入了条件编译预处理命令,因此要根据 NUM 是否被定义过来决定
编译那一个 printf 语句。而在程序的第一行已对 NUM 作过宏定义,因此应对第一个 printf
语句作编译故运行结果是输出了学号和成绩。
在程序的第一行宏定义中,定义 NUM 表示字符串 OK,其实也可以为任何字符串,甚至不
给出任何字符串,写为:
#define NUM
也具有同样的意义。只有取消程序的第一行才会去编译第二个 printf 语句。读者可上机试作。
2。 第二种形式:
#ifndef 标识符
程序段 1
#else
程序段 2
#endif
与第一种形式的区别是将 “ifdef” 改为“ifndef” 。它的功能是,如果标识符未被
#define 命令定义过则对程序段 1 进行编译,否则对程序段 2 进行编译。这与第一种形式的
功能正相反。
3。 第三种形式:
#if 常量表达式
程序段 1
#else
程序段 2
#endif
它的功能是,如常量表达式的值为真(非 0),则对程序段 1 进行编译,否则对程序段 2
进行编译。因此可以使程序在不同条件下,完成不同的功能。
【例 9。13】
#define R 1
main(){
float c;r;s;
printf (〃input a number: 〃);
scanf(〃%f〃;&c);
#if R
r=3。14159*c*c;
printf(〃area of round is: %fn〃;r);
#else
s=c*c;
printf(〃area of square is: %fn〃;s);
#endif
}
本例中采用了第三种形式的条件编译。在程序第一行宏定义中,定义 R 为 1,因此在条
件编译时,常量表达式的值为真,故计算并输出圆面积。
上面介绍的条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序
进行编译,生成的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段 1
或程序段 2,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方法是十
分必要的。
9。5 本章小结
1。 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程
序员在程序中用预处理命令来调用这些功能。
2。 宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在
宏调用中将用该字符串代换宏名。
3。 宏定义可以带有参数,宏调用时是以实参代换形参。而不是“值传送”。
4。 为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两
边也应加括号。
5。 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,
结果将生成一个目标文件。
6。 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了
内存的开销并提高了程序的效率。
7。 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。
谭浩强 C 语言程序设计 2001 年 5 月 1 日
10 指针
指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。
利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样
处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。学习指针是
学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。
同时,指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要
多编程,上机调试。只要作到这些,指针也是不难掌握的。
10。1 地址指针的基本概念
在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个
内存单元,不同的数据类型所占用的内存单元数不等,如整型量