讀古今文學網 > 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]
等效
  

對於這類程序,直接使用數組即可,不需要使用指針。