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

作者 謝恩銘,公眾號「程序員聯盟」(微信號:coderhub)。
轉載請注明出處。
原文:http://www.lxweimin.com/p/2070cfd368ca

《C語言探索之旅》全系列

內容簡介


  1. 前言
  2. 函數原型
  3. 頭文件
  4. 分開編譯
  5. 變量和函數的作用范圍
  6. 總結
  7. 第二部分第二課預告

1. 前言


上一課是 C語言探索之旅 | 第一部分練習題 。

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

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

俗語說得好:“一口是吃不成一個胖子的?!?/p>

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

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

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

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

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

2. 函數原型


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

這是為什么呢?

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

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

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

我“完全”同意(可別讓 C語言之父 Dennis Ritchie 聽到了...)。但是請相信,這樣設計應該也是有理由的。計算機先驅們早就想到了,也提出了解決之道。

下面我們就來學一個新的知識點,借著這個技術,你可以把你的自定義函數放在程序的任意位置。

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


我們會聲明我們的函數,需要用到一個專門的技術:函數原型,英語是 function prototype。function 表示“函數”,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 函數很熟悉,是經常打交道的“哥們”,所以不需要函數原型來“介紹” main 函數。

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

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

double rectangleArea(double, double);

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

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

3. 頭文件


頭文件在英語中是 header file。header 表示“數據頭,頁眉”,file 表示“文件”。

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

到目前為止,我們的程序只有一個 .c 文件(被稱為“源文件”,在英語中是 source file。source 表示“源,源頭,水源”),比如我們之前把這個 .c 文件命名為 main.c。當然名字是無所謂的,起名為hello.c,haha.c 都行。

一個項目多個文件


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

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

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

之前我們用 CodeBlocks 這個 IDE 創建第一個 C語言項目的時候,其實已經接觸過了。

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

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

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

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

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

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

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

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

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

.h 文件和 .c 文件


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

  • .h 文件:header file,表示“頭文件”,這些文件包含了函數的原型。
  • .c 文件:source file,表示“源文件”,包含了函數本身(定義)。

所以,通常來說我們不常把函數原型放在 .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* screen)
{
    // ...
}

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

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

注意到一個不同點了嗎?

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

  • <> 用于引入標準庫的頭文件。對于 IDE,這些頭文件一般位于 IDE 安裝目錄的 include 文件夾中;在 Linux 操作系統下,則一般位于系統的 include 文件夾里。
  • "" 用于引入自定義的頭文件。這些頭文件位于你自己的項目的目錄中。

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

看到了嗎,.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 環境下,那就要到系統文件夾里去找,這里不討論了,感興趣的讀者可以去網上搜索。

在我的情況,因為安裝的是 CodeBlocks 這個 IDE,所以在 Windows下,我的頭文件們“隱藏”在這兩個路徑下:

C:\Program Files\CodeBlocks\MinGW\include

C:\Program Files\CodeBlocks\MinGW\x86_64-w64-mingw32\include

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

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

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

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

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

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

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

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

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

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

C:\Program Files\CodeBlocks\MinGW\lib

C:\Program Files\CodeBlocks\MinGW\x86_64-w64-mingw32\lib

被編譯成二進制碼的 .c 文件,有了一個新的后綴名:.a(在 CodeBlocks 的情況,它的編譯器是 MinGW。MinGW 簡單來說就是 GCC 編譯器的 Windows 版本)或者 .lib(在 Visual C++ 的情況),等。這是靜態鏈接庫的情況。

你在 Windows 中還能找到 .dll 結尾的動態鏈接庫;你在 Linux 中能找到 .so 結尾的動態鏈接庫。暫時我們不深究靜態鏈接庫和動態鏈接庫,有興趣的讀者可以去網上自行搜索。

這些被編譯之后的文件被叫做庫文件或 Library 文件(library 表示“庫,圖書館,文庫”),不要試著去閱讀這些文件的內容,因為是看不懂的亂碼。

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

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

4. 分開編譯


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

之前的課里面展示的編譯示例圖是比較簡化的,下圖是一幅編譯原理的略微詳細的圖,希望大家用心理解并記?。?/p>

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

  • 預處理器(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 目標文件和標準庫文件整合在一起,然后鏈接成最終的可執行文件。

如下圖所示:

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

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

5. 變量和函數的作用范圍


為了結束這一課,我們還得學習最后一個知識點:變量和函數的作用范圍(有效范圍)

我們將學習變量和函數什么時候是可以被調用的。

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

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

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 函數中就不能調用,編譯會出錯。

記住:在函數里定義的變量只能在函數內部使用,我們稱之為局部變量,英語是 local variable。local 表示“局部的,本地的”,variable 表示“變量”。

全局變量(請避免使用)


全局變量的英語是 global variable。global 表示“全局的,總體的”。

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

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

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

為了創建能被所有函數調用的全局變量,我們須要在函數之外定義。通常我們把這樣的變量放在程序的開頭,#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 關鍵字。

6. 總結


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

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

  3. .c 文件被一個叫做編譯器(compiler)的程序轉換成 .o 的二進制目標文件。

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

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

7. 第二部分第二課預告


今天的課就到這里,一起加油吧!

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


我是 謝恩銘,公眾號「程序員聯盟」(微信號:coderhub)運營者,慕課網精英講師 Oscar 老師,終生學習者。
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:「向著標桿直跑」

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容