首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》19.2 函数的参数

关灯直达底部

从例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 51 -2 3 -4 5sum=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      &p12ff7c 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 12ff4c1 12ff64 -2 12ff68 3 12ff6c -4 12ff70 5 12ff745 12ff781 12ff642 12ff50 4 12ff54 6 12ff58 8 12ff5c 10 12ff606 12ff646 12ff64sum=136 12ff64 -2 12ff68 3 12ff6c -4 12ff70 5 12ff7418 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 52 4 6 8 10sum=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 52 4 6 8 101 -2 3 -4 52 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 72 4 6 8 10 121 -2 3 -4 5 72 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]等效  

对于这类程序,直接使用数组即可,不需要使用指针。