【Day 21】我的第一個 C 語言體驗之旅

我的第一個 C 語言體驗之旅

我的第一個 C 語言體驗之旅

從小到大我接觸過不少跟電腦相關的知識,但一直沒有正式學過程式語言。
不過因為當時太喜歡《仙境傳說 Online》,所以自己跑去研究怎麼架設私人伺服器,並在這過程中接觸到了一些簡單的腳本語言。

Note
腳本語言(Scripting Language),指的是一種設計來「快速編寫、執行」的程式語言,簡單來說就是一種「寫了就能馬上跑」的程式語言,不需要像 C 或 Java 那樣先編譯一大堆步驟。

這也算是我真正踏入程式世界的起點,沒想到這段經歷後來成為我學習程式的重要基礎。

Note
私人伺服器,簡單來說就是玩家自行架設的遊戲伺服器,遊戲規則由架設者訂定,由於不是官方授權,通常會有版權爭議或法律風險。

當時接觸的腳本語言雖然不算正式的程式語言,但已經包含了一些程式的基本概念,例如 變數、條件判斷、迴圈 等等。

而我寫的語言叫做 eAthena script。
下面提供一個簡單範例給你參考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
prontera,150,150,4    script    Hello NPC    100,{

mes "[Hello NPC]";
mes "請問你想選擇哪一種藥水?";
next;

// 顯示選單,選項依序為第一個、第二個...
switch(select("藍色藥水(恢復 SP 100):紅色藥水(恢復 HP 100)")) {
case 1:
mes "[Hello NPC]";
mes "你選擇了藍色藥水!";
getitem 504,1; // 504 是藍色藥水的道具 ID
close;
case 2:
mes "[Hello NPC]";
mes "你選擇了紅色藥水!";
getitem 501,1; // 501 是紅色藥水的道具 ID
close;
}
}

那這段程式碼要怎麼執行呢?其實很簡單:

  1. 把程式碼存成一個.txt 檔案。
  2. 放到伺服器的npc資料夾裡。
  3. 重新啟動伺服器,就能生效囉!

這邊也分享一下當時啟動伺服器的畫面給你參考:

EAthena(圖源網路)

不過 eAthena script 並不是這篇的重點,所以你只要知道我因為它而第一次接觸到「程式語言」就好。

後來在學時期,老師宣布下一學期要教我們一門程式語言——C 語言。

現在回想起來,真的是標準的「從入門到放棄」,那時候我才深刻體會到,程式世界比打副本還要難。

C 語言之從入門到放棄(圖源網路)

「今天我們要學習的程式語言是 C 語言!」老師一邊說著,一邊在白板上寫下大大的 C。

「C 語言可以說是所有程式語言的基礎,很多語言都是從 C 衍生出來的。」老師繼續補充。

「哦哦哦!這不就代表學會 C,以後幾乎都能通了嗎?」當下的我超級興奮,心想如果能打好正式的程式基礎,對我之後寫腳本應該會超有幫助。

但實際上呢……現實狠狠給了我一巴掌。

我的記憶只停留在 printf("Hello, World!\n"); 這一行程式碼:

1
2
3
4
5
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}

因為 C 語言對當時的我來說實在太難了,上到指標那一段我就徹底陣亡。掙扎了一下之後,最後還是決定回去玩遊戲比較實在。
結果指標沒學會,倒是先在遊戲裡練成了滿級玩家。

萬語之祖:C

為什麼許多課程都會把 C 語言當作入門首選呢?其原因有幾個:

  1. 電腦基礎體系: 大部分作業系統(Unix、Linux、Windows 等)、嵌入式系統,甚至許多編譯器,都是用 C 語言開發的。學 C 能幫助你更了解電腦怎麼運作。
  2. 語言特性: C 會逼你學會記憶體管理(指標、動態配置)和精確的資料型別,這能讓你更清楚底層的概念,例如:Stack、Heap、記憶體洩漏(Memory Leak)等。
  3. 萬語之祖: 很多語言像 JavaScript、Java、C#、Go 等等,都受 C 的影響,可以說是從 C 演化出來的。

這也是為什麼常常會有人說:

「你只要會 C 語言,其他語言都不在話下!」

因為你已經掌握了最底層的語法模式與邏輯思維,所以再去學其他語言時,就能上手得更快。

舉例來講,JavaScript 的 for 迴圈與 C 語言的 for 迴圈幾乎是一模一樣的:

1
2
3
4
// JavaScript 的 for 迴圈
for (let i = 0; i < 10; i++) {
console.log(i);
}
1
2
3
4
// C 語言的 for 迴圈
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}

這樣的語法結構能讓你在學 JavaScript 時不會覺得陌生,因為你早就在 C 語言裡熟悉了迴圈的寫法,所以上手會快很多。

另外,我們前面提到過 C 語言會強迫你理解 記憶體管理。但在 JavaScript 中,你完全不用操心這部分,因為它有內建的 自動垃圾回收機制(Garbage Collection) ,能自動幫你釋放不再使用的記憶體,這樣一來,我們就能更專注在程式邏輯上。

不過這並不代表可以完全忽略記憶體問題,如果程式邏輯寫得不好,依然可能造成 記憶體洩漏 。因此,有了 C 語言的基礎,你會更清楚記憶體管理的重要性。

Note

  1. 記憶體洩漏(Memory Leak): 指程式在執行時,沒有釋放掉不再使用的記憶體,久而久之就會耗盡系統資源,造成效能下降甚至程式當機。
  2. 自動垃圾回收機制(Garbage Collection): 指程式語言會自動管理記憶體的分配與釋放,幫助降低記憶體洩漏的風險。

接下來,我會簡單介紹一些 C 語言的基本語法,讓你對它有個初步的概念。
不過要先強調,這並不代表看完這篇文章你就會 C 語言,畢竟它的知識點非常多,這裡只能算是淺嚐一下皮毛而已。

打聲招呼吧!Hello, World

首先讓我們來看看所有程式語言的經典入門範例:

1
2
3
4
5
6
#include <stdio.h>

int main(void) {
printf("Hello, world!\n");
return 0;
}

Note
Hello, World 是程式設計入門的經典範例,用來展示一門語言的基本語法和輸出方式。它的目的就是讓初學者快速體驗,如何在該語言中編寫並執行一個最簡單的程式。

不用擔心,我會逐行來說明與介紹:

  • #include:告訴電腦「我要把某個工具加進來」。在 C 語言中,通常會用來引入函式庫,讓我們能使用裡面已經寫好的功能。
  • <stdio.h>:這是 C 語言的標準輸入輸出庫(Standard Input Output Library),簡單來說,它就像一個工具包,裡面放了很多常用的工具,例如 printf 函式就是從這裡來的。

所以這一行意思就是:「嘿!電腦!我要使用標準輸入輸出庫裡面的工具!」

接著往下看一點:

  • int C 語言中的一種資料型別,代表「整數」(Integer)。在這裡用來指定 main 函式的回傳值型別。至於資料型別的細節,我們會在後面章節再深入介紹。
  • main C 語言的「主函式」(Main Function),每個 C 程式都必須有一個 main 函式,這是程式執行的起點。
  • void 表示 main 函式不接受任何參數
  • {} C 語言的區塊符號,用來包裹函式的內容

所以這一行意思就是:「嘿!電腦!這是我的主函式,當你執行程式時,請從這裡開始!接著這個函式叫做 main,它不接受任何參數,並且會返回一個整數值!」

接著讓我們看一下程式碼的內容:

  • printf("Hello, world!\n"); 這行會在螢幕上顯示「Hello, world!」,\n 是換行符號,表示輸出後會換行。
  • return 0; 這行表示 main 函式執行成功並回傳 0,代表程式正常結束。在 C 語言裡這是一種慣例,如果回傳非 0(例如return 1;),就代表程式異常結束。

所以這一行意思就是:「嘿!電腦!請在螢幕上輸出 ‘Hello, world!’,然後換行!接著告訴我程式執行成功並返回 0!」。

那麼以上就是最基本的 C 語言入門範例,是不是超簡單的呢?!(並沒有。)

資料型別

前面我們有提到一個特別的東西 —— int,也稱之為 「資料型別(Data Type)」 ,而這個概念在幾乎所有程式語言裡都存在。

那麼,為什麼需要資料型別呢?主要有幾個原因:

  1. 記憶體管理: 不同型別佔的空間不同,能幫助更有效利用記憶體。例如:int 通常佔用 4 個位元組(bytes),而 char 只佔用 1 個位元組。
  2. 類型安全: 型別能幫忙檢查正確性,減少奇怪的錯誤或資料遺失。不過要注意,不同型別之間可能會發生 隱式轉型(implicit conversion) ,像是自動把浮點數變整數,可能導致資料遺失或奇怪的結果。
    因此,通常會在編譯時加上警告選項(例如 -Wall -Wextra),甚至用 -Werror 把警告當錯誤處理。
  3. 程式可讀性: 明確的型別讓程式碼更清楚,也方便維護。

Note
隱式轉型(Implicit Conversion):指的是在程式裡,當不同型別的值要做運算或指定給變數時,編譯器會自動幫你把其中一個型別轉換成另一個型別,讓程式能正常執行。這個過程是自動發生的,開發者不需要特別寫轉換指令。

那麼 C 語言中主要常見的資料型別區分為三大類:

  1. 整數(Integer): 用來表示整數值,例如:intshortlong 等等。
  2. 浮點數(Floating Point): 用來表示帶小數點的數值,例如:floatdouble 等等。
  3. 字元(Character): 用來表示單一字元,例如:char

先不談 類型安全程式可讀性 ,因為這兩點比較直覺,真正難懂的是記憶體管理,那要怎麼證明它的重要性呢?其實很簡單,在 C 語言裡可以用 sizeof 關鍵字,來查看不同資料型別佔用的記憶體大小。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(void) {
printf("int 的大小:%zu\n", sizeof(int));
printf("char 的大小:%zu\n", sizeof(char));
printf("float 的大小:%zu\n", sizeof(float));
printf("double 的大小:%zu\n", sizeof(double));

return 0;
}

實際輸出之後,你可以看到以下的結果:

1
2
3
4
int 的大小:4
char 的大小:1
float 的大小:4
double 的大小:8

C 語言資料型別大小

Note
注意:在 C 標準裡,1 byte 定義為 sizeof(char),不一定等於 8 bits(標準只保證 CHAR_BIT >= 8)。實務上大多是 8 bits,但不同系統與編譯器下,型別大小可能不同,例如:(Windows 64-bit 下是 4 bytes,但 Linux 64-bit 下通常是 8 bytes),因此建議跨平台程式使用 <stdint.h> 提供的固定大小型別,如 int32_tuint64_t

不過這裡要特別提醒,在不同平台(像編譯器或硬體架構)上,某些型別的大小可能會不一樣,例如 Windows x64 和 Linux x64 就有所差異。

「ㄟ…Boolean 呢?」你可能會問。

如果你本身已經有其他程式語言的開發經驗,可能會疑惑:為什麼到現在還沒提到 Boolean 這個資料型別?

在早期的 C 語言裡,其實沒有內建 Boolean 型別,而是直接用整數來表示: 0 代表 假(false),非零則代表 真(true)。

一直到 C99 才引入 <stdbool.h> ,裡面定義了 booltruefalse,所以如果你想在 C 裡使用 Boolean,可以這樣寫:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdbool.h>

int main(void) {
printf("bool 的大小:%zu\n", sizeof(bool));

printf("true 的值:%d\n", true);
printf("false 的值:%d\n", false);
return 0;
}

這樣你就會得到以下結果:

1
2
3
bool 的大小:1
true 的值:1
false 的值:0

那麼我們這一節就先介紹到這邊,讓自己也稍微緩衝一下,下一個章節就讓我們來聊聊讓我放棄 C 語言的元兇 —— 指標(Pointer)吧!

結語

回想起來,當時其實滿喜歡寫腳本的,可以把自己想像中的規則套用到遊戲裡,整個感覺就很爽。

同步更新

本文將同步更新至以下網站: