還是老問題。沒有返回值照樣可以改變實參的值,有返回值一樣可以不改變實參的值。設計函數關鍵是看如何簡單、實用。結構函數可以無返回值,也可以返回結構或結構指針,推薦盡可能優先考慮void類型。
【例21.17】一個源程序如下:
#include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; void fg ( struct List * ); void disp ( struct List * ); void main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); fg (p ); printf (\"%fn\" , p->z ); p->z=123.456 ; ++p ; disp (arg ); } void fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%fn\" , s[i].a , s[i].z ); }
有一位程序員分析得出如下運行結果。
1000 ,98.900000 //disp 使用結構數組名調用,故只有arg[0] 有數據 0 ,0.000000 0 ,0.000000 0 ,0.000000 //p 指向的是arg[1] ,這一組為0 88.500000 // 賦值arg[2].z 的輸出 88.500000 // 返回後保留arg[2] 的值 1000 ,98.900000 // disp 使用結構數組名調用 0 ,0.000000 0 ,123.456000 // 因為用它覆蓋了88.5
請問這個分析對嗎?
【解答】不對。對第1次和第2次的輸出的分析是對的。對返回主程序的輸出的分析是錯的。這時要注意函數fg返回的指針到底指向哪裡。在fg中,p是指向arg[2]。但這個函數沒有返回值,既然現在的指針不參與返回,這就要取決於在程序中的具體使用方法。
在這個函數fg中,p參與左值運算,但在fg程序運行結束時,它是返回進入時的指向,即arg[1],所以這時的z為0,輸出應為0值而不是88.5,並且保留對arg[2]的修改。輸入123.456是修改當前指向的arg[1]的z值。雖然執行指針運算使p指向arg[2],但調用時卻是使用數組名,所以從arg[0]開始輸出。由此可見,輸出結果應該為
1000 ,98.900000 0 ,0.000000 0 ,0.000000 0 ,0.000000 88.500000 0.000000 1000 ,98.900000 0 ,123.456000 0 ,88.500000
【例21.18】如果將例21.17中的fg函數設計為返回指針的函數,是否會輸出像那個程序員分析的結果呢?
【解答】如果只是將函數聲明和定義分別修改,主程序不變,則仍然不會符合他的分析。為了更好地說明問題,特意在程序中輸出指針指向的地址,一看輸出結果,就非常清楚了。
// 增加輸出地址的源程序 #include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; struct List *fg ( struct List * ); // 原型聲明 void disp ( struct List * ); int main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); printf (\" 調用fg 之前=0x%xn\" , p ); // 輸出調用時的指向地址 fg (p ); printf (\" 調用fg 之後=0x%xn\" , p ); // 輸出調用返回後的指向地址 printf (\"%fn\" , p->z ); p->z=123.456 ; ++p ; disp (arg ); return 0 ; } struct List *fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); printf (\" 在fg 中=0x%xn\" , p ); // 輸出程序最後使用時的指向地址 return p ; } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%fn\" , s[i].a , s[i].z ); }
程序運行結果如下:
1000 ,98.900000 0 ,0.000000 0 ,0.000000 調用fg 之前=0x427bb0 0 ,0.000000 88.500000 在fg 中=0x427bc0 調用fg 之後=0x427bb0 0.000000 1000 ,98.900000 0 ,123.456000 0 ,88.500000
儘管將fg函數設計為返回指針的函數,但在主程序中並沒有使用這個返回值,所以它返回主程序之後,指針指向的地址值與調用時的一樣,所以效果也與例21.17相同。
顯然,如果在主程序中使用語句
p= fg (p ); // 程序中用p 接收返回的地址值
接收返回的地址值,則輸出結果為:
1000 ,98.900000 0 ,0.000000 0 ,0.000000 調用fg 之前=0x427bb0 0 ,0.000000 88.500000 在fg 中=0x427bc0 調用fg 之後=0x427bc0 88.500000 1000 ,98.900000 0 ,0.000000 0 ,123.456000
這個結果就與那個程序員分析的一樣了。
【例21.19】如果將例21.18中的fg函數設計為如下的返回結構的函數。
struct List fg (struct List *p ) { printf (\"%d ,%fn\" , p->a , p->z ); p++ ; p->z=88.5 ; printf (\"%fn\" , p->z ); printf (\"%un\" , p ); return *p ; }
假設主函數不變,試分析輸出結果。
【解答】因為沒有使用返回值,所以結果與例21.18的一樣。
函數有返回值,主函數不使用,這個函數對主函數的影響就與它的返回值無關。如果使用如下調用方式。
*p=fg (p );
在fg函數里,最後使用的是arg[2],返回的指針指向進入時的結構數組元素arg[1],所以除了保留修改的arg[2]值之外,還將arg[2]的值整體賦給arg[1],所以輸出是「88.5」。這個值接著又被新輸入的「123.456」代替。
由此可見,如何設計函數的返回值以及如何使用返回值,均是要仔細斟酌的。
為了加強理解,可以使用跟蹤調試方法觀察程序的運行過程。下面給出配合單步跟蹤的源程序,程序裡將地址用十六進制輸出,為arg[3]的a賦值以便對比,並在輸出結果中給出執行的過程。
// 演示程序 #include <stdio.h> struct List { int a ; double z ; }arg[3] ,*p ; struct List fg ( struct List * ); void disp ( struct List * ); int main ( ) { p=arg ; p->a=1000 ; p->z=98.9 ; p++ ; disp (arg ); *p=fg (p ); printf (\"%0x ,%d ,%fn\" , p ,p->a ,p->z ); printf (\"%0x ,%d ,%fn\" , (p+1 ),(p+1 )->a ,(p+1 )->z ); p->z=123.456 ; printf (\"%0x ,%d ,%fn\" , p ,p->a ,p->z ); ++p ; disp (arg ); return 0 ; } struct List fg (struct List *p ) { printf (\"%d ,%f ,%0xn\" , p->a , p->z ,p ); p++ ; p->a=58 ; p->z=88.5 ; printf (\"%d ,%f ,%0xn\" , p->a ,p->z ,p ); return *p ; } void disp ( struct List *s ) { int i ; for (i=0 ;i<3 ;i++ ) printf (\"%d ,%f ,%0xn\" , s[i].a , s[i].z ,s+i ); }
程序運行結果如下。
1000 ,98.900000 ,427ba0 // 主程序設置arg[0] 0 ,0.000000 ,427bb0 //arg[1] 的初值 0 ,0.000000 ,427bc0 //arg[2] 的初值 0 ,0.000000 ,427bb0 //427bc0 進入函數fg ,輸出arg[1] 的值 58 ,88.500000 ,427bc0 // 設置arg[2] 427bb0 ,58 ,88.500000 // 輸出arg[2] 427bc0 ,58 ,88.500000 // 返回並輸出arg[1] ,等效arg[1]=arg[2] 427bb0 ,58 ,123.456000 // 123.456 覆蓋88.5 ,輸出修改的arg[1] , 1000 ,98.900000 ,427ba0 // 輸出全部內容 58 ,123.456000 ,427bb0 // 主程序操作修改返回的arg[1] 58 ,88.500000 ,427bc0 //fg 函數修改的內容
【例21.20】假設已定義如下複數結構。
typedef struct { double re , im ; }complex ;
編寫複數的加、減、乘、除的計算函數並驗證之。
【解答】主要是除法運算需要考慮除數為0的情況。其實,作為除法函數,應該處理這種情況,但作為調用者,也應該避免這種情況。不要把希望寄托在別人身上,要考慮主動預防錯誤。
對於除法函數,可能選擇的路也很多,要根據要求考慮合適的處理方法。有時不希望直接使用exit函數退出,而希望在得到結果後自己處理。例如:
complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; d = y.re*y.re + y.im*y.im ; if (d==0 ) return z ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
這裡返回的z的實部和虛部都是0,可以在主程序中判別這個值進行處理。p函數是在計算出d之後,再判別d是否為0。其實,可以先判別除數的實部和虛部是否為0,如果為0,則不要去計算d值。例如:
complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; if ((y.re==0 )&& (y.im==0 )) return z ; d = y.re*y.re + y.im*y.im ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
主程序根據z值自己決定如何處理。其實,在調用p函數之前,應該養成先判別除數是否為0的習慣。如果為0,則根本不需要調用p函數。
注意:函數可以有多個返回路徑,但每次運行只能有一個條件滿足,也即一個路徑。
這裡給出一個示範的處理方法。
#include <stdio.h> typedef struct { double re , im ; }complex ; complex add (complex , complex ); complex minus ( complex , complex ); complex mul (complex , complex ); complex p (complex , complex ); int main ( ) { complex a ,b ,c ,d ; a.re=4.0 ; a.im=3.0 ; b.re=2.0 ; b.im=1.0 ; d.re=0.0 ; d.im=0.0 ; c=add (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); c=minus (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); c=mul (a ,b ); printf (\"%lf + %lfin\" ,c.re ,c.im ); if ((b.re==0 )&& (b.im==0 )){ printf (\" 除數為0 ,不能調用,退出!n\" ); return 0 ; } c=p (a ,b ); if ((c.re==0 )&& (c.im==0 )){ printf (\" 除數為0 ,返回為0 ,輸出為0 。n\" ); } printf (\"%lf + %lfin\" ,c.re ,c.im ); c=p (a ,d ); if ((c.re==0 )&& (c.im==0 )) { printf (\" 除數為0 ,返回為0 ,輸出為0 。n\" ); } printf (\"%lf + %lfin\" ,c.re ,c.im ); if ((d.re==0 )&& (d.im==0 )){ printf (\" 除數為0 ,不能調用,退出!n\" ); return 0 ; } c=p (a ,d ); if ((c.re==0 )&& (c.im==0 )) { printf (\" 除數為0 ,返回為0 ,輸出為0 。n\" ); } return 0 ; } complex add (complex x , complex y ) { complex z ; z.re = x.re + y.re ; z.im = x.im + y.im ; return ( z ); } complex minus ( complex x , complex y ) { complex z ; z.re = x.re - y.re ; z.im = x.im - y.im ; return ( z ); } complex mul (complex x , complex y ) { complex z ; z.re = x.re * y.re - x.im*y.im ; z.im = x.im * y.re + x.re*y.im ; return ( z ); } complex p (complex x , complex y ) { double d ; complex z ; z.re=0 ;z.im=0 ; if ((y.re==0 )&& (y.im==0 )) return z ; d = y.re*y.re + y.im*y.im ; z.re = (x.re * y.re + x.im*y.im )/d ; z.im = (x.im * y.re - x.re*y.im )/d ; return ( z ); }
程序運行結果如下。
6.000000 + 4.000000i 2.000000 + 2.000000i 5.000000 + 10.000000i 2.200000 + 0.400000i 除數為0 ,返回為0 ,輸出為0 。 0.000000 + 0.000000i 除數為0 ,不能調用,退出!
注意:主程序有意製造除數為0的情況以檢驗處理分支。
【例21.21】本程序是編寫一個用Eratosthenes篩選算法找出比N小的係數的程序。算法思想如下。
產生一個包含從2到N的排序連接鏈。
for ( num = 2 ; num = n ; num = 可能存在的下一個鏈元素)
刪除鏈中所有為num的整數倍的數,鏈中剩下的數為素數。
編寫的程序通不過編譯,請找出錯誤。
// 源程序 const int N=100 ; struct number{ // 結構具有指向自己的成員next int num ; struct number *next ; }a[N] ; #include <stdio.h> void Star (struct number * ); void Find (struct number * ); int main () { struct number *p ; // 結構指針 p=&a[2] ; // 指向結構變量 Star (p ); // 初始化 Find (p ); // 求解 return 0 ; } void Star (struct number *p ) { int i ; for (i=2 ;i<N-1 ;i++ ) // 初始化,實際形成一排序連接鏈 { a[i].num =i ; a[i].next = &a[i+1] ; } a[N-1].num =N-1 ; // 處理第N 個數組元素 a[N-1].next =0 ; // 結束標誌 } void Find (struct number *p ) { int n=0 ; for (p=&a[2] ;p ;p=p->next ) // 使結構指針自動指向下一個可能的鏈元素 for (n=2 ; n<p->num ; n++ ) { if (p->next==0 ) break ; // 沒有下一可能鏈元素則退出循環 else { for (n=2 ;n < p->num ;n++ ) // 以從2 開始的整數n 作為除數,以指針 // 指向的下一個鏈元素為被除數 { if ((p->next->num )%n==0 ) // 如果餘數為0 {// 則指針改為指向下一個鏈元素的next ,即將下一鏈 p->next = p->next->next ; // 元素刪除,並且退出循環 break ; } } } } if (N<=2 ) // 輸出 printf (\" 不存在比%d 小的素數n\" ,N ); else{ int i=1 ; for (p=&a[2] ;p ;p=p->next ,i++ ){ printf (\"%5d\" ,p->num ); if (i%5==0 ) printf (\"n\" ); } } printf (\"n\" ); }
【解答】分析第1次掃瞄的信息。
error C2057 : expected constant expression error C2466 : cannot allocate an array of constant size 0
錯誤很簡單,是結構數組「a[N]」的N不符合要求。即使使用
const unsigned mt N = 100
聲明,N也不能作為數組的維數,這裡需要使用宏定義來定義常數,即
#define N 100
程序輸出結果如下:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97