映月读书网 > C语言解惑 > 19.2 函数的参数 >

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 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]
等效
  

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