C語言探索之旅 | 第二部分第一課:模塊化編程

1240
C語言之父 Dennis Ritchie

-- 簡書作者 謝恩銘 轉載請註明出處

第二部分第一課:模塊化編程


上一課是C語言探索之旅 | 第一部分第十課:練習題+習作,至此,我們【C語言探索之旅】的第一部分結束了。

大家肯定注意到了,今天的封面圖片我用了老爺子 Dennis Ritchie(丹尼斯.裏奇。C語言之父)的壁紙,略表敬仰和懷念之情。

還有一個原因:因爲這一部分要開始難起來了,開始學習C語言的高級技術。

圖片上的The Legend,是英語“傳奇”的意思。Dennis Ritchie是2011年去世的。

同年,喬布斯喬幫主也離開了我們。

不過不要忘了這個“喬布斯站在其肩頭”的偉大的人,Dennis Ritchie:C語言之父(近代很多編程語言的靈感都來自C語言,例如C++,Java,Objective-C)。

Dennis Ritchie還算是Unix操作系統之父(與Ken Thompson一起,Unix是近代不少操作系統的先驅,其中Linux系統吸取了Unix系統的特點,蘋果的Mac OS系統底層基於改動過的Unix,Android系統底層基於改動過的Linux系統)。

Dennis Ritchie在發明了C語言後,就用C語言重寫了原本用彙編語言編寫的Unix系統。

而蘋果的MAC OS和iOS系統使用了諸如C語言,Objective-C,C++等來編寫。

以後應該拍一系列早期計算機黑客的傳奇的電影,特別是丹尼斯.裏奇,肯定得拍一部紀錄片。如果沒有Dennis Ritchie,我們今天很多技術都沒法實現。

網上有一篇圖文介紹,叫《Unix英烈傳:圖文細數十五位計算先驅》
http://www.linuxidc.com/Linux/2013-08/89395p2.htm

還有最後一個原因:老爺子長得帥啊,給我們程序員長臉啊。

《灌籃高手》裏櫻木花道稱呼安西教練爲老爺子,願我們的Dennis Ritchie教練也“安息”。

好了,話休絮煩,我們迴歸正題。

第二部分開始


話說上一課是第一部分最後一課,現在開始第二部分的探索之旅!

在這一部分中,我們會學習C語言的高級技術。這一部分內容將是一座高峯,會挺難的。但是我們一起翻越。

俗語說得好:一口是吃不成一個胖子的。

但是小口小口,慢慢吃,還是能吃成胖子的嘛。所以要細水長流,肥油慢積。一路上有你(油膩)~

一旦你跟着我們的課程一直到這一部分的結束,你將會掌握C語言的核心技術,也可以理解大部分C語言寫的程序了。

之後在第三部分,我們就會一起來學習C語言圖形編程,構建C語言的2D遊戲等等。

到目前爲止我們的程序都只是在一個main.c文件裏搗騰,因爲我們的程序還很短小,這也足夠。

但如果之後你的程序有了十多個函數,甚至上百個函數,那麼你就會感到全部放在main.c一個文件裏是多麼擁擠和混亂。

正因爲如此,計算機科學家纔想出了模塊化編程。原則很簡單:與其把所有源代碼都放在一個main.c當中,我們將把它們合理地分割,放到不同的文件裏面。

函數原型


到目前爲止,寫自定義函數的時候,我們都要求大家暫時把函數寫在main函數的前面。

這是爲什麼呢?(唉呀媽呀,想到了蔡明和郭達那個小品...)

因爲這裏的順序是一個重要的問題。如果你將自己定義的函數放置在main函數之前,電腦會讀到它,會知道這個函數,當你在main函數中調用這個函數時,電腦知道這個函數,也知道到哪裏去執行它。

但是假如你把這個函數寫在main函數後面,那你在main函數裏調用這個函數的時候,電腦就不認識它了,你可以自己寫個程序測試一下。是的,很奇怪吧?這絕對有點任性的。

那你會說:“C語言豈不是設計得不好麼?”

我完全同意(別讓上面的老爺子聽到了...)。但是請相信,這樣設計應該也是有理由的,計算機先驅們早就想到了,也提出瞭解決之道。

下面我們就來學一個新的知識點,藉着這個,你可以把你的自定義函數放在程序的任意位置。不必操心總是好事。

用來聲明一個函數的“函數原型”


我們會聲明我們的函數,用我們所說的術語:函數原型(prototype)。

就好比你對電腦發出一個通知:“看,我的函數的原型在這裏,你給我記住啦”。

我們來看一下我們上一課舉的一個函數的例子(計算矩形面積):

double rectangleArea(double length, double width)
{
  return length * width;
}

怎麼來聲明我們上面這個函數的原型呢?

  1. 複製,黏貼第一行

  2. 在最後放上一個分號;

  3. 把這一整行放置在main函數前面

簡單嗎?現在你就可以把你的函數的定義放在main函數後面啦,電腦也會認識它,因爲你在main函數前面已經聲明過這個函數了。

你的程序會變成這樣:

#include <stdio.h>
#include <stdlib.h>

// 下面這一行是rectangleArea函數的函數原型
double rectangleArea(double length, double width);

int main(int argc, char *argv[])
{
  printf("長爲10,寬爲5的矩形面積 = %f\n", rectangleArea(10, 5));
  printf("長爲3.5,寬爲2.5的矩形面積 = %f\n", rectangleArea(3.5, 2.5));
  printf("長爲9.7,寬爲4.2的矩形面積 = %f\n", rectangleArea(9.7, 4.2));

  return 0;
}

// 現在我們的rectangleArea函數就可以放置在程序的任意位置啦
double rectangleArea(double length, double width)
{
  return length * width;
}

與原先的程序相比有什麼改變呢,其實就是在程序的開頭加了函數的原型而已(記得不要忘了那個分號)。

函數的原型,其實是給電腦的一個提示或指示。比如上面的程序中,函數原型

double rectangleArea(double length, double width);

就是對電腦說:“老兄,存在一個函數,它的輸入是哪幾個參數,輸出是什麼類型”,這樣就能讓電腦更好地管理。

多虧了這一行代碼,現在你的rectangleArea函數可以置於程序的任何位置啦。

記得:最好養成習慣,對於C語言程序,總是定義了函數,再寫一下函數的原型。

那麼不寫函數原型行不行呢?

也行。只要你把每個函數的定義都放在main函數之前,但是你的程序慢慢會越來越大,等你有幾十或者幾百個函數的時候,你還顧得過來麼?

所以養成好習慣,不吃虧的。

你也許注意到了,main函數沒有函數原型。因爲不需要,main函數是每個C程序必須的入口函數。(人家“有權”,跟編譯器關係好,編譯器對main函數很熟悉,是經常打交道的哥們,所以不需要函數原型來“介紹”main函數)。

還有一點,在寫函數原型的時候,對於圓括號裏的函數參數,名字是不一定要寫的,可以只寫類型。

因爲函數原型只是給電腦做個介紹,所以電腦只需要知道輸入的參數是什麼類型就夠了,不需要知道名字。所以我們以上的函數原型也可以簡寫如下:

double rectangleArea(double, double);

看到了嗎,我們可以省略length和width這兩個變量名,只保留double(雙精度浮點型)這個類型名字。

千萬不要忘了函數原型末尾的分號,因爲這是編譯器區分函數原型和函數定義開頭的重要指標。如果沒有分號,編譯時會出現比較難理解的錯誤提示。

頭文件


每次看到這個術語,我都想到已經結婚的“我們的青春”:周杰倫 的《頭文字D》。

到目前爲止,我們的程序只有一個.c文件(稱之爲“源文件”),比如我們之前把這個.c文件命名爲main.c,當然名字是無所謂的,起名爲hello.c,hehe.c都行。

一個項目多個文件

在實際編寫程序的時候,你的項目一般肯定不會把代碼都寫在一個main.c文件中。當然,可行是可行的。

但是,試想一下,如果你把所有代碼都塞到這一個main.c文件中,那代碼量可能達到10000多行,你要在裏面找一個東西太難了。也正是因爲這樣,通常我們每一個項目都會創建多個文件。

那以上說到的項目是指什麼呢?

之前我們用Code::Blocks這個IDE創建第一個C語言項目的時候,其實有接觸過。但是我們會再解釋一下。

一個項目(英語是 project),簡單來說是指你的程序的所有源代碼(還有一些其他的文件),項目裏面的文件有多種類型。

目前我們的項目還只有一個源文件:main.c

看一下你的IDE,一般來說項目是列在左邊。

1240

如上圖,你可以看到,這個項目(在Projects一欄裏)只有一個文件:main.c

現在我們再來展示一個包含好多個文件的項目:

1240

上圖中,我們可以看到在這個項目裏有好幾個文件。實際中的項目大多是這樣的,你看到那個main.c文件了嗎?通常來說在我們的程序中,會把main函數只定義在main.c當中。

當然不是一定非要這樣,每個人都有自己的編程風格。不過希望跟着這個課程學習的讀者,可以和我們保持一致的風格,方便理解。

那你又要問了:“爲什麼創建多個文件呢?我怎麼知道爲項目創建幾個文件合適呢?”

答案是:這是你的選擇。通常來說,我們把同一主題的函數放在一個文件裏。

.h文件和.c文件


在上圖中,我們可以看到有兩種類型的文件:一種是以.h結尾的,一種是以.c結尾的。

  • .h文件:稱爲“頭文件”,這些文件包含了函數的原型
  • .c文件:稱爲“源文件”,包含了函數本身(定義)

所以,通常來說我們不常把函數原型放在.c文件中,而是放在.h文件中,除非你的程序很小。

對每個.c文件,都有同名的.h文件。上面的項目那個圖中,你可以看到.h和.c文件一一對應。

  • files.h和files.c
  • editor.h和editor.c
  • game.h和game.c

但我們的電腦怎麼知道函數原型是在.c文件之外的另一種文件裏呢?

需要用到我們之前介紹過的預處理指令 #include來將其引入到.c文件中。

請做好準備,下面將有一波密集的知識點“來襲”。

怎麼引入一個頭文件呢?其實你已經知道怎麼做了,之前的課程我們已經寫過了。

比如我們來看我們上面的game.c文件的開頭

#include <stdlib.h>
#include <stdio.h>
#include "game.h"

void player(SDL_Surface* ecran)
{
  // ...
}

看到了嗎,其實你早就熟悉了,要引入頭文件,只需要用#include這個預處理指令。

因此我們在game.c源文件中一共引入了三個頭文件: stdlib.h, stdio.h,game.h

注意到一個不同點了嗎?

在標準庫的頭文件(stdlib.h, stdio.h)和你自己定義的頭文件(game.h)的引入方式是有點區別的:

  • <> 用於引入標準庫的頭文件,在IDE中一般位於安裝目錄的include文件夾中,在linux中則一般位於系統的include文件夾裏
  • "" 用於引入自定義的頭文件,位於你自己的項目的目錄中

我們再來看一下我們的對應的game.h這個頭文件的內容:

1240

看到了嗎,.h文件中存放的是函數原型。

你已經對一個項目有大致概念了。

那你又會問了:“爲什麼要這樣安排呢?把函數原型放在.h頭文件中,在.c源文件中用#include引入,爲什麼不把函數原型寫在.c文件中呢?”

答案是:方便管理,條理清晰,不容易出錯,省心。

因爲如前所述,你的電腦在調用一個函數前必須先“知道”這個函數,我們需要函數原型來讓使用這個函數的其他函數預先知道。

如果用了.h頭文件的管理方法,在每一個.c文件開頭只要用#include這個指令來引入頭文件的所有內容,那麼頭文件中聲明的所有函數原型都被當前.c文件所知道了,你就不用再操心那些函數的定義順序或者有沒有被其他函數知道

例如我的main.c函數要使用functions.c文件中的函數,那我只要在main.c的開頭寫 #include "functions.h",之後我在main.c函數中就可以調用function.c中定義的函數了。

你可能又要問了:“那我怎麼在項目中加入新的.h和.c文件呢?”

很簡單,在codeblocks裏,鼠標右鍵點擊項目列表的主菜單處,選擇Add Files,或者
在菜單欄上依次單擊 File->New->File...

就可以選擇添加文件的類型了。

引入標準庫


你腦海裏肯定出現一個問題:

如果我們用#include來引入stdio.h和stdlib.h這樣的標準庫的頭文件,而這些文件又不是我自己寫的,那麼它們肯定存在於電腦裏的某個地方,我們可以去找到,對吧?

是的,完全正確!

如果你使用的是IDE(集成開發環境),那麼它們一般就在你的IDE的安裝目錄裏。

如果是在純Linux環境下,那就要到系統文件夾裏去找,這裏不討論了,以後開了Linux課會講到,感興趣的讀者可以去網上搜索。

在我的情況,因爲安裝的是codeblocks這個IDE,所以在windows下,我的頭文件們“隱藏”在這個路徑下:

E:\Program Files\CodeBlocks\MinGW\include

1240

一般來說,都在一個叫做include的文件夾裏。

在裏面,你會找到很多文件,都是.h文件,也就是C語言系統定義的標準頭文件,也就是系統庫的頭文件(對windows,mac,linux都是通用的,C語言本來就是可移植的嘛)。

在這衆多的頭文件當中,你可以找到我們的老朋友:stdio.h和stdlib.h。

你可以雙擊打開這些文件或者選擇你喜歡的文本編輯器來打開,不過也許你會嚇一跳,因爲這些文件裏的內容很多,而且好些是我們還沒學到的用法,比如除了#include以外的其他的預處理指令。

你可以看到這些頭文件中充滿了函數原型,比如你可以在stdio.h中找到printf函數的原型。

你要問了:“ok,現在我已經知道標準庫的頭文件在哪裏了,那與之對應的標準庫的源文件(.c文件)在哪裏呢?”

不好意思,你見不到它們啦。因爲.c文件已經被事先編譯好,轉換成計算機能理解的二進制碼了。

“伊人已去,年華不復,吾將何去何從?”

既然見不到原先的它們了,至少讓我見一下“美圖秀秀”之後的它們吧…

可以啊,你在一個叫lib的文件夾下面就可以找到,在我的Windows下的路經爲:

E:\Program Files\CodeBlocks\MinGW\lib

1240

被編譯成二進制碼的.c文件,有了一個新的後綴名:.a(在codeblocks的情況,因爲編譯器是mingw)或者.lib(在visual c++的情況,因爲編譯器是Visual),當然以後我們還會學到在linux下還有.so這個後綴名,等。暫時不深究。

這些被編譯之後的文件被叫做庫文件或library文件(library是英語“庫”的意思),不要試着去閱讀這些文件的內容,完全不是人看得懂的亂碼…

學到這裏可能有點暈,不過繼續看下去就會漸漸明朗起來,下面的章節會有示意圖幫助理解。

小結一下:
在我們的.c源文件中,我們可以用#include這個預處理指令來引入標準庫的.h頭文件或自己定義的頭文件,這樣我們就能使用標準庫所定義的printf這樣的函數,這樣電腦就認識了這些函數(藉着.h文件中的函數原型),就可以檢驗你調用這些函數時有沒有用對,比如函數的參數個數,返回值類型等。

分開編譯


現在我們知道了一個項目是由若干文件組成的,那我們就可以來了解一下編譯器的工作原理。

之前的課裏面展示的編譯示例圖是比較簡化的,下圖是一幅編譯原理的略微詳細的圖,希望大家用心理解並記住:

1240

上圖將編譯時所發生的事情基本詳細展示了,我們來仔細分析:

  • 預處理器(preprocessor):顧名思義,預處理器爲編譯做一些預備工作,所以預處理器是在編譯之前啓動的。它的任務是執行特殊的指令,這些指令是通過預處理命令給出的,預處理命令以#開頭,很容易辨認。

預處理指令有好多種,目前我們學過的只有#include,它使我們可以在一個文件中引入另一個文件的內容。#include這個預處理指令也是最常用的。

預處理器會把#include所在的那一句話替換爲它所引入的頭文件的內容,比如

#include <stdio.h>

預處理器在執行時會把上面這句指令替換爲stdio.h文件的內容。所以到了編譯的時候,你的.c文件的內容會變多,包含了所有引入的頭文件的內容,顯得比較臃腫。

  • 編譯(compilation):這是核心的步驟,以前的課我們說過,正是編譯把我們人類寫的代碼轉換成計算機能理解的二進制碼(0和1組成)。編譯器編譯一個個.c文件。對於codeblocks這樣的IDE來說,就是你放在項目列表中的所有.c文件;如果你是用gcc來編譯,那麼你要指定編譯哪幾個.c文件。

編譯器會把.c文件先轉換成.o文件(有的編譯器會生成.obj文件),.o文件一般叫做“目標文件”(o就是英語 object [目標] 的首字母),是臨時的二進制文件,會被用於之後生成最終的可執行二進制文件。

.o文件一般會在編譯完成後被刪除(根據你的IDE的設置)。從某種程度上來說.o文件雖然是臨時中間文件,好像沒什麼大用,但保留着不刪除也是有好處:假如項目有10個.c文件,編譯後生成了10個.o文件。

之後你只修改了其中的一個.c文件,如果重新編譯,那麼編譯器不會爲其他9個.c文件重新生成.o文件了,只會重新生成你更改的那個。節省資源。

  • 鏈接器(linker):顧名思義,鏈接器的作用是鏈接,鏈接什麼呢?就是編譯器生成的.o文件,鏈接器把所有.o文件鏈接器來,“製作成”一個“大塊頭”:最終的可執行文件(windows下是.exe文件,linux下有不少種形式)。

現在你知道從代碼到生成一個可執行程序的內部原理了吧,下面我們要展示給大家的這張圖,很重要,希望大家理解並記住。

大部分的錯誤都會在編譯階段被顯示,但也有一些是在鏈接的時候顯示,有可能是少了.o文件之類。

之前那幅圖其實還不夠完整,你可能想到了:我們用.h文件引入了標準庫的頭文件的內容(裏面主要是函數原型),函數的具體實現的代碼我們還沒引入呢,怎麼辦呢?

對了,就是之前提到過的.a或.lib這樣的庫文件(由標準庫的.c源文件編譯而成)。

所以我們的鏈接器(linker)的活還沒完呢,它還需要負責鏈接標準庫文件,把你自己的.c文件編譯生成的.o目標文件和標準庫文件整合在一起,然後鏈接成最終的可執行文件。

如下圖所示:

1240

這下我們的示意圖終於完整了。

這樣我們纔有了一個完整的可執行文件,裏面有它需要的所有指令的定義,比如printf的定義

之後在第三部分我們會用到系統的圖形庫,也是在.a庫文件中定義的,包含了一系列指令的定義,比如告訴電腦怎麼創建一個窗口,繪製圖形,等等。好戲在後頭。

變量和函數的作用範圍


爲了結束我們這一課,我們還必須來學習最後一個知識點:

變量和函數的作用範圍(有效範圍)

我們將學習它們什麼時候是可以被調用的。

函數的私有變量(局部變量)

當你在一個函數裏定義了一個變量之後,這個變量會在函數結尾時從內存中被刪除。

int multipleTwo(int number)
{
  int result = 0; // 變量result在內存中被創建
  result = 2 * number;

  return result;
} // 函數結束,變量result從內存中被刪除

在一個函數裏定義的變量,只在函數運行期間存在。

這意味着什麼呢?意味着你不能從另一個函數中調用它。

#include <stdio.h>

int multipleTwo(int number);

int main(int argc, char *argv[])
{
  printf("15的兩倍是 %d\n", multipleTwo(15));
  printf("15的兩倍是 %d", result); // 錯誤!

  return 0;
}

int multipleTwo(int number)
{
  int result = 0;
  result = 2 * number;

  return result;
}

可以看到,在main函數中,我們試着調用result這個變量,但是因爲這個變量是在multipleTwo函數中定義的,在main函數中就不能調用,會出錯。

記住:在函數裏定義的變量只能在函數內部使用,我們稱之爲局部變量

全局變量:避免使用


能被所有文件使用的全局變量

我們可以定義能被項目的所有文件的所有函數調用的變量。我們會展示給大家怎麼做,爲了告知大家這方法存在,但是一般來說,要避免使用能被所有文件使用的全局變量。

可能這樣做一開始會讓你的代碼簡單一些,但是不久你就會爲之煩惱了。

爲了創建能被所有函數調用的全局變量,我們須要在函數之外定義。通常我們把這樣的變量放在程序的開頭,#include預處理指令的後面。

#include <stdio.h>

int result = 0; // 定義全局變量result

void multipleTwo(int number); // 函數原型

int main(int argc, char *argv[])
{
  multipleTwo(15); // 調用multipleTwo函數,使全局變量result的值變爲原來的兩倍

  printf("15的兩倍是 %d\n", result); // 我們可以調用變量result

  return 0;
}

void multipleTwo(int number)
{
  result = 2 * number;
}

上面的程序中,我們的函數multipleTwo不再有返回值了,而是用於將result這個全局變量的值變成2倍。之後main函數可以再使用result這個變量。

由於這裏的result變量是一個完全開放的全局變量,所以它可以被項目的所有文件調用,也就能被所有文件的任何函數調用。

注:這種類型的變量是很不推薦使用的,因爲不安全。一般用函數裏的return語句來返回一個變量的值。

只能在一個文件裏被訪問的全局變量


剛纔我們學習的完全開放的全局變量可以被項目的所有文件訪問。我們也可以使一個全局變量只能被它所在的那個文件調用。

就是說它可以被自己所在的那個文件的所有函數調用,但不能被項目的其他文件的函數調用。

怎麼做呢?

只需要在變量前面加上 static 這個關鍵字。如下所示:

static int result = 0;

static在英語裏是“靜止的,不變的”之意。

函數的static(靜態)變量


注意:
如果你在聲明一個函數內部的變量時,在前面加上static這個關鍵字,它的涵義和上面我們演示的全局變量是不同的。
函數內部的變量如果加了static,那麼在函數結束後,這個變量也不會銷燬,它的值會保持。下一次我們再調用這個函數時,此變量會延用上一次的值。

例如:

int multipleTwo(int number)
{
  static int result = 0; // 靜態變量result在函數第一次被調用時創建
  result = 2 * number;

  return result;
} // 變量result在函數結束時不會被銷燬

這到底意味着什麼呢?

就是說:result這個變量的值,在下次我們調用這個函數時,會延用上一次結束調用時的值。

有點暈是嗎?不要緊。來看一個小程序,以便加深理解:

#include <stdio.h>

int increment();

int main(int argc, char *argv[])
{
  printf("%d\n", increment());
  printf("%d\n", increment());
  printf("%d\n", increment());
  printf("%d\n", increment());

  return 0;
}

int increment()
{
  static int number = 0;
  number++;
  return number;
}

上述程序中,在我們第一次調用increment函數時,number變量被創建,初始值爲0,然後對其做自增操作(++運算符),所以number的值變爲1。

函數結束後,number變量並沒有從內存中被刪除,而是保存着1這個值。

之後,當我們第二次調用increment函數時,變量number的聲明語句(static int number = 0;)會被跳過不執行(因爲變量number還在內存裏呢。你想一個皇帝還沒駕崩,太子怎麼能繼位呢)。

我們繼續使用上一次創建的number變量,這時候變量的值沿用第一次increment函數調用結束後的值:1,再對它做++操作(自加1),number的值就變爲2了。

依此類推,第三次調用increment函數後number的值爲3,第四次number的值爲4。

所以運行程序,輸出如下:

1
2
3
4

一個文件中的局部函數(本地函數或靜態函數)


我們用函數的作用域來結束我們關於變量和函數的作用域的學習。

正常來說,當你在一個.c源文件中創建了一個函數,那它就是全局的,可以被項目中所有其他.c文件調用。

但是有時我們需要創建只能被本文件調用的函數,怎麼做呢?

聰明如你肯定想到了:對了,就是使用static關鍵字,與變量類似。

把它放在函數前面。如下:

static int multipleTwo(int number)
{
  // 指令
}

現在,你的函數就只能被同一個文件中的其他函數調用了,項目中的其他文件中的函數就只可遠觀而不可褻玩焉…

小結一下變量的所有可能的作用範圍


  1. 在函數體內定義的變量,如果前面沒加static關鍵字,則是局部變量,在函數結束時被刪除,只能在本函數內被使用。

  2. 在函數體內定義,但是前面加了static關鍵字,則爲靜態變量,在函數結束時不被刪除,其值也會保留。

  3. 在函數外面定義的變量被稱爲全局變量,如果前面沒有static關鍵字,則其作用範圍是整個項目的所有文件,就是說它可以被項目的所有文件的函數調用。

  4. 函數外面定義的變量,如果前面加了static關鍵字,那就只能被本文件的所有函數調用,而不能被項目其他的文件的函數調用。

同樣地,小結一下函數的所有可能的作用範圍


  1. 一個函數在默認情況下是可以被項目的所有文件的函數調用的。

  2. 如果我們想要一個函數只能被本文件的函數所調用,只需要在函數前加上static關鍵字。

總結


  1. 一個程序包含一個或多個.c文件(一般稱爲 源文件,source。當然我們一般也把所有的高級語言代碼叫做源代碼)。通常來說,每個.c文件都有一個和它同名但不同擴展名的.h文件(有點像同父異母的兄弟)。.c文件裏面包含了函數的實際定義,而.h文件裏包含函數的原型聲明。

  2. .h文件的內容被一個叫做預處理器(preprocessor)的程序引入到.c文件的開頭。

  3. .c文件被一個叫做編譯器(compiler)的程序轉換成.o的二進制目標文件(o是英語object的首字母,表示“對象、目標”)。

  4. .o文件又被一個叫做鏈接器(linker)的程序連接成一個最終的.exe可執行文件(exe是英語executable的前三個字母,表示“可執行的”。在windows操作系統裏可執行程序的擴展名是.exe,在linux系統裏,可執行程序有不少擴展名(.elf,等),也可以沒有擴展名)

  5. 變量和函數都有“有效範圍”,某些時候是訪問不到的。

第二部分第二課預告:


今天的課就到這裏,一起加油咯。

下一次我們學習第二部分第二課。

咳咳,我必須正襟危坐,假裝嚴肅地來宣佈:

來認識一下C語言的精華和王牌:C語言探索之旅 | 第二部分第二課:進擊的指針,C語言的王牌!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章