(*(void(*) ()) 0)();
恐怕像這樣的表示式,無論是新程式設計師,還是經驗豐富的老程式設計師,都會感到不寒而慄。然而,構造這類表示式其實只有一條簡單的規則:按照使用的方式來宣告。float f(); float *pf;在上面的程式碼中,很顯然,f 是一個返回值為浮點型別的函數;而 pf 則是一個指向浮點數的指標。如果將它們簡單地組合起來,就可以得到如下兩種宣告方式:
float *f1(); float (*f2)();上面兩者的區別在於:因為“()”結合優先順序高於“*”,也就是說“*f1()”等價於“*(f1())”,即f1是一個函數,它返回值型別為指向浮點數的指標;同理,f2 是一個函數指標,它所指向的函數的返回值為浮點型別。
float (*f2)();因為 f2 是一個指向返回值為浮點型別的函數的指標,因此,該型別的型別轉換符如下:
(float (*)())
即它表示一個“指向返回值為浮點型別的函數的指標”的型別轉換符。(*(void(*) ())0)();
在這裡假定變數 fp 是一個函數指標,顯然“*fp”就是該指標所指向的函數。當然,“(*fp)()”就是呼叫該函數的方式(在 ANSI C 標準中,允許程式設計師簡寫為“fp()”這種形式)。在“(*fp)()”中,“*fp”兩側的括號非常重要,因為“()”結合優先順序高於“*”。如果“*fp”兩側沒有括號,那麼“*fp()”實際上與“*(fp())”的含義完全一致,ANSI C 把它作為“*((*fp)())”的簡寫形式。(*0)();
大家都知道,函數指標變數不能是一個常數,很顯然上式並不能生效。因此,上式中的0必須被轉化為函數指標,一個指向返回值為 void 型別的函數的指標。也就是說,需要將 fp 的宣告修改成如下形式:void (*fp)();
這樣,就可以得到該型別的型別轉換符:(void (*)())
現在將常數 0 轉型為“指向返回值為 void 的函數的指標”型別就可以寫成如下形式:(void (*)())0
最後,使用“(void(*)())0”來替換“(*fp)(0”中的fp或“(*0)()”中的0,就可以很簡單地得到下面的表示式:(*(void (*)())0)();
為了便於大家理解,在這裡繼續對“(*(void(*)())0)()”做如下 4 點說明:/*函數原型*/ char *Memcopy(char *dest, const char *src, size_t size); /*函數定義*/ char *Memcopy(char *dest, const char *src, size_t size) { assert((dest != NULL) && (src != NULL)); char *retAddr = dest; while (size --> 0) { *(dest++) = *(src++); } return retAddr; }在上面的程式碼中,當呼叫 Memcopy() 函數時,編譯器就會檢查呼叫函數的實參是不是 3 個?每個引數的型別是否匹配?函數的返回型別是否正確?如果編譯程式發現函數的呼叫或定義與函數原型不匹配,編譯程式就會報告出錯或發出警告訊息。
#include <stdio.h> #include <stdlib.h> typedef int ElementType; typedef struct node { ElementType data; struct node *next; }StackNode, *LinkStack; void InvertedSequence(int num) { int i=0; int result=0; LinkStack ls; // 初始化 ls = (LinkStack)malloc(sizeof(StackNode)); ls->next = NULL; printf("資料輸入為:n"); for(i=0; i<num; i++) { // 入棧 StackNode *temp; temp = (StackNode *)malloc(sizeof(StackNode)); if(temp != NULL) { temp->data = i; temp->next = ls->next; ls->next = temp; printf("%d ",i); } } printf("n資料輸出為:n"); while (ls->next != NULL) { // 出棧 StackNode *temp = ls->next; result = temp->data; ls->next = temp->next; free(temp); printf("%d ",result); } printf("n"); } int main(void) { InvertedSequence(20); return 0; }在 InvertedSequence(int num) 方法中,我們實現了鏈棧的常用操作,即包括初始化、入棧與出棧等操作,其執行結果為:
typedef int BOOL; #define TRUE 1 #define FALSE 0 #define STACK_SIZE 100 typedef int ElementType; typedef struct node { ElementType data; struct node *next; }StackNode,*LinkStack; // 初始化 void InitStack(LinkStack ls) { ls->next = NULL; } // 是否為空 BOOL IsEmpty(LinkStack ls) { if(ls->next == NULL) { return TRUE; } else { return FALSE; } } // 入棧 BOOL Push(LinkStack ls, ElementType element) { StackNode *temp; temp = (StackNode *)malloc(sizeof(StackNode)); if(temp == NULL) { return FALSE; } temp->data = element; temp->next = ls->next; ls->next = temp; return TRUE; } // 出棧 BOOL Pop(LinkStack ls, ElementType *element) { if(IsEmpty(ls)) { return FALSE; } else { StackNode *temp = ls->next; *element = temp->data; ls->next = temp->next; free(temp); return TRUE; } } void InvertedSequence(int num) { int i=0; int result=0; LinkStack ls; ls = (LinkStack)malloc(sizeof(StackNode)); // 初始化 InitStack(ls); printf("資料輸入為:n"); for(i=0; i<num; i++) { // 入棧 Push(ls,i); printf("%d ",i); } printf("n資料輸出為:n"); while (!IsEmpty(ls)) { // 出棧 Pop(ls,&result); printf("%d ",result); } printf("n"); }現在,通過對比以上兩端程式碼,可以很容易看出,後段程式碼不僅簡單易讀、易維護,而且其函數也具有更好的可複用性。嚴格遵循函數的功能單一原則,這樣不但能夠讓你更好地命名函數,也使理解和閱讀程式碼變得更加容易。
void Init (void) { /* 初始化矩形的長與寬 */ Rect.length = 0; Rect.width = 0; /* 初始化“點”的坐標 */ Point.x = 0; Point.y = 0; }很顯然,上面的函數 Init(void) 設計是不合理的,因為矩形的長、寬與點的坐標基本沒有任何關聯。因此,我們應該將其抽象為如下兩個函數:
/* 初始化矩形的長與寬 */ void InitRect(void) { Rect.length = 0; Rect.width = 0; } /* 初始化“點”的坐標 */ void InitPoint(void) { Point.x = 0; Point.y = 0; }
void Init( void ) { /*本地初始化*/ ... InitRemote(); } void InitRemote(void) { /*遠端初始化*/ ... }從表面上看,上面的 Init(void) 函數主要完成本地初始化與遠端初始化工作,在其功能實現上沒什麼不妥之處。但從設計觀點看,卻存在著一定的缺陷。從 Init(void) 函數中,我們可以看出,本地初始化與遠端初始化的地方是相當的。因此,如果遠端初始化作為獨立的函數存在,那麼本地初始化也應該作為獨立的函數存在。
void Init(void) { InitLocal(); InitRemote(); } void InitLocal(void) { /*本地初始化*/ ... } void InitRemote(void) { /*遠端初始化*/ ... }
int Max(int x,int y) { return(x>y?x:y); } int Min(int x,int y) { return(x<y?x:y); }當然,也可以使用宏來代替上面的函數,程式碼如下:
#define MAX(x,y) (((x) > (y)) ? (x) : (y)) #define MIN(x,y) (((x) < (y)) ? (x) : (y))在 C 程式中,我們可以適當地用宏程式碼來提高執行效率。宏程式碼本身不是函數,但使用起來與函數相似。前處理器用複製宏程式碼的方式代替函數呼叫,省去了引數壓棧、生成組合語言的 CALL 呼叫、返回引數、執行 return 等過程,從而提高了執行速度。
/*希爾排序法*/ void ShellSort(int v[],int n) { int i,j,gap,temp; for(gap=n/2;gap>0;gap /= 2) { for(i=gap;i<n;i++) { for(j=i-gap;(j >= 0) && (v[j] > v[j+gap]);j -= gap ) { temp=v[j]; v[j]=v[j+gap]; v[j+gap]=temp; } } } } /* 氣泡排序法 */ void BubbleSort (int v[],int n) { int i,j,temp; for(j=0;j<n;j++) { for(i=0;i<(n-(j+1));i++) { if(v[i]>v[i+1]) { temp=v[i]; v[i]=v[i+1]; v[i+1]=temp; } } } }在上面的範例程式碼中,函數 ShellSort(int v[],int n) 與函數 BubbleSort(int v[],int n) 分別實現了希爾排序與氣泡排序的功能。仔細觀察這兩個簡單的排序函數,不難發現,無論是 ShellSort(int v[],int n) 函數,還是 BubbleSort(int v[],int n) 函數,都會執行交換操作。因此,我們可以將它們的交換操作程式碼抽取出來,獨立成一個新的函數,範例程式碼如下:
/*交換*/ void Swap(int *i, int *j) { int temp; temp=*i; *i=*j; *j=temp; }這樣,抽取出 Swap(int*i,int*j) 函數之後,不僅能夠避免不必要的程式碼重複,便於以後維護與升級程式碼,而且能夠使我們的程式碼具有更大的複用價值。
/*希爾排序法*/ void ShellSort(int v[],int n) { int i,j,gap; for(gap=n/2;gap>0;gap /= 2) { for(i=gap;i<n;i++) { for(j=i-gap;(j >= 0) && (v[j] > v[j+gap]);j -= gap ) { Swap(&v[j],&v[j+gap]); } } } } /* 氣泡排序法 */ void BubbleSort (int v[],int n) { int i,j; for(j=0;j<n;j++) { for(i=0;i<(n-(j+1));i++) { if(v[i]>v[i+1]) { Swap(&v[i],&v[i+1]); } } } }
long fab(const int index) { if(index == 1 || index == 2) { return 1; } else { return fab(index-1)+fab(index-2); } }遞回之所以能實現,是因為函數的每個執行過程都在堆疊中有自己的形參和區域性變數的副本,而這些副本和函數的其他執行過程毫不相干。所以遞回函數有一個最大的缺陷,那就是增加了系統的開銷。因為每呼叫一個函數,系統就需要為函數準備堆疊空間用於儲存引數資訊,如果頻繁進行遞回呼叫,系統需要為其開闢大量的堆疊空間。
long fab(const int index) { if(index == 1 || index == 20) { return 1; } else { long l1 = 1L; long l2 = 1L; long l3 = 0; /*疊代求值*/ for(int i = 0;i < index-2;i ++) { l3 = l1 + l2; l1 = l2; l2 = l3; } return l3; } }在很多時候,因為遞回需要系統堆疊,所以空間消耗要遠比非遞回程式碼大很多。而且,如果遞迴深度太大,可能會導致系統資源不夠用。因此大家都有這樣一個觀點:能不用遞迴演算法就不用遞迴演算法,遞迴演算法都可以用疊代演算法來代替。