讀古今文學網 > C語言解惑 > 21.4 結構函數的返回值 >

21.4 結構函數的返回值

還是老問題。沒有返回值照樣可以改變實參的值,有返回值一樣可以不改變實參的值。設計函數關鍵是看如何簡單、實用。結構函數可以無返回值,也可以返回結構或結構指針,推薦盡可能優先考慮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