C語言不完全類型與延遲定義

來源:互聯網
上載者:User

標籤:c語言   不完全類型   延遲定義   模組化   

一直以為我的C語言學的還可以,雖說不是出神入化,但是至少比較熟悉吧。但是前一段時間看了一篇推文,再百度了一下C語言不完全類型。發現我居然C語言不完全類型和用途甚廣的延遲定義都沒概念。這兩天仔細查閱了相關概念並用代碼實驗了一下。

本文結構如下:

  •  C語言不完全類型概念介紹
  • 一個故事
  • 延遲定義的優點
  • 思考…

C語言不完全類型

不完全類型也就是不知道變數的所有的類型資訊。比如可以聲明一個數組,但是不給出該數組的長度;聲明一個指標,但是不給出該指標的類型;聲明一個結構體類型,但是不給出完整的結構體定義,只說它是一個結構體。但是最終你必須得給出完整的類型資訊。要不然編譯會報錯的。編譯器在編譯某個單元時,如果遇到一個不完全類型的定義的類型或變數(假設它叫p),它會把這當作正常現象,然後繼續編譯該單元,如果在本單元內找不到p完整的類型資訊,它就去其它編譯單元找。如果把整個編譯過程分為編譯、連結兩個過程。在編譯階段遇到不完全類型是正常的,但是在連結過程中,所有的不完全類型必須存在對應的完整類型資訊,否則報錯。

舉個例子,下面的代碼先聲明了一個不完全類型的變數字元數組str,沒有給出它的長度資訊。然後再定義了一次str數組,這次給出的長度資訊。

char str[];//不完全類型定義

char str[10];//終於遇到了str數組的完整類型資訊,編譯器鬆了一口氣

注意:不完全類型定義不適合局部變數,如果把上面兩行代碼放在一個函數體中,會出現符號重定義錯誤。

不完全類型由於不包含具體的類型資訊,所以不能通過sizeof來獲得其大小。(編譯器君的旁邊:我連它的完整類型都不知道,我怎麼告訴你它的大小。)下面的代碼不能編譯通過。它會報錯 error: invalid application of `sizeof‘ to an incomplete type 不能對不完全類型使用sizeof。

#include<stdio.h>

 

char str[];

int n =sizeof(str);

char str[10];

 

int main()

{

      printf("%d ",n);

      return 0;

}

如果把int n = sizeof(str)放到charstr[10]後面就沒事了。

再舉一個結構體的例子。下面的代碼先聲明了一個不完全類型的結構體s。然後又定義了該結構體。

struct s;

struct s{

      int a;

      int b;

};


一個故事,不完全類型的應用:延遲定義

(哈哈,終於要講應用了,好激動!)

C語言的不完全類型的一般用作模組化編程中。現在假設你要實現一個儲存字元的棧給你的程式員隊友用。於是你寫出了下面的代碼。

//Stack.h

typedef structNode{

      char       data;

      struct Node*       next;

}Node;

 

typedef struct {

      Node*    top;//只需要棧頂指標

      int num;

}Stack;

/*為了快速返回棧中元素的個數,你用一個int變數來記錄棧中元素的個數,在入棧和出棧時對num進行增減操作,這樣在返回棧中元素的個數的時候,就可以直接返回num變數了。*/

 

/構造一個空棧S

voidInitStack(Stack **s);

//銷毀棧S

voidDestroyStack(Stack **s);

//判斷棧是否為空白

intStackEmpty(Stack *s);

//返回棧的長度

intStackLength(Stack *s);

//用e返回棧頂元素

int GetTop(Stack*s,char *e);

//入棧

void Push(Stack*s,char e);

//彈出棧頂元素,並用e返回其值

int Pop(Stack*s,char *e);

為了不暴露實現細節,你將實現棧操作的各種實現代碼封裝成到了一個庫檔案中(靜態、動態都行)。然後將這個.h檔案和庫檔案信心滿滿的提交給了隊友。並暗示自己“我果然是個牛x的程式員,小美一定會崇拜我、尊敬我、對我欲罷不能!不行,我要謙虛,當作什麼也沒發生”。

幾個小時候,隊友皺著眉頭過來了。你心想“不對啊,怎麼不是崇拜的眼神”。然後發生了下面的一段對話。

“你封裝的什麼啊?連個棧中元素的個數都統計不對”,隊友很生氣

“怎麼可能?為了快速返回棧中元素的個數,我特意用了一個int變數來儲存棧中元素的個數”你也很生氣,明明用一個單獨變數來儲存元素個數是你的得意之處,居然被人說不對。

“我知道,可就是不對”,隊友堅持說你不對。

然後為了找出問題,你和隊友一起檢查他寫的代碼。你發現他寫了如下代碼。

Push(s);

++(s->num);

Pop(s,&e);

--(s->num);

原來隊友在壓棧和出棧時都對棧s的num進行了加減操作。

你:“為什麼你要動我的num?”

隊友:“你的num不是用來儲存棧中元素的個數的嗎?我壓棧、出棧當然要對它進行增減啊!”

你:“我在實現代碼中已經這麼做了。”

原來你以為理所當然的事,隊友卻和你想的不一樣。然後你告訴他不要多事,num的大小由你來維持。你們終於又可以一起愉快的玩耍了。

但是你在隊友的代碼裡還是發現像下面一些不爽的代碼,這讓你感到膈應。

e =s->top->data;

int len =s->num;

為了獲得棧頂元素,他沒有用你提供的GetTop函數,而是自作主張的直接通過指標直接存取棧內部的top指標。而且沒用你給的StackLength函數,而是直接存取棧結構體的num成員。雖然你可以和他溝通,讓他不要自己去訪問你的實現細節。但是你不能保證他會完全聽你的。能不能從技術上解決這個問題呢?個人覺得,團隊協作時,能從技術上解決的事,不必從溝通上解決。這樣可以減少團隊溝通成本。扯多了,回到剛才的問題——我們能否從技術上解決這個問題?

問題分析和解決方案

回頭看看我們剛才寫的代碼。隊友會直接存取我們封裝的內部細節,是因為他看到了我們給的.h檔案中結構體的詳細定義。那麼能不能去掉.h檔案中Stack結構體的定義呢?顯然不行,因為我們給出的幾個函數介面都要用到Stack類型的參數,如果使用者看不到Stack的定義,那他怎麼定義Stack類型的變數然後傳遞給這些介面函數呢?答案是,我們給出Stack結構體類型的定義,但是不給出Stack結構體的詳細資料。也就是前面講到的不完全類型定義。改進後的代碼如下。

//Stack.h

typedef structSqStack Stack;

//…各個函數介面定義

//Stack.c

typedef structNode{

      char       data;

      struct Node*       next;

}Node;

struct SqStack{

      Node*    top;

      int num;

};

在改進後的代碼中,我們只是定義了一個不完全類型struct SqStack結構體,並用typedef將其和Stack名的類型等價。而將SqStack結構體的詳細定義放到了.c檔案中。這樣將.h檔案和庫檔案提交給隊友後,他再也看不到Stack的詳細資料了。這下,他就沒有直接存取結構體內部的衝動了。(看你丫還怎麼訪問?老老實實用哥提供的函數介面吧!)隊友看不到Stack結構體的內部是怎麼定義的,自然也不知道怎麼訪問,而且任何通過Stack類型指標變數的訪問方式都會讓編譯器報錯。這下,我再也不用擔心隊友的愚蠢了。

上面講到的,在標頭檔中只定義一個結構體的不完全類型,而將結構體的詳細定義延遲到在.c檔案的方式就叫“延遲定義”。


延遲定義帶來的其它好處

在上面的執行個體過程中,雖然我們是為了隱藏實現細節才使用的延遲定義。但是延遲定義也帶來了其它好處。另一個好處就是,我們可以以很小的工作量代價來更改實現。比如我們發現Stack結構的實現有bug需要修正,或者我們想將內部的棧的鏈式儲存改為順序儲存。我都可以直接在.c檔案中修改,然後重新編譯產生新的庫檔案,不需要修改.h檔案,因為我們的介面函數並不需要變。將庫檔案提交給使用者,使用者只需替換原來舊的庫檔案,而不需要修改代碼。如果是動態連結程式庫,使用者只需替換庫檔案就行。如果是靜態庫,使用者只需替換庫後重新連結一下。至始至終,使用者都不需要修改代碼,不需要重新編譯自己的用戶端代碼。

如果沒有用延遲定義,將內部細節放在.h檔案中,那修改細節後,使用者的用戶端代碼也需要重新編譯。



 延遲定義的優點

我們來總結一下延遲定義的優點:

1.       隱藏了內部實現細節,強制使用者按介面規則訪問。減少溝通成本。

2.       便於修改。

上面兩點都是實現模組化編程所必須的。而且個人認為,站在客戶的角度,知道的細節越少越好,知道的越多,要記憶和思考的東西也越多。就像電視劇中的經典台詞:“有時候知道的太多並不是好事”。角色被人幹掉的理由也是“你知道的太多了”,或者“你知道了你不該知道的事情”。我只按照既定的規則來訪問別人提供的代碼,有什麼問題直接問別人,而不是看實現代碼。按照既定規則來訪問也便於職責劃分。如果非要總結延遲定義帶來的第3個好處,個人認為是:便於職責劃分,清晰職責邊界。

 

 

思考…

Q:既然延遲定義便於模組化編程,那C++、Java等物件導向的語言中有用到延遲定義類似的技術嗎?

A:當然有,只是物件導向語言中不叫“延遲定義”。而是叫封裝、存取權限、介面和多態。類的封裝和存取權限可以阻止客戶訪問類的實現細節,而介面和多態可以隱藏實現細節。當然它們的好處不只這些。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.