從例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] 等效
對於這類程序,直接使用數組即可,不需要使用指針。