博客 / 詳情

返回

《C語言電子書-2026最新版》-編程語言與程序

大家好,我是良許,一個深耕嵌入式 12 年的老工程師,前世界 500 強高工。

我花了 3 個月時間,寫了一個 C 語言電子書,以非常通俗的語言跟大家講解 C 語言,把複雜的技術講得連小學生都能聽得懂,絕不是 AI 生成那種晦澀難懂的電子垃圾。

點擊此處免費領取 C 語言電子書

C 語言電子書目錄如下:

1.2.1 編程語言是什麼?

語言的本質:溝通的橋樑

在我們的日常生活中,語言是人與人之間溝通的工具。中文、英文、法文等自然語言讓我們能夠表達思想、傳遞信息、交流感情。同樣地,編程語言就是人與計算機之間溝通的工具。就像我們用中文告訴朋友"幫我買一杯咖啡"一樣,我們用編程語言告訴計算機"幫我計算1加1等於幾"。

但是,計算機和人不同。人類的大腦非常智能,即使我們説話不夠準確,或者表達有歧義,朋友也能理解我們的意思。比如你説"買個東西",朋友會根據上下文和你的表情猜出你要買什麼。但計算機卻是一個"死腦筋",它只能按照非常精確、明確的指令來工作。你必須告訴它每一個步驟該怎麼做,不能有任何模糊的地方。

編程語言的發展層次

如果我們把編程語言按照抽象程度來分類,可以分為三個層次:

  • 機器語言(第一代):這是計算機真正能夠理解的語言,完全由0和1組成。就像是給計算機説"方言",每種不同的CPU都有自己的機器語言。比如一個簡單的加法運算,在機器語言中可能看起來像這樣:10110000 01000001,這對人類來説完全無法理解。想象一下,如果你要寫一個計算器程序,需要用這樣的代碼寫幾千行,那簡直是一場噩夢。

<img src="https://lxlinux.superbed.verylink.top/item/68425bb858cb8da5c832304b.png" style="zoom:50%;" />

  • 彙編語言(第二代):為了讓程序員不再直接面對0和1,人們發明了彙編語言。它用一些英文縮寫來代替機器碼,比如用MOV表示移動數據,ADD表示加法運算。這就像是在0和1的基礎上貼了一些"標籤",雖然比機器語言好理解一些,但編程仍然非常複雜,需要程序員對計算機硬件非常瞭解。

<img src="https://lxlinux.superbed.verylink.top/item/68425c5558cb8da5c8323e0a.png" style="zoom: 20%;" />

  • 高級語言(第三代及以上):這就是我們今天要學習的C語言以及其他現代編程語言所屬的類別。高級語言更接近人類的自然語言和數學表達式。比如,我們想讓計算機計算兩個數的和,在C語言中只需要寫:c = a + b;,這幾乎和我們平時的數學表達一模一樣。

<img src="https://lxlinux.superbed.verylink.top/item/68425d1c58cb8da5c83251d8.png" style="zoom: 50%;" />

按照執行方式分類:編譯型語言與解釋型語言

編程語言還可以按照執行方式分為兩大類,這就像看書有兩種方式一樣:

  • 編譯型語言:就像把一本中文書完整地翻譯成英文書,然後給外國人看英文版。編譯型語言需要通過編譯器把整個程序翻譯成機器語言,生成一個可執行文件,然後計算機直接運行這個可執行文件。C語言就是典型的編譯型語言。

編譯型語言的好處是運行速度很快,因為計算機直接執行機器語言,不需要中間的翻譯過程。但是缺點是每次修改程序後都需要重新編譯,而且編譯後的程序只能在特定的操作系統上運行,移植到其他系統需要重新編譯。

  • 解釋型語言:就像請一個翻譯員坐在旁邊,一邊看中文書一邊翻譯給外國人聽。解釋型語言需要通過解釋器逐行翻譯並執行程序。Python、JavaScript就是典型的解釋型語言。

解釋型語言的好處是編寫和調試很方便,修改程序後可以立即運行,而且程序可以在任何安裝瞭解釋器的系統上運行。但是缺點是運行速度相對較慢,因為需要邊翻譯邊執行,而且運行時必須安裝相應的解釋器。

按照編程方式分類:面向過程與面向對象

編程語言還可以按照編程思想分為不同類型:

  • 面向過程的語言:這種編程方式把程序看作是一系列函數的組合,就像一條工廠的流水線。原材料從一端進入,經過一道道工序的處理,最後變成成品從另一端出來。每個工序就是一個函數,負責完成特定的任務。C語言就是典型的面向過程語言。

<img src="https://lxlinux.superbed.verylink.top/item/68425f0358cb8da5c8327f7b.png" style="zoom:50%;" />

面向過程的思維方式比較直觀,適合解決流程比較明確的問題。比如計算器程序:輸入數據→進行運算→輸出結果,這是一個清晰的流程。對於我們學習嵌入式開發來説,面向過程的思維方式更貼近硬件的工作方式,也更容易理解程序的執行過程。

  • 面向對象的語言:這種編程方式把程序看作是一羣對象的互動,就像一個社會由不同的人組成,每個人都有自己的特點和能力。比如在一個遊戲程序中,可能有玩家對象、敵人對象、道具對象等,每個對象都有自己的屬性(比如血量、攻擊力)和行為(比如移動、攻擊)。C++、Java、Python等都支持面向對象編程。

<img src="https://lxlinux.superbed.verylink.top/item/68425fed58cb8da5c83288a8.png" style="zoom: 25%;" />

面向對象的思維方式更適合構建複雜的大型軟件系統,因為它能更好地組織和管理代碼,讓程序更容易維護和擴展。

1.2.2 什麼是程序?

程序的本質:指令的序列

程序,簡單來説,就是一系列指令的有序集合,告訴計算機要做什麼以及怎麼做。這就像一本菜譜,詳細地告訴廚師每一個步驟:先洗菜,再切菜,然後熱鍋,接着下油,最後炒菜。程序也是這樣,它一步一步地告訴計算機:先讀取數據,再進行計算,然後判斷結果,最後輸出答案。

讓我們用一個生活中的例子來理解程序。假設你要教一個完全不會做飯的女朋友煮蛋炒飯,你需要給出非常詳細的步驟:

  1. 打開冰箱,取出2個雞蛋
  2. 拿一個碗,把雞蛋打散
  3. 熱鍋,倒入適量油
  4. 把蛋液倒入鍋中,快速攪拌
  5. 雞蛋半熟時,倒入米飯
  6. 翻炒3分鐘
  7. 加入適量鹽和醬油
  8. 繼續翻炒1分鐘
  9. 關火,裝盤

這個做飯的過程就是一個"程序",每一步都是一條"指令"。程序必須足夠詳細和準確,不能有遺漏或模糊的地方,否則執行者(無論是女朋友還是計算機)就不知道該怎麼辦。

從程序到進程:程序的運行狀態

很多同學容易混淆"程序"和"進程"這兩個概念。讓我用一個簡單的比喻來解釋:

  • 程序就像一本菜譜,它靜靜地放在書架上,裏面記錄了做菜的步驟和方法。菜譜本身不會做菜,它只是一份指令的集合。
  • 進程就像根據菜譜正在做菜的過程。當廚師拿起菜譜開始做菜的時候,這個"做菜的過程"就是一個進程。進程包括了廚師、菜譜、食材、廚具,以及正在進行的做菜動作。

同樣地,當我們雙擊一個程序圖標時,操作系統就會創建一個進程來執行這個程序。進程包括了程序的代碼、程序運行所需的內存空間、CPU的執行狀態等等。

任務與多任務

在現代計算機中,我們經常聽到"任務"這個詞。任務(Task)其實就是進程的另一種説法,特別是在嵌入式系統中,我們更習慣用"任務"這個詞。

  • 單任務系統:就像一個廚師在廚房裏,同一時間只能做一道菜。早期的計算機系統就是這樣,同一時間只能運行一個程序。如果要運行新程序,必須先關閉當前運行的程序。
  • 多任務系統:就像一個很有經驗的廚師,可以同時處理多道菜:一邊炒菜,一邊煮湯,還能抽空準備下一道菜的食材。現代的操作系統都是多任務系統,可以同時運行多個程序。

實際上,計算機的CPU在任意時刻只能執行一個指令,但它執行得非常快,可以在不同的任務之間快速切換。比如它可能用0.01秒處理音樂播放器,然後用0.01秒處理瀏覽器,再用0.01秒處理文字處理軟件。因為切換得非常快,用户感覺就像是多個程序在同時運行。

程序的不同類型

根據功能和用途的不同,程序可以分為很多類型:

  • 系統程序:這些是計算機系統的基礎軟件,比如操作系統、驅動程序、編譯器等。它們就像房子的地基和框架,雖然用户平時看不到,但是沒有它們,其他程序就無法運行。
  • 應用程序:這些是用户直接使用的軟件,比如微信、QQ音樂、瀏覽器、遊戲等。它們就像房子裏的傢俱和裝飾,是用户能夠直接感受到的部分。
  • 嵌入式程序:這些程序運行在嵌入式系統中,比如洗衣機的控制程序、汽車的發動機管理程序、智能手機的基帶程序等。它們通常直接控制硬件設備,對實時性和可靠性要求很高。

<img src="https://lxlinux.superbed.verylink.top/item/684262fa58cb8da5c8329b5f.png" style="zoom: 50%;" />

1.2.3 程序與算法的關係

經典公式:程序 = 數據結構 + 算法

在計算機科學領域,有一個非常著名的公式:程序 = 數據結構 + 算法。這個公式是由瑞士計算機科學家尼古拉斯·沃思(Niklaus Wirth)提出的,它精確地概括了程序的本質。

讓我們用一個生活中的例子來理解這個公式。想象你要組織一次同學聚會:

  • 數據結構就像你的通訊錄,裏面記錄了每個同學的姓名、電話、地址等信息,以及這些信息是如何組織和存儲的。
  • 算法就像你組織聚會的步驟和方法:如何聯繫同學、如何選擇聚會地點、如何安排活動等。
  • 程序就是把通訊錄和組織方法結合起來,實際執行聚會組織工作的過程。

沒有通訊錄(數據結構),你不知道要聯繫誰;沒有組織方法(算法),你不知道怎麼辦聚會;只有把兩者結合起來,才能成功組織一次聚會(完成程序的功能)。

<img src="https://lxlinux.superbed.verylink.top/item/684265be58cb8da5c832b3d7.png" style="zoom:33%;" />

什麼是數據結構?

數據結構的定義:數據結構是指數據元素之間的關係,以及對這些數據進行操作的方法。簡單來説,就是數據怎麼存放、怎麼組織的問題。

讓我們用幾個生活中的例子來理解不同的數據結構:

  • 數組(Array)- 像一排儲物櫃:想象學校裏的儲物櫃,每個櫃子都有一個編號(1號、2號、3號...),每個櫃子裏可以放一樣東西。數組就是這樣,它是一系列相同類型數據的有序集合,每個數據都有一個位置編號(索引)。

比如,你要存儲一個班級所有學生的成績,可以用數組:成績[1] = 85, 成績[2] = 92, 成績[3] = 78...。數組的特點是查找某個位置的數據很快(直接根據編號找到櫃子),但如果要在中間插入或刪除數據就比較麻煩(需要移動後面所有的數據)。

  • 鏈表(Linked List)- 像一串糖葫蘆:糖葫蘆是一顆一顆串起來的,每顆都用竹籤連接到下一顆。鏈表也是這樣,每個數據元素都包含數據本身和指向下一個元素的"指針"。

鏈表的特點是插入和刪除數據很方便(只需要改變指針的指向),但查找某個特定數據需要從頭開始一個一個地找,就像要吃糖葫蘆中間的某顆糖,必須從第一顆開始數。

  • 棧(Stack)- 像一摞盤子:想象餐廳裏洗好的盤子一個摞一個地放着,取盤子時只能從最上面取,放盤子時也只能放在最上面。棧就是這樣的"後進先出"(LIFO - Last In First Out)的數據結構。

棧在程序中有很多用途,比如保存函數調用的信息。當程序調用一個函數時,會把當前的狀態"壓入"棧中;當函數執行完畢時,再從棧中"彈出"之前的狀態。

  • 隊列(Queue)- 像排隊買票:人們排隊買票時,先來的人先買到票,後來的人要排在隊尾。隊列就是這樣的"先進先出"(FIFO - First In First Out)的數據結構。

隊列常用於處理需要排隊等待的任務,比如打印機的打印任務、操作系統的任務調度等。

  • 樹(Tree)- 像族譜:族譜顯示了家族成員之間的關係,有祖先、父母、兄弟姐妹、子女等。樹形數據結構也是這樣,每個元素都有明確的層次關係。

樹結構非常適合表示有層次關係的數據,比如文件系統(文件夾包含子文件夾和文件)、組織架構圖等。

什麼是算法?

算法的定義:算法是解決特定問題的一系列明確、有限的步驟。它回答的是"怎麼做"的問題。

我們講的算法更側重“邏輯算法”,並非“數學型算法”,比如PID算法、濾波算法,數學型算法通常需要碩士、博士以上學歷(算法工程師)。

<img src="https://lxlinux.superbed.verylink.top/item/684268b158cb8da5c832bb47.png" style="zoom:25%;" />

讓我們通過幾個具體的例子來理解算法:

查找算法 - 在電話簿中找人
假設你要在一本按姓名排序的電話簿中找到"張三"的電話號碼,你可能會用以下幾種方法:

  1. 順序查找:從第一頁開始,一頁一頁地翻,直到找到張三。這種方法簡單但可能很慢。
  2. 二分查找:因為電話簿是按字母順序排列的,你可以翻到中間的一頁,看看是在"張"之前還是之後,然後繼續在相應的一半中查找。這樣每次都能排除一半的頁面,查找速度快很多。

<img src="https://lxlinux.superbed.verylink.top/item/6842673958cb8da5c832b654.png" style="zoom: 50%;" />

數據結構與算法如何結合成程序?

理解了數據結構和算法的概念後,我們來看看它們是如何結合成一個完整的程序的。

以學生成績管理系統為例

第一步:確定數據結構
首先,我們需要決定如何存儲學生信息。每個學生有姓名、學號、各科成績等信息,我們可以設計這樣的數據結構:

struct Student {
    char name[50];      // 姓名
    int id;            // 學號
    float scores[5];   // 五科成績
    float average;     // 平均分
};

然後,我們需要存儲所有學生的信息,可以用數組:

struct Student students[100];  // 最多100個學生
int student_count = 0;         // 當前學生數量

第二步:設計算法
接下來,我們需要設計各種操作的算法:

  1. 添加學生算法

    • 檢查是否還有空間
    • 輸入學生信息
    • 計算平均分
    • 將學生添加到數組中
    • 更新學生總數
  2. 查找學生算法

    • 輸入要查找的學號
    • 遍歷學生數組
    • 比較每個學生的學號
    • 找到後返回學生信息
  3. 計算平均分算法

    • 將所有科目成績相加
    • 除以科目數量
    • 返回結果

第三步:組合成程序
最後,我們把數據結構和算法組合起來,形成完整的程序:

#include <stdio.h>

// 數據結構定義
struct Student {
    char name[50];
    int id;
    float scores[5];
    float average;
};

struct Student students[100];
int student_count = 0;

// 算法實現
float calculate_average(float scores[]) {
    float sum = 0;
    for(int i = 0; i < 5; i++) {
        sum += scores[i];
    }
    return sum / 5;
}

void add_student() {
    if(student_count >= 100) {
        printf("學生數量已滿!\n");
        return;
    }
    
    // 輸入學生信息
    printf("請輸入學生姓名:");
    scanf("%s", students[student_count].name);
    
    printf("請輸入學號:");
    scanf("%d", &students[student_count].id);
    
    printf("請輸入5科成績:");
    for(int i = 0; i < 5; i++) {
        scanf("%f", &students[student_count].scores[i]);
    }
    
    // 計算平均分
    students[student_count].average = 
        calculate_average(students[student_count].scores);
    
    student_count++;
    printf("學生信息添加成功!\n");
}

// 主程序
int main() {
    int choice;
    while(1) {
        printf("1. 添加學生\n2. 查找學生\n3. 退出\n");
        printf("請選擇:");
        scanf("%d", &choice);
        
        switch(choice) {
            case 1:
                add_student();
                break;
            case 2:
                // 查找學生的代碼...
                break;
            case 3:
                return 0;
        }
    }
}

通過這個例子,我們可以清楚地看到:

  • 數據結構解決了"數據怎麼存儲"的問題(用結構體存儲學生信息,用數組存儲多個學生)
  • 算法解決了"怎麼處理數據"的問題(如何添加學生、如何計算平均分)
  • 程序是數據結構和算法的結合,實現了完整的功能

1.2.4 如何從零生產一個程序?

程序誕生的完整過程

很多初學者認為編程就是坐在電腦前敲代碼,但實際上,從零開始製作一個程序就像建造一座房子一樣,需要經過設計、施工、裝修、驗收等多個階段。編程只是其中的一個環節,讓我們來詳細瞭解程序誕生的整個過程。

1. 第一階段:編程(Programming)- 用代碼描述解決方案

什麼是編程?
編程就是用計算機能理解的語言來描述解決問題的方法。這就像用中文寫作文一樣,你心裏有想法,但需要用文字把想法表達出來。編程也是這樣,你知道怎麼解決問題,但需要用編程語言把解決方法"寫"出來。

編程的具體過程

讓我們用一個簡單的例子來理解編程過程。假設我們要編寫一個程序,計算圓的面積:

步驟1:分析問題

  • 需要什麼輸入?半徑
  • 需要做什麼計算?面積 = π × 半徑²
  • 需要什麼輸出?面積的數值

步驟2:設計解決方案

  • 提示用户輸入半徑
  • 讀取用户輸入的半徑
  • 使用公式計算面積
  • 顯示計算結果

步驟3:編寫代碼

#include <stdio.h>

int main() {
    float radius, area;
    const float PI = 3.14159;
    
    // 提示用户輸入
    printf("請輸入圓的半徑:");
    
    // 讀取用户輸入
    scanf("%f", &radius);
    
    // 計算面積
    area = PI * radius * radius;
    
    // 輸出結果
    printf("圓的面積是:%.2f\n", area);
    
    return 0;
}

2. 第二階段:編譯(Compilation)- 翻譯成計算機語言

為什麼需要編譯?
我們寫的C語言代碼就像用中文寫的説明書,但計算機只能理解機器語言(0和1組成的代碼)。編譯就是把中文説明書翻譯成計算機能理解的"外星語"的過程。

編譯的詳細過程

編譯過程其實包含幾個步驟,就像翻譯一本書需要經過初稿、校對、潤色等多個環節:

  • 預處理(Preprocessing)
    這是編譯的第一步,預處理器會處理所有以#開頭的指令。比如:

    • #include <stdio.h>:把stdio.h文件的內容複製到當前文件中
    • #define PI 3.14159:把代碼中所有的PI替換成3.14159

就像寫作文前先準備好所有需要的資料和素材。

  • 編譯(Compilation)
    編譯器把預處理後的C語言代碼翻譯成彙編語言。彙編語言比機器語言容易理解一些,但仍然很接近硬件。這就像把中文先翻譯成英文,為進一步翻譯做準備。
  • 彙編(Assembly)
    彙編器把彙編語言翻譯成機器語言,生成目標文件(.obj或.o文件)。這就像把英文翻譯成計算機能理解的"外星語"。
  • 鏈接(Linking)
    鏈接器把多個目標文件和系統庫文件組合成一個完整的可執行文件。這就像把翻譯好的各個章節裝訂成一本完整的書。

編譯工具的使用

在實際開發中,我們通常使用集成開發環境(IDE)來簡化編譯過程:

命令行編譯
如果你使用GCC編譯器,編譯過程可能是這樣的:

gcc -o circle_area circle_area.c

這條命令告訴GCC編譯器:把circle_area.c編譯成名為circle_area的可執行文件。

IDE編譯
如果你使用開發環境如Dev-C++、Code::Blocks等,通常只需要按F9鍵或點擊"編譯並運行"按鈕,IDE會自動完成整個編譯過程。

編譯過程中可能遇到的問題

  • 語法錯誤(Syntax Errors)
    這就像寫作文時的錯別字或語法錯誤。比如忘記寫分號、括號不匹配等。編譯器會告訴你錯誤的位置,你需要修改後重新編譯。
  • 鏈接錯誤(Linking Errors)
    這通常是因為找不到某個函數的定義,或者缺少必要的庫文件。就像寫書時引用了某個資料,但在參考文獻中找不到這個資料。
  • 警告(Warnings)
    警告不會阻止編譯,但提醒你代碼中可能存在問題。就像老師批改作文時的建議,雖然不是錯誤,但最好改正。

3. 第三階段:執行(Execution)- 程序開始工作

什麼是程序執行?
編譯完成後,我們得到了一個可執行文件,但它還只是靜靜地躺在硬盤上。程序執行就是讓這個"沉睡"的程序"甦醒"過來,開始工作。

執行過程的詳細步驟

  • 加載(Loading)
    當你雙擊可執行文件時,操作系統會把程序從硬盤加載到內存中。這就像把一本書從書架上取下來,打開準備閲讀。

操作系統會為程序分配內存空間,包括:

  1. 代碼段:存儲程序的指令
  2. 數據段:存儲全局變量和靜態變量
  3. 堆:用於動態分配內存
  4. 棧:用於存儲局部變量和函數調用信息
  • 創建進程
    操作系統會為程序創建一個進程,分配一個進程ID(PID),並在進程表中記錄相關信息。這就像給每個正在做菜的廚師分配一個工作台和工具。
  • 開始執行
    CPU開始執行程序的指令。對於我們的圓面積計算程序:
  1. 首先執行printf("請輸入圓的半徑:");,在屏幕上顯示提示信息
  2. 然後執行scanf("%f", &radius);,等待用户輸入
  3. 用户輸入數據後,執行area = PI * radius * radius;進行計算
  4. 最後執行printf("圓的面積是:%.2f\n", area);顯示結果

4. 調試(Debugging)- 發現和修復錯誤

程序很少能一次性完美運行,通常需要經過調試過程來發現和修復錯誤:

  • 語法調試:修復編譯時發現的語法錯誤。
  • 邏輯調試:程序能夠運行,但結果不正確。需要檢查算法邏輯是否有誤。
  • 運行時調試:程序在某些情況下會崩潰或產生異常。需要找出導致問題的原因。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.