从例19.8中可见,全局变量作为公共变量有很多方便之处,但过多的全局变量也会引发不安全因素。像例19.8,如果将number作为函数的参数传递,就简化了设计。
【例19.9】将number作为display函数的参数,改写例19.8的程序。
//c19_9.h #include <stdio.h> void display (int ); //c19_9.c #include \"c19_9.h\" int main ( ) { int number ; printf (\" 输入:\" ); scanf (\"%d\" ,&number ); display (number ); return 0 ; } //c19_91.c #include \"c19_9.h\" void display (int number ) { printf (\"%dn\" ,number );}
19.2.1 完璧归赵
【例19.10】分析下面程序的问题,设计满足需要的程序。
#include <stdio.h> void display (int ,int* ); int main ( ) { int i=0 ,sum=0 ,a={1 ,-2 ,3 ,-4 ,5} ,*p=a ; int b={2 ,4 ,6 ,8 ,10} ; display (a ,p ); display (b ,p ); for (i=0 ;i<5 ;i++ ,p++ ) sum=sum+*p ; printf (\"sum=%dn\" ,sum ); return 0 ; } void display (int a ,int*p ) { int i=0 ; for (;i<5 ;i++ ,p++ ) printf (\"%d \" ,*p ); printf (\"n\" ); }
这个程序原想分别输出数组a和b的内容,以及数组b所有元素之和。但结果是两次输出数组a的内容及数组a所有元素之和。输出结果如下:
1 -2 3 -4 5 1 -2 3 -4 5 sum=3
原因是指针变量初始化为
p=a ;
在调用函数display之后,仍然保持这种关系不变。display函数使用指针显示数据,结果显示的仍然是数组a的内容。返回之后,还是指向数组a,所以计算的也是数组a的元素之和。
设计存在的问题是display函数没有重新设置指针的指向,而是保持它原来的设置。对第1次调用来说,是正常的。第2次就不运行了,它不管display的参数,而是自顾自地指向数组a,拿它作为显示对象,从而产生错误结果。
设计一个函数,一定要自己保证设计的正确性。对本例来说,就是要执行初始化。修改后的程序如下:
void display (int a[ ] , int*p ) { int i=0 ; for (p=a ; i<5 ; i++ , p++ ) printf (\"%d %d \" ,*p ,p ); printf (\"n\" ); }
如果这个指针参数只是借来使用,就不要改变它。需要注意的是,这不是指在离开之前执行一次“p=a;”,因为p并不是作为返回值,所以退出该被调用的函数后,p自然回到原来的值,即维持指针原来的指向(指向a),所以要执行“p=b;”才能计算数组b的元素之和。
改变是指不正确地把它作为左值。为了说明这个问题,下面修改一下display程序,看看会带来何种后果。
void display (int a[ ] , int*p ) { int i=0 ; for (p=a ; i<5 ; i++ , p++ ) printf (\"%d %d \" ,*p ,p ); printf (\"n\" ); *p=i+*p ; }
增加一句“*p=i+*p;”,根据变量声明的顺序,可知for语句结束时,使得p越界并指向变量sum的存储地址。这时有*p=0,i=5。执行这条语句就使sum=*p+i=5。
为了更容易说明危害程度,将变量声明的顺序改变一下,并输出存储地址,对照这些输出,很容易判别每次的改变过程。
#include <stdio.h> void display (int ,int* ); int main ( ) { int i=0 ,sum=0 ,a={1 ,-2 ,3 ,-4 ,5} ; int b={2 ,4 ,6 ,8 ,10} ,*p=a ; printf (\"%0x %0x %0x %0x %0x %0x\" ,&i ,&sum ,a ,b ,p ,&p ); printf (\"n\" ); display (a ,p ); printf (\"%d %0x \" ,*p ,p ); printf (\"n\" ); display (b ,p ); printf (\"%d %0x \" ,*p ,p ); printf (\"n\" ); for (i=0 ;i<5 ;i++ ,p++ ) sum=sum+*p ; printf (\"sum=%dn\" ,sum ); display (a ,p ); return 0 ; } void display (int a , int*p ) { int i=0 ; for (p=a ;i<5 ;i++ ,p++ ) printf (\"%d %0x \" ,*p ,p ); printf (\"n\" ); *p=i+*p ; printf (\"%d %0x \" ,*p ,p ); printf (\"n\" ); }
编译程序为变量分配的内存如下:
i sum a b p &p 12ff7c 12ff78 12ff64 12ff50 12ff64 12ff4c
第1次调用返回不再多说,使sum=5。第2次调用使用数组b,它在display函数里越界的地址是12ff64,即数组a[0]。这时a[0]=1,i=5。将使a[1]=5+1=6。
这时for语句,sum已经是5,a[0]=6,计算结果sum=5+6-2+3-4+5=13。
第3次用a数组调用display函数时,显示a数组a[0]的内容为6。返回越界又是sum的地址,sum=13+5=18。
下面是增加输出信息后的输出结果:
12ff7c 12ff78 12ff64 12ff50 12ff64 12ff4c 1 12ff64 -2 12ff68 3 12ff6c -4 12ff70 5 12ff74 5 12ff78 1 12ff64 2 12ff50 4 12ff54 6 12ff58 8 12ff5c 10 12ff60 6 12ff64 6 12ff64 sum=13 6 12ff64 -2 12ff68 3 12ff6c -4 12ff70 5 12ff74 18 12ff78
对照一下,可以对指针的性质有更深入的理解。
为了做到完璧归赵,最好的方法就是将它们作为常量指针传递,使用
void display (int[ ] , const int* );
原型即可。如果设计中出现误用左值的情况,编译系统就会报错。以后凡是传递不允许改变的参数,推荐使用const声明。
从这个例子也可悟出一个道理:必须管好传递的指针参数,否则会后患无穷。
19.2.2 多余的参数
上节的例19.10设计的display函数的参数过多,实无必要。其实,display函数只需要一个参数即可完成输出任务,程序中也不需要指针。简化的设计如下:
#include <stdio.h> void display (int ); int main ( ) { int i=0 ,sum=0 ,a[ ]={1 ,-2 ,3 ,-4 ,5} ,*p=a ; int b[ ]={2 ,4 ,6 ,8 ,10} ; display (a ); display (b ); for (i=0 ;i<5 ;i++ ) sum=sum+b[i] ; printf (\"sum=%dn\" ,sum ); return 0 ; } void display (int a ) { int i=0 ; for (;i<5 ;i++ ) printf (\"%d \" ,a[i] ); printf (\"n\" ); }
输出结果如下:
1 -2 3 -4 5 2 4 6 8 10 sum=30
【例19.11】下面的程序是否正确?
#include <stdio.h> void swap (int * , int * ); void main () { int a=23 , b=85 ; int *p1=&a , *p2=&b ; swap (p1 ,p2 ); // 可以直接使用swap (&a ,&b ); } void swap (int *P1 , int *P2 ) { int x=5 ,*temp=&x ; *temp=*P1 ; *P1=*P2 ; *P2=*temp ; }
【解答】程序设计完全达到设计要求,但存在多余参数。因为调用swap函数可以直接使用“swap(&a,&b);”,所以主程序中没有必要声明并使用指针,即
int *p1=&a , *p2=&b ; // 多余的方式
同样,在swap函数里交换的是数据,没必要使用指针。
// 修改后的程序 #include <stdio.h> void swap (int* , int* ); void main () { int a=23 , b=85 ; swap (&a , &b ); } void swap (int *P1 , int *P2 ) { int temp ; temp=*P1 ; *P1=*P2 ; *P2=temp ; }
结论:在能完成预定目标的前提下,传递的参数越少越好,设计的参数越少越好。
【例19.12】找出下面程序中多余的语句。
#include <stdio.h> struct LIST{ int a ,b ; }d={3 ,8} ; void swap (struct LIST * ); // 函数参数采用传地址值的方式 void main () { struct LIST *p=&d ; // 多余的方式 swap (p ); // 可以直接使用swap (&d ) } // 将结构指针作为参数,以传地址值的方式传递这个参数 void swap (struct LIST *s ) { int temp=s->a ; s->a=s->b ; s->b=temp ; printf (\" 函数中 a=%d ,b=%dn\" , s->a ,s->b ); }
【解答】直接使用“swap(&d);”即可。
19.2.3 传递的参数与函数参数匹配问题
这种情况常发生在调用库函数或调用别人提供的函数时,原因是对那些函数了解不够。一般来讲,传递的参数类型必须与函数设计的参数类型严格一致。但有一种情况需要注意,那就是传递数组参数。因为数组名就是存储数组的首地址,所以既可以使用数组名,也可以使用指针。在碰到指针作为参数时,例如下面是显示一维数组a的函数原型:
void disp (int * );
在调用时,可以直接使用disp(a),如果没有指向a的指针,不需要再使用
int *p=a ;
语句。有的人不放心,认为函数原型是指针,就一定要换成指针去传递,忘记了数组名就是存储首地址的指针。其实细想一下,当用数组名a作为参数时,与原设计的int*,是否恰好组合成如下形式?
int *p=a ;
到这里,应该豁然开朗了吧!
【例19.13】这个例子设计两种形式同一功能的函数,使用两种方式调用以说明传递数组的问题。
#include <stdio.h> void display (int ); void disp (int * ); int main ( ) { int i=0 ,sum=0 ,a[ ]={1 ,-2 ,3 ,-4 ,5} ; int b[ ]={2 ,4 ,6 ,8 ,10} ,*p ; display (a ); p=b ; display (p ); p=a ; disp (p ); disp (b ); return 0 ; } void display (int a ) { int i=0 ; for (;i<5 ;i++ ) printf (\"%d \" ,* (a+i )); printf (\"n\" ); } void disp (int *p ) { int i=0 ; for (;i<5 ;i++ ) printf (\"%d \" ,* (p+i )); printf (\"n\" ); }
输出结果如下:
1 -2 3 -4 5 2 4 6 8 10 1 -2 3 -4 5 2 4 6 8 10
【例19.14】找出下面程序的错误并改正之。
#include <stdio.h> void display (int[ ] ); void disp (int * ); int main ( ) { int i=0 ,sum=0 ,a[2][3]={1 ,-2 ,3 ,-4 ,5 ,7} ; int b[2][3]={2 ,4 ,6 ,8 ,10 ,12} ,*p ; display (a ); p=b ; display (p ); p=a ; disp (p ); disp (b ); return 0 ; } void display (int a ) { int i=0 ; for (;i<6 ;i++ ) printf (\"%d \" ,* (a+i )); printf (\"n\" ); } void disp (int *p ) { int i=0 ; for (;i<6 ;i++ ) printf (\"%d \" ,p[i] ); printf (\"n\" ); }
程序设计的函数均正确,只是调用时错误地使用一维数组的方式。对二维数组a[2][3]而言,其数组名是a[0]。修改主函数即可。
int main ( ) { int i=0 ,sum=0 ,a[2][3]={1 ,-2 ,3 ,-4 ,5 ,7} ; int b[2][3]={2 ,4 ,6 ,8 ,10 ,12} ,*p ; display (a[0] ); p=b[0] ; display (p ); p=a[0] ; disp (p ); disp (b[0] ); return 0 ; }
程序输出结果如下:
1 -2 3 -4 5 7 2 4 6 8 10 12 1 -2 3 -4 5 7 2 4 6 8 10 12
匹配的另一个问题是函数的原型声明。本例中的语句
void display (int[ ] ); void disp (int * );
就是对数组和指针的典型声明方式。还要注意的是结构、字符数组等的声明和匹配方式。
19.2.4 等效替换参数
英文字符串可以作为变量,中文则不行。在有些场合就需要先用英文作为变量求解,然后再对结果进行转换。
【例19.15】一般求解逻辑问题常会碰到这类问题。例如我国有4大淡水湖。下面是4个人对湖的大小的回答。
A说:洞庭湖最大,洪泽湖最小,鄱阳湖第三。
B说:洪泽湖最大,洞庭湖最小,鄱阳湖第二,太湖第三。
C说:洪泽湖最小,洞庭湖第三。
D说:鄱阳湖最大,太湖最小,洪泽湖第二,洞庭湖第三。
已知4个人每个人仅答对了一个,请编程给出4个湖从大到小的顺序。
1.算法分析
(1)为了编程方便,使用汉语拼音表示4个湖名,即:
洞庭湖─Dongting
洪泽湖─Hongze
鄱阳湖─Poyang
太湖─Tai
(2)令湖的大小依次为1、2、3、4。1表示最大,4表示最小。然后用As、Bs、Cs、Ds代表4个人说的话,则得到如下表达式:
As= ( Dongting ==1 )+ ( Hongze==4 )+ ( Poyang==3 ); Bs= ( Hongze==1 )+ ( Dongting==4 )+ ( Poyang==2 )+ (Tai==3 ); Cs= ( Hongze==4 )+ ( Dongting==3 ); Ds= ( Poyang==1 )+ ( Tai==4 )+ ( Hongze==2 )+ ( Dongting==3 );
(3)用1、2、3、4去枚举每个湖的大小,可以通过四重循环来实现。题目中说4个人每个人只答对了一个,也就是说程序中的判定条件为:
if (As==1 && Bs==1 && Cs==1 && Ds==1 )
这样就可以确定4个湖的大小了,然后按照从大到小的顺序输出这4个湖。
(4)需要一个字符数组存放4个湖的名字。不使用下标0,所以声明为:
char lake[5][10] ;
(5)比较时,不能把自己与自己比较,所以必须排除这种情况。
(6)用函数find求解,使用二维字符串数组lake作为参数。
2.源程序清单
#include<stdio.h> // 预编译命令 #include<string.h> void Find (char lake[50] ); void main () { int i ; char lake[5][50] ; // 字符数组用来存放名次 // 传输参数求解 Find (lake ); // 按照从大到小的顺序输出这4 个湖 for ( i=1 ;i<=4 ;i++ ) printf (\"%d %s \" , i ,lake[i] ); printf (\"n\" ); } void Find (char lake[5][50] ) { int As , Bs , Cs , Ds ; // 定义每个人说的话 int Dongting , Hongze , Poyang , Tai ; // 定义4 个湖 for (Dongting=1 ;Dongting<=4 ;Dongting++ ) // 循环控制变量为Dongting for (Hongze=1 ;Hongze<=4 ;Hongze++ ){ // 循环控制变量为Hongze if (Hongze==Dongting ) // 不让两个变量相同 continue ; for (Poyang=1 ;Poyang<=4 ;Poyang++ ){ if (Poyang==Hongze || Poyang==Dongting ) // 不让两个变量相同 continue ; Tai=10-Dongting-Hongze-Poyang ; // 计算变量Tai As= (Dongting==1 )+ (Hongze==4 )+ (Poyang==3 ); //A 说的话 Bs= ( Hongze==1 )+ ( Dongting==4 )+ ( Poyang==2 )+ (Tai==3 ); //B 说的话 Cs= ( Hongze==4 )+ ( Dongting==3 ); //C 说的话 Ds= ( Poyang==1 )+ ( Tai==4 )+ ( Hongze==2 )+ ( Dongting==3 ); //D 说的话 if (As==1 && Bs==1 && Cs==1 && Ds==1 ){ // 每个人说对一句 strcpy (lake[Dongting] ,\" 洞庭湖\" ); strcpy (lake[Hongze] ,\" 洪泽湖\" ); strcpy (lake[Poyang] ,\" 鄱阳湖\" ); strcpy (lake[Tai] ,\" 太湖\" ); }//endif } //End Poyang } //End Hongze }
程序运行结果如下:
1 鄱阳湖 2 洞庭湖 3 太湖 4 洪泽湖
这里没有使用一维数组指针,如果使用,则方法如下所示:
char (*p )[50]=lake ; Find (p ); for ( i=1 ;i<=4 ;i++ ) printf (\"%d%s \" , i ,* (p+i )); // 与p[i] 等效
对于这类程序,直接使用数组即可,不需要使用指针。