讀古今文學網 > C語言解惑 > 19.3 函數的類型和返回值 >

19.3 函數的類型和返回值

調用一個函數的目的有兩種,一種是直接在被調函數里輸出結果,另一種是從它那裡得到一種供程序繼續使用的中間值。

19.3.1 函數的類型力求簡單

如果要求使用一個二維數組定義複數結構並寫出複數加法的計算函數。假設定義一個二維數組


double  a[3][2]
;
  

分別用這個數組0和1行存儲兩個運算數據,結果存入第3行。

可以用不同的方法設計這個程序,比較幾種方法的特點。

【例19.16】下面是使用指針並返回實數型的複數加法程序,分析一下它存在的問題。


#include <stdio.h>
double *add
(double*
);
int main
()
{
     double a[3][2]={4
,3
,2
,1}
;
     double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
    double *p=a[0]
;
    add
(p
);
    printf
("%lf+%lfi\n"
,p[4]
,p[5]
);
     p=b[0]
;
    add
(p
);
    printf
("%lf+%lfi\n"
,p[4]
,p[5]
);
     p=c[0]
;
    add
(p
);
    printf
("%lf+%lfi\n"
,p[4]
,p[5]
);
    return 0
;
}
double *add
(double *p
)
{
     p[4] = p[0] + p[2]
;
     p[5] = p[1] + p[3]
;
     return p
;
}
  

這個程序將函數add設計得很複雜,是返回指針的函數。add函數里面使用語句


return p
;
  

即可。但在主程序中,add修改了數組的內容,所以不需要設計指針變量接收函數的返回值。但在主函數中調用時,由於主函數里的printf使用指針輸出,所以不能使用數組首地址。可以改為使用數組輸出,例如:


add
(b[0]
);
printf
("%lf+%lfi\n"
,b[2][0]
,b[2][1]
);
  

其實,沒有必要將函數設計為返回指針的函數。如果將函數原型改為


double add
(double *
);
  

在add函數里使用


return  *p
;
  

即可。主程序的調用方法一樣,既可以使用指針,也可以使用數組的首地址。

既然不使用函數返回值,最簡單的應該是將它設計為void類型。

【例19.17】將add設計為void類型,參數使用指針的例子。

將add函數的原型設計為


void add
(double *
);
  

函數設計如下:


void add
(double *p
)
{
      p[4] = p[0] + p[2]
;
      p[5] = p[1] + p[3]
;
}
  

在主函數里面既可以使用指針,也可以使用數組首地址。

19.3.2 實參要與函數形參的類型匹配

【例19.18】將add設計為void類型,參數使用數組的例子。

重點主要是數組的表示方法。這裡的add函數仍然使用原來的設計方法,這就要求數組的標識符也選擇p,省去改寫add函數的麻煩。完整的程序如下。


#include <stdio.h>
void add
(double[ ]
);
int main
()
{
     double a[3][2]={4
,3
,2
,1}
;
     double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
     double *p=a[0]
;
     add
(p
);
     printf
("%lf+%lfi\n"
,a[2][0]
,a[2][1]
);
     add
(b[0]
);
     printf
("%lf+%lfi\n"
,b[2][0]
,b[2][1]
);
     add
(c[0]
);
     printf
("%lf+%lfi\n"
,c[2][0]
,c[2][1]
);
     return 0
;
}
void add
(double p
)
{
      p[4] = p[0] + p[2]
;
      p[5] = p[1] + p[3]
;
}
  

這個程序使用語句


double *p=a[0]
;
  

定義指針變量p,是因為a是二維數組,其首地址是a[0]。如果使用語句


double *p=a
;
  

在第1次掃瞄時會有警告信息,雖然能通過二次掃瞄,運行結果也正確,但應該避免警告信息,即應採取正規的方法定義指針變量。

運行結果如下:


6.000000+4.000000i
7.000000+7.000000i
5.000000+2.000000i
  

【例19.19】下面也是將add設計為void類型,參數使用數組的例子。雖然運行正常,但有編譯警告信息,請消除這些警告信息。


#include <stdio.h>
void add
(double [ ][2]
);
int main
()
{
     double a[3][2]={4
,3
,2
,1}
;
     double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
     double *p=&a[0][0]
;
     add
(p
);
     printf
("%lf+%lfi\n"
,a[2][0]
,a[2][1]
);
     add
(b[0]
);
     printf
("%lf+%lfi\n"
,b[2][0]
,b[2][1]
);
     add
(c[0]
);
     printf
("%lf+%lfi\n"
,c[2][0]
,c[2][1]
);
     return 0
;
}
void add
(double a[2]
)
{
      a[2][0] = a[0][0] + a[1][0]
;
      a[2][1] = a[0][1] + a[1][1]
;
}
  

【解答】二維函數原型聲明中的參數只需要列數,不需要行數。Add函數原型聲明和定義均無問題,看來是調用方式產生的警告信息。

對它進行第1次掃瞄,編譯信息如下:


warning C4047
: 'function' 
: 'double 
(*
)[2]' differs in levels of indirection from 'double *'
warning C4024
: 'add' 
: different types for formal and actual parameter 1
warning C4047
: 'function' 
: 'double 
(*
)[2]' differs in levels of indirection from 'double [2]'
warning C4024
: 'add' 
: different types for formal and actual parameter 1
warning C4047
: 'function' 
: 'double 
(*
)[2]' differs in levels of indirection from 'double [2]'
warning C4024
: 'add' 
: different types for formal and actual parameter 1
answer.obj - 0 error
(s
), 6 warning
(s
)
  

果真如此!分析警告信息,把調用b和c改為如下方式:


add
(b
);
add
(c
);
  

這時只有指針p作為參數帶來了警告信息。注意「double(*)[2]」意思是需要一維數組的指針才能匹配。應該用如下方式定義並使用指針。


double 
(*p
)[2]
;
p=a
;
  

或者直接定義如下:


     double 
(*p
)[2]=a
;
//
完整程序
#include <stdio.h>
void add
(double [ ][2]
);
int main
()
{
     double a[3][2]={4
,3
,2
,1}
;
     double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
     double 
(*p
)[2]
;          // 
可直接用double 
(*p
)[2]=a
;
     p=a
;
     add
(p
);
     printf
("%lf+%lfi\n"
,a[2][0]
,a[2][1]
);
     add
(b
);
     printf
("%lf+%lfi\n"
,b[2][0]
,b[2][1]
);
     add
(c
);
     printf
("%lf+%lfi\n"
,c[2][0]
,c[2][1]
);
     return 0
;
}
void add
(double a[2]
)
{
     a[2][0] = a[0][0] + a[1][0]
;
     a[2][1] = a[0][1] + a[1][1]
;
}
  

19.3.3 正確設計函數的返回方式

【例19.20】使用一個二維數組定義複數結構並編寫複數除法的計算函數。

【解答】假設定義一個二維數組


double  a[3][2]
;
  

分別用這個數組0和1行存儲兩個運算數據,結果存入第3行。

因為用數組作為參數,所以能夠返回計算結果。對這種情況,最簡單的是將函數設計為void類型。參數選擇指針,原型如下:


void pe
(double *
);
  

完整的源程序如下:


#include <stdio.h>
#include <stdlib.h>
void pe
(double *
);
int main
()
{
      double a[3][2]={4
,3
,2
,1}
;
      double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
     double *p
;
     p=a[0]
;
     pe
(p
);
     printf
("%lf%+lfi\n"
,p[4]
,p[5]
);
     p=b[0]
;
     pe
(p
);
     printf
("%lf%+lfi\n"
,p[4]
,p[5]
);
     p=c[0]
;
     pe
(p
);
     printf
("%lf%+lfi\n"
,p[4]
,p[5]
);
     return 0
;
}
void pe
(double *p
)
{
      double d
;
      if 
((p[2]==0
)&&
(p[3]==0
)){
             printf
("
除數為0
,退出!\n"
);
             exit
(1
) 
;
      }
      d=p[2] * p[2]+p[3] * p[3]
;
      p[4] = 
(p[0] * p[2]+p[1] * p[3]
)/d
;
      p[5] = 
(p[1] * p[2] - p[0] * p[3]
)/d
;
}
  

運行結果如下:


2.200000+0.400000i
0.689655-0.724138i
除數為0
,退出!
  

主函數里使用指針下標計算,所以3個輸出語句完全一樣。

對於除法函數而言,如果不處理除數為0的情況,就會使程序出錯。所以在這個函數里使用exit函數退出。

因為pe函數的參數是double型指針,所以可以使用指針下標進行計算。

如果使用數組,其方法與例19.19的相同。使用數組的源程序如下:


#include <stdio.h>
#include <stdlib.h>
void pe
(double [2]
);
int main
()
{
     double a[3][2]={4
,3
,2
,1}
;
     double b[3][2]={5
,2
,2
,5}
;
     double c[3][2]={5
,2}
;
     double 
(*p
)[2]
;
     p=a
;
     pe
(p
);
     printf
("%lf%+lfi\n"
,a[2][0]
,a[2][1]
);
     pe
(b
);
     printf
("%lf%+lfi\n"
,b[2][0]
,b[2][1]
);
     pe
(c
);
     printf
("%lf%+lfi\n"
,c[2][0]
,c[2][1]
);
     return 0
;
}
void pe
(double a[2]
)
{
     double d
;
     if 
((a[1][0]==0
)&&
(a[1][1]==0
)){
            printf
("
除數為0
,退出!\n"
);
            exit
(1
) 
;
     }
     d=a[1][0]*a[1][0]+a[1][1]*a[1][1]
;
     a[2][0] = 
(a[0][0] * a[1][0]+a[0][1] * a[1][1]
)/d
;
     a[2][1] = 
(a[0][1] * a[1][0]-a[0][0] * a[1][1]
)/d
;
}
  

【例19.21】使用一個二維數組定義複數結構並編寫複數除法的計算函數。要求不斷從鍵盤輸入兩個複數進行計算,當兩個複數均為0時,結束程序運行。找出下面程序中的錯誤並改正之。


#include <stdio.h>
#include <stdlib.h>
void pe
(double *
);
int main
()
{
     double a[3][2]={0
,0}
;
     double *p=a[0]
;
     for
(;;) {
            printf
("
輸入第1
個複數:"
);
            scanf
("%lf%lfi"
,&p[0]
,&p[1]
);
            printf
("
輸入第2
個複數:"
);
            scanf
("%lf%lfi"
,&p[2]
,&p[3]
);
            if
((p[0]==0
)&&
(p[1]==0
)&&
(p[2]==0
)&&
(p[3]==0
)) {
                 printf
("
再見!\n"
);
                 return 0
;
            }
            if
((p[0]==0
)&&
(p[1]==0
))
                      printf
("
被除數為0
,本次作廢,繼續輸入。\n"
);
            pe
(p
);
            printf
("
計算結果:%lf%+lfi\n"
,p[4]
,p[5]
);
      }
}
void pe
(double *p
)
{
        double d
;
        if 
((p[2]==0
)&&
(p[3]==0
)){
               printf
("
除數為0
,本次作廢,繼續輸入。\n"
);
               return
;
       }
       d=p[2] * p[2]+p[3] * p[3]
;
       p[4] = 
(p[0] * p[2]+p[1] * p[3]
)/d
;
       p[5] = 
(p[1] * p[2] - p[0] * p[3]
)/d
;
       return
;
}
  

先運行程序檢驗給定的幾個條件,以便從中找出錯誤。

運行結果如下:


輸入第1
個複數:
0 0
輸入第2
個複數:
9 8
被除數為0
,本次作廢,繼續輸入。
計算結果:0.000000+0.000000i
輸入第1
個複數:
0 0
輸入第2
個複數:
0 3
被除數為0
,本次作廢,繼續輸入。
計算結果:0.000000+0.000000i
輸入第1
個複數:
0 0
輸入第2
個複數:
0 0
再見!
  

由此可見,滿足前2個條件時,執行了printf語句。被除數為0時,不應調用pe函數。增加


continue
;
  

語句,使它返回到循環的起點即可。第2個問題也是執行了printf語句。可以在pe函數里返回一個值,例如置「p[2]=-1」在主程序中判別這個值,如果「p[2]=-1」,則返回起點。

修改後的源程序如下:


#include <stdio.h>
#include <stdlib.h>
void pe
(double *
);
int main
()
{
     double a[3][2]={0
,0}
;
     double *p=a[0]
;
     for
(;;){
            printf
("
輸入第1
個複數:"
);
            scanf
("%lf%lfi"
,&p[0]
,&p[1]
);
            printf
("
輸入第2
個複數:"
);
            scanf
("%lf%lfi"
,&p[2]
,&p[3]
);
              if
((p[0]==0
)&&
(p[1]==0
)&&
(p[2]==0
)&&
(p[3]==0
))  {
                   printf
("
再見!\n"
);
                   return 0
;
              }
              if
((p[0]==0
)&&
(p[1]==0
)) {
                     printf
("
被除數為0
,本次作廢,繼續輸入。\n"
);
                    continue
;
              }
             pe
(p
);
             if
(p[2]==-1
)   continue
;
             printf
("
計算結果:%lf%+lfi\n"
,p[4]
,p[5]
);
     }
}
void pe
(double *p
)
{
       double d
;
       if 
((p[2]==0
)&&
(p[3]==0
)){
              printf
("
除數為0
,本次作廢,繼續輸入。\n"
);
              p[2]=-1
;
              return
;
       }
       d=p[2] * p[2]+p[3] * p[3]
;
       p[4] = 
(p[0] * p[2]+p[1] * p[3]
)/d
;
       p[5] = 
(p[1] * p[2] - p[0] * p[3]
)/d
;
       return
;
}
  

運行測試如下:


輸入第1
個複數:
0 0
輸入第2
個複數:
2 5
被除數為0
,本次作廢,繼續輸入。
輸入第1
個複數:
2 5
輸入第2
個複數:
0 0
除數為0
,本次作廢,繼續輸入。
輸入第1
個複數:
1 0
輸入第2
個複數:
1 1
計算結果:0.500000-0.500000i
輸入第1
個複數:
0 1
輸入第2
個複數:
1 1
計算結果:0.500000+0.500000i
輸入第1
個複數:
0 1
輸入第2
個複數:
1-1
計算結果:-0.500000+0.500000i
輸入第1
個複數:
0 0
輸入第2
個複數:
0 0
再見!
  

針對這個程序而言,可以將判別除數為0的任務交給主程序,簡化pe程序的設計。也就是只有滿足計算條件的情況下,才調用pe函數。

【例19.22】這是個修改例19.21的程序。程序計算結果正確,但不能退出程序,請找出原因。


#include <stdio.h>
#include <stdlib.h>
void pe
(double *
);
int main
()
{
     double a[3][2]={0
,0}
;
     double *p=a[0]
;
     for
(;;)  {
            printf
("
輸入第1
個複數:"
);
            scanf
("%lf%lfi"
,&p[0]
,&p[1]
);
            printf
("
輸入第2
個複數:"
);
            scanf
("%lf%lfi"
,&p[2]
,&p[3]
);
            if
((p[0]==0
)&&
(p[1]==0
)) {
                    printf
("
被除數為0
,本次作廢,繼續輸入。\n"
);
                    continue
;
            }
            if
((p[2]==0
)&&
(p[3]==0
)){
                    printf
("
除數為0
,本次作廢,繼續輸入。\n"
);
                    continue
;
            }
            if
((p[0]==0
)&&
(p[1]==0
)&&
(p[2]==0
)&&
(p[3]==0
)) {
                    printf
("
再見!\n"
);
                    return 0
;
            }
            pe
(p
);
            printf
("
計算結果:%lf%+lfi\n"
,p[4]
,p[5]
);
    }
}
void pe
(double *p
)
{
     double d
;
     d=p[2] * p[2]+p[3] * p[3]
;
     p[4] = 
(p[0] * p[2]+p[1] * p[3]
)/d
;
     p[5] = 
(p[1] * p[2] - p[0] * p[3]
)/d
;
}
  

【解答】判斷語句的順序錯誤,造成不可能執行。一定要把復合語句


if
((p[0] == 0
) && 
(p[1 ]== 0
) && 
(p[2] == 0
) && 
(p[3] == 0
))
  

放在第1個,以便保證能夠退出。主函數只有這個出口,所以在for循環體之外是不需要return語句的。注意不要多加這條執行不到的「return 0;」語句。

19.3.4 正確設計和使用函數指針

【例19.23】本程序是輸出多項式x 2+5x+8和x 3-6x在區間〔-1,+1〕增長步長為0.1時的所有結果。找出程序中的錯誤。


#include <stdio.h>
double f1
(double x
);
double f2
(double x
);
#define STEP 0.1
int main
( 
)
{
     int i
;
     double x
, 
(*p
)(double
);     //double 
(*p
)(double
)與double 
(*p
)( 
)等效
     for
( i=0
; i<2
; i++
) {
        printf
("
第%d
個方程:\n"
,i+1
);
        if 
( i==0
) p = f1
(x
);
        else       p = f2
(x
);
        for
( x = -1
; x <= 1
; x += STEP
)
               printf
("%f\t%f\n"
, x
, 
(*p
)(x
));
     }
     return 0
;
}
//
函數 f1
( 
)
double f1
(double x
)
{ return 
( x*x + 5*x +8
);}
// 
函數f2
( 
)
double f2
(double x
)
{return
( x*x*x-6*x 
);}
  

給函數指針變量賦值時,只需要給出函數名而不必給出參數。因為語句


p=f1
;
  

是將函數入口地址賦給指針變量p,不涉及實參與形參結合的問題;而寫成


p=f1
(x
);
  

的形式則是錯誤的。

正如數組名代表的是數組起始地址,這裡的函數名則代表函數的入口地址。這裡的p就是指向函數f1的指針變量,它和f1都指向函數的開頭,調用p就是調用f1。與過去所講的變量的重要區別是:它只能指向函數的入口處,而不能指向函數中間的具體指令。因此,*(p+1)、p+n、p--及p++等運算對它都是沒有意義的。

同理,「p=f2(x);」也是錯誤的,應為「p=f2;」。

「double(*p)();」語句僅僅說明定義的p是一個指向函數的指針變量,此函數帶回實型的返回值。但它並不是固定指向哪一個函數的,而只是表示定義了這樣一個類型的變量,專門用來存放函數的入口地址。在程序中把哪一個函數的地址賦給它,它就指向那一個函數。這個程序是使用循環語句分別計算兩個函數。

下面給出部分運行結果。


第1
個方程:
-1.000000       4.000000
-0.900000       4.310000
…              
…
1.000000        14.000000
第2
個方程:
-1.000000       5.000000
…              
…
0.900000        -4.671000
1.000000        -5.000000
  

【例19.24】這是一個使用指向函數的函數指針變量作為參數的例子。第1次掃瞄有警告信息,請消除這個警告信息。


#include <stdio.h>
void all 
(int
, int
, int 
(*
)());
int max
(int
,int 
), min
(int
,int 
),mean
(int
,int 
);
void main
( 
)
{
     int a
, b
;
     printf
("
輸入a
和b
的值:"
);
     scanf
("%d %d"
,&a
,&b
);
     printf
("max="
);
     all
(a
,b
,max
);
     printf
("min="
);
     all
(a
, b
, min
);
     printf
("mean="
);
     all
(a
,b
,mean
);
}
void all 
(int x
, int y
,int 
(*func
)(int x
, int y
))
{ printf
("%d\n"
,(*func
)(x
,y
)); }
int max
(int x
, int y
)
{
      if 
( x > y 
)  return x
;
      else          return y
;
}
int min
(int x
, int y
)
{
     if 
( x < y 
)   return x
;
     else           return y
;
}
int mean
(int x
, int y
)
{ return
( 
(x+y
)/2 
);}
  

【解答】不要認為是用第3句程序存在問題。用int在一行聲明3個函數是完全正確的。如果使用「void all(int x,int y,int(*func)(int,int)」定義,則聲明原型時必須與定義的參數形式一致,即應該使用


void all 
(int
, int
, int 
(*
)(int
,int
));
  

其實,聲明時不帶參數最方便。所以可以修改定義為


void all 
(int x
, int y
,int 
(*func
)( 
))
{ printf
("%d\n"
,(*func
)(x
,y
)); }
  

這裡是用函數指針作為函數參數,所以應將函數指針看做形參,函數名看做實參,用實參直接代替形參。

如果要按部就班將函數名賦給函數指針,在主程序中聲明一個函數指針即可。下面是使用這種方法的源程序。


#include <stdio.h>
void all 
(int
, int
, int 
(*
)());
int max
(int
,int 
), min
(int
,int 
),mean
(int
,int 
);
int main
( 
)
{
     int a
, b
;
     int 
(*p
)();          //
聲明函數指針
     printf
("
輸入a
和b
的值:"
);
     scanf
("%d %d"
,&a
,&b
);
     p=max
;          //
指向一個具體函數
     printf
("max="
);
     all
(a
,b
,p
);          //
作為函數的參數
     printf
("min="
);
     p=min
;
     all
(a
, b
, p
);
     printf
("mean="
);
     p=mean
;
     all
(a
,b
,p
);
     return 0
;
}
void all 
(int x
, int y
,int 
(*func
)())
{ printf
("%d\n"
,(*func
)(x
,y
)); }
int max
(int x
, int y
)
{
     if 
( x > y 
)  return x
;
     else          return y
;
}
int min
(int x
, int y
)
{
     if 
( x < y 
)  return x
;
     else          return y
;
}
int mean
(int x
, int y
)
{   return
( 
(x+y
)/2 
);}
  

運行示例:


輸入a
和b
的值:
32 98
max=98
min=32
mean=65
  

【例19.25】分別使用聲明函數指針和直接實參和形參結合的兩種方法,編寫求函數10x 2-9x+2在區間〔0,1〕內x以0.01的增量變化的最小值。

先聲明函數指針,將被調函數賦給指針的參考程序如下。


#include <stdio.h>
double s1=0.0
;
double s2=1.0
;
double step=0.01
;
double func
( 
),MinValue
(double
(*
)( 
));
int main 
( 
)
{
    double 
(*p
)( 
);               //
定義函數指針
    p=func
;               //
指向目標函數
    printf
("
最小值是:%2.3f\n"
, MinValue
(p
));
    return 0
;
}
double func
(double x
)               //
目標函數
{return 
(10*x*x-9*x+2
);}
double MinValue
(double
(*f
)( 
))               //
定義求最小值函數,它包括函數指針
{
     double x=s1
, y=
(*f
)(x
);               //
定義變量y
與func
函數返回類型一致
     while
( x <= s2 
){
           if
( y > 
(*f
)(x
) 
)
                 y=
(*f
)(x
);
           x += step
;
      }
      return y
;
}
  

運行結果為


最小值是:- 0.025
  

直接將函數指針作為參數。


#include <stdio.h>
double s1=0.0
;
double s2=1.0
;
double step=0.01
;
double func
( 
),value
(double
(*
)( 
));
int main 
( 
)
{
     printf
("
最小值是:%2.3f\n"
, value
(func
));     //
直接使用目標函數作為參數
     return 0
;
}
double func
(double x
)     //
目標函數
{return 
(10*x*x-9*x+2
);}
double value
(double
(*f
)( 
))     //
定義求最小值函數,它包括函數指針
{
    double x=s1
, y=
(*f
)(x
);
    while
( x <= s2 
){
           if
( y > 
(*f
)(x
) 
)
                   y=
(*f
)(x
);
           x += step
;
     }
     return y
;
}
  

19.3.5 如何解讀函數聲明

雖然程序組合方式的定義都很完備,但這些定義有時也容易引起混淆,甚至與人們的直覺相悖。有些語法結構的用法與意義與人們想當然的認識也不一致,例如運算符的優先級、if語句和函數調用等。

這裡重點討論一下如何理解函數聲明問題。假如一個函數原型聲明如下:


(*
( void 
( * 
) 
( 
) 
) 0 
) 
( 
);
  

如何理解這個函數的含義呢?

構造函數表達式應遵循的一條簡單規則:按照使用的方式來聲明。

1.聲明一個給定類型的變量

任何C變量的聲明都由兩部分組成:類型以及一組類似表達式的聲明符。從表面上看,聲明符與表達式有些類似,對它求值應該返回一個聲明中給定類型的結果。最簡單的聲明符就是單個變量。下面的聲明


float f 
, g
;
  

的含義是:當對其求值時,表達式f和g的類型為浮點數(float)類型。因為聲明符與表達式的相似,所以可以在聲明符中任意使用括號。按此推理,可知


float   
( 
( f 
) 
);
  

的含義是:當對其求值時,表達式((f))的類型為浮點類型,由此可推知,f也是浮點類型。而聲明


float   f f 
( 
);
  

的含義是:表達式f f()的求值結果是一個浮點數,也就是說,f f是一個返回值為浮點類型的函數。類似地,


float  *pf
;
  

聲明的含義是:*pf是一個浮點數,也就是說,pf是一個指向浮點數的指針。

就像在表達中進行組合一樣,也可以把以上這些形式在聲明中組合起來。因此,


float  * g 
( 
) 
, 
( *h 
) 
( 
);
  

表示*g()與(*h)()是浮點表達式。因為()的結合優先級高於*,也就是*(g())。這顯然表明g是一個函數,該函數的返回值類型為指向浮點數的指針。同理,可以分析(*h)()中後面的括號代表函數,而(*h)代表函數指針,h是一個函數指針,h所指向的函數的返回值為浮點類型。

2.類型轉換符

一旦知道了如何聲明一個給定類型的變量,就很容易得到該類型的類型轉換符了。只需要把聲明中的變量名和聲明尾部的分號去掉,再將剩餘的部分用一對括號全部括起來即可。例如,下面的聲明


float  
( *h 
) 
( 
);
  

表示h是一個指向返回值為浮點類型的函數指針,因此


( float  
( * 
) 
( 
)  
)
  

就表示一個「指向返回值為浮點類型的函數指針」的類型轉換符。

3.分析表達式(*(void(*)())0)()的含義

(1)假定變量fp是一個函數指針,則*fp就是該指針所指向的函數,所以調用該函數的方式是:


(*fp
) 
( 
);
  

注意:必須用一對括號將*fp括起來,否則*fp()實際上與(*fp())的含義完全一樣。因為函數運算符「()」的優先級高於運算符,所以(*fp)的含義才與其區別開來。

(2)找一個恰當的表達式替換fp。設計是想在計算機啟動時,硬件將調用首地址為0位置的函數。顯然,根據定義寫出


(*0
) 
( 
);
  

的聲明,C編譯器並不認可。因為運算符「*」必須要有一個指針來做操作數。而且這個指針還應該是一個函數指針,以便保證經運算符「*」作用後的結果能夠作為函數被調用。因此,必須對0進行類型轉換,轉換後的類型可以描述為「指向返回值為void類型的函數指針」。

如果fp是一個指向返回值為void類型的函數指針,那麼(*fp)()的值為void,fp的聲明如下:


void 
(*fp
) 
( 
);
  

這就可以保證用「(*fp)();」完成調用存儲位置為0的函數。

但這種寫法的代價是多些了一個「啞」變量。

(3)因為一旦知道如何聲明一個變量,自然也就知道如何對一個常數進行類型轉換,將其轉換為該變量的類型。只需在變量聲明中將變量名去掉即可,將常數0轉換為「指向返回值為void類型的函數指針」類型,可以寫作:


(void 
(*
) 
( 
)) 0
  

用(void(*)())0替換(*fp)()中的fp,從而得到:


(*
(void 
(*
) 
( 
)) 0 
) 
( 
);
  

尾部的分號使得表達式成為語句。

當然,使用typedef能夠使表述更加清晰。


typedef void 
( *funcptr 
) 
( 
);
( *funcptr 
) 0 
) 
( 
);
  

4.signal函數

signal庫函數里signal函數原型比較複雜。一般表示為:


void 
( * signal 
( int 
, void 
(* 
)(int
)))(int
);
  

signal函數接受兩個參數:一個是代表需要「被捕獲」的特定signal的整數值;另一個是指向用戶提供的函數的指針,該函數用於處理「捕獲到」的特定signal,返回值類型為void。

假設用戶的信號處理函數為:


void sigfunc
( int n
)
{
     // 
特定信號處理部分
}
  

由此可得sigfunc函數的聲明如下:


void sigfunc
( int 
)
  

聲明一個指向sigfunc函數的指針變量sfp,因為sfp指向sigfunc函數,則*sfp就代表了sigfunc函數,從而*sfp可以被調用。假定sig是一個整數,則(*sfp)(sig)的值為void類型,因此可以聲明sfp如下:


void 
(*sfp
) 
( int 
) 
;
  

因為signal函數的返回值類型與sfp的返回類型一樣,上式也就聲明了signal函數,即


void 
(*signal
(something
) 
) 
( int 
) 
;
  

something代表signal函數的參數類型,這裡還沒有完成對它的聲明。

現在已經完成的聲明是:傳遞適當的參數調用signal函數,對signal函數的返回值(為函數指針類型)解除引用(dereference),然後傳遞一個整數參數調用解除引用後所得函數,最後返回值為void類型。因此,signal函數是一個指向返回值為void類型函數的指針。

現在是要確定signal函數的參數something。signal函數接受兩個參數:一個是整型的信號編號,另一個是指向用戶定義的信號處理函數的指針。前面已經定義了指向用戶定義的信號處理函數的指針sfp如下:


void 
(*sfp
) 
( int 
) 
;
  

sfp的類型可以通過將上面的聲明中的sfp去掉而得到,即void(*)(int)。此外,signal函數的返回值是指向調用前的用戶定義的信號處理函數的指針,這個指針的類型與sfp指針類型一致。參數something就是如下形式:


int 
, void 
( * 
) 
( int 
)
  

由此可知,聲明signal函數如下:


void 
(*signal
( int 
, void 
( * 
) 
( int 
) 
) 
) 
( int 
) 
;
  

同樣,使用typedef可以簡化上面的函數聲明。


typedef void 
( *HANDLER 
) 
( int 
);
HANDLER signal 
( int
, HANDLER 
);