C 語言由 Dennis Ritchie 於 1972 年在貝爾實驗室開發,至今已超過 50 年歷史,仍然是世界上最重要的程式語言之一。
從零到精通 — C語言編程課程
C 語言
繁體中文教學
C 語言由 Dennis Ritchie 於 1972 年創造,C 語言是所有程式設計師的基礎。學習 C,你將真正理解電腦的運作原理,掌握記憶體管理與底層效能優化,立即在香港編程學院C語言自學,把C語言從入門到精通。
18+
章節
50+
程式範例
10
互動測驗
1972
誕生年份
#include <stdio.h>
/* 我的第一個 C 程式 */
int main() {
printf("Hello, World!\n");
return 0;
}
/* 我的第一個 C 程式 */
int main() {
printf("Hello, World!\n");
return 0;
}
📋 課程目錄
CH.01
C 語言簡介
歷史、特性與應用領域
CH.02
環境安裝
GCC、VS Code 設定
CH.03
基礎語法
程式結構與規則
CH.04
變數與型態
int, float, char, double
CH.05
運算子
算術、邏輯、位元
CH.06
輸出與輸入
printf / scanf 全解
CH.07
條件判斷
if, else, switch
CH.08
迴圈
for, while, do-while
CH.09
陣列
一維、多維陣列
CH.10
字串
字元陣列與 string.h
CH.11
函式
定義、呼叫、遞迴
CH.12
指標
C 語言最強大特性
CH.13
結構體
struct, typedef, union
CH.14
記憶體管理
malloc, free, stack/heap
CH.15
檔案處理
fopen, fread, fwrite
CH.16
前置處理器
#define, #include, #ifdef
CH.17
位元運算
AND, OR, XOR, 位移
CH.18
完整範例
排序、搜尋、實戰案例
C 語言簡介
了解 C 的歷史、特性與應用
誕生年份
1972 年,Dennis Ritchie 在貝爾實驗室開發
TIOBE 排名
長期位居前三,超過 40 年不衰的程式語言
執行效率
接近機器語言的速度,幾乎無效能損耗
應用廣泛
OS、嵌入式、資料庫、遊戲引擎核心
C 語言的應用領域
作業系統
嵌入式系統
資料庫引擎
遊戲引擎
網路協議
驅動程式
🌟 為什麼學習 C 語言?
C 語言是 Python、Java、C++、C# 等現代語言的基礎。學好 C,你將真正理解電腦的底層原理:記憶體如何分配、指標如何運作、資料如何在記憶體中儲存。這些知識會讓你成為更優秀的程式設計師。
C 語言是 Python、Java、C++、C# 等現代語言的基礎。學好 C,你將真正理解電腦的底層原理:記憶體如何分配、指標如何運作、資料如何在記憶體中儲存。這些知識會讓你成為更優秀的程式設計師。
C 語言
✅ 程序式語言
✅ 極高執行效率
✅ 底層記憶體控制
✅ 標準函式庫精簡
✅ 嵌入式首選
❌ 無內建OOP
✅ 極高執行效率
✅ 底層記憶體控制
✅ 標準函式庫精簡
✅ 嵌入式首選
❌ 無內建OOP
C++ 語言
✅ 物件導向+程序式
✅ 高執行效率
✅ 豐富標準函式庫
✅ 模板泛型程式
✅ 遊戲/應用開發
❌ 語法較複雜
✅ 高執行效率
✅ 豐富標準函式庫
✅ 模板泛型程式
✅ 遊戲/應用開發
❌ 語法較複雜
C — 第一個程式
hello.c
#include <stdio.h>
/* 這是一個單行/多行註解
C 語言的起點:Hello World */
int main() {
printf("Hello, World!\n");
printf("歡迎學習 C 語言!\n");
return 0; // 程式正常結束
}
▶ 輸出結果
Hello, World!
歡迎學習 C 語言!
歡迎學習 C 語言!
環境安裝
設定 GCC 編譯器與開發環境
1
下載 MinGW-w64
前往 mingw-w64.org 下載最新 installer,選擇 x86_64 架構安裝。
2
設定環境變數
將
C:\mingw64\bin 加入系統 PATH 環境變數,讓終端機找到 gcc 指令。3
安裝 VS Code
下載 Visual Studio Code,安裝 C/C++ Extension Pack 擴充套件。
4
驗證安裝
開啟終端機輸入
gcc --version,看到版本號表示安裝成功!1
安裝 Xcode Command Line Tools
開啟終端機,輸入
xcode-select --install,這會自動安裝 clang/gcc。2
或透過 Homebrew 安裝 GCC
先安裝 Homebrew,再執行
brew install gcc 取得最新版本。3
驗證並安裝 VS Code
輸入
gcc --version,確認可執行。安裝 VS Code 加上 C/C++ 擴充套件。1
Ubuntu/Debian 安裝 GCC
執行
sudo apt-get update && sudo apt-get install gcc build-essential2
驗證安裝
執行
gcc --version,確認 GCC 版本正確顯示。C 程式編譯流程
Shell — 編譯與執行
# 步驟1:撰寫程式碼,儲存為 hello.c
# 步驟2:使用 GCC 編譯
gcc hello.c -o hello
# 加上警告選項(建議開發時使用)
gcc -Wall -Wextra -o hello hello.c
# 步驟3:執行程式
./hello # Linux/macOS
hello.exe # Windows
# 完整除錯編譯
gcc -g -o hello hello.c # -g 加入偵錯資訊
💡 提示: 建議使用 -Wall 編譯選項,它會顯示所有警告,幫助你養成良好的程式習慣,及早發現潛在問題。
基礎語法
C 語言程式的結構與規則
每個 C 程式都有固定的結構。理解這個結構是學習 C 語言的第一步。
C — 程式結構解析
// 1. 前置處理指令(引入標頭檔)
#include <stdio.h> // 標準輸出入
#include <stdlib.h> // 一般工具
#include <string.h> // 字串處理
// 2. 全域變數(在所有函式外宣告)
int globalVar = 100;
// 3. 函式原型宣告
void greet(char *name);
// 4. 主函式(程式進入點)
int main() {
// 5. 區域變數
int age = 20;
char name[] = "小明";
// 6. 呼叫函式
greet(name);
printf("年齡: %d\n", age);
return 0; // 回傳 0 表示成功
}
// 7. 函式定義
void greet(char *name) {
printf("你好,%s!\n", name);
}
▶ 輸出結果
你好,小明!
年齡: 20
年齡: 20
⚠️ 重要規則:
• 每條語句以
• 程式區塊用
• C 語言區分大小寫(
•
•
• 每條語句以
; 結束• 程式區塊用
{ } 括起來• C 語言區分大小寫(
main ≠ Main)•
main() 是程式唯一的進入點•
return 0 表示程式正常結束
常用格式說明符
| 格式符 | 說明 | 範例 |
|---|---|---|
%d | 整數(十進位) | printf("%d", 42); |
%f | 浮點數 | printf("%f", 3.14); |
%.2f | 浮點數(保留2位小數) | printf("%.2f", 3.14159); |
%c | 字元 | printf("%c", 'A'); |
%s | 字串 | printf("%s", "Hi"); |
%p | 指標地址 | printf("%p", &x); |
%x | 十六進位 | printf("%x", 255); |
%o | 八進位 | printf("%o", 8); |
%ld | 長整數 | printf("%ld", 1000000L); |
%% | 印出 % 符號 | printf("100%%"); |
變數與資料型態
int, float, char, double, bool
| 型態 | 大小 | 範圍 | 格式符 | 範例 |
|---|---|---|---|---|
char | 1 byte | -128 ~ 127 | %c / %d | char c = 'A'; |
unsigned char | 1 byte | 0 ~ 255 | %u | unsigned char uc = 200; |
int | 4 bytes | -2,147,483,648 ~ 2,147,483,647 | %d | int n = 42; |
long | 4/8 bytes | 依平台而定 | %ld | long l = 10000L; |
long long | 8 bytes | 極大整數範圍 | %lld | long long ll = 999LL; |
float | 4 bytes | ~6-7 位有效數字 | %f | float f = 3.14f; |
double | 8 bytes | ~15-17 位有效數字 | %lf | double d = 3.14; |
_Bool | 1 byte | 0 (false) 或 1 (true) | %d | _Bool b = 1; |
C — 變數宣告與使用
#include <stdio.h>
#include <stdbool.h> // bool 型態
int main() {
// 基本型態宣告
int age = 25;
double price = 99.90;
char grade = 'A';
bool passed = true;
// 常數 (const 修飾)
const double PI = 3.14159265;
// 多變數同時宣告
int x = 10, y = 20, z = 30;
// 型態大小
printf("int 大小: %zu bytes\n", sizeof(int));
printf("double 大小: %zu bytes\n", sizeof(double));
// 型態轉換
int a = 7, b = 2;
double result = (double)a / b; // 強制轉型
printf("7/2 = %.2f\n", result);
printf("年齡:%d 成績:%c 通過:%s\n",
age, grade, passed ? "是" : "否");
return 0;
}
▶ 輸出結果
int 大小: 4 bytes
double 大小: 8 bytes
7/2 = 3.50
年齡:25 成績:A 通過:是
double 大小: 8 bytes
7/2 = 3.50
年齡:25 成績:A 通過:是
運算子
算術、比較、邏輯、位元運算
| 運算子 | 說明 | 範例 | 結果 |
|---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 減法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 7 / 2 | 3(整數除法) |
% | 取餘數 | 7 % 3 | 1 |
++ | 遞增 | x++ | x+1 |
-- | 遞減 | x-- | x-1 |
| 運算子 | 說明 | 範例 | 結果 |
|---|---|---|---|
== | 等於 | 5 == 5 | 1 (true) |
!= | 不等於 | 5 != 3 | 1 (true) |
> | 大於 | 5 > 3 | 1 (true) |
< | 小於 | 5 < 3 | 0 (false) |
>= | 大於等於 | 5 >= 5 | 1 (true) |
<= | 小於等於 | 3 <= 5 | 1 (true) |
| 運算子 | 說明 | 範例 | 結果 |
|---|---|---|---|
&& | AND(且) | 1 && 0 | 0 (false) |
|| | OR(或) | 1 || 0 | 1 (true) |
! | NOT(非) | !1 | 0 (false) |
| 運算子 | 說明 | 等同於 |
|---|---|---|
= | 指定 | x = 5 |
+= | 加後指定 | x = x + 5 |
-= | 減後指定 | x = x - 5 |
*= | 乘後指定 | x = x * 5 |
/= | 除後指定 | x = x / 5 |
%= | 模後指定 | x = x % 5 |
C — 運算子綜合示範
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("=== 算術運算 ===\n");
printf("%d + %d = %d\n", a, b, a+b);
printf("%d %% %d = %d\n", a, b, a%b);
printf("=== 遞增/遞減 ===\n");
int x = 5;
printf("x++ = %d\n", x++); // 先用再加
printf("++x = %d\n", ++x); // 先加再用
printf("=== 三元運算子 ===\n");
int score = 75;
char *result = (score >= 60) ? "及格" : "不及格";
printf("成績: %s\n", result);
return 0;
}
▶ 輸出結果
=== 算術運算 ===
10 + 3 = 13
10 % 3 = 1
=== 遞增/遞減 ===
x++ = 5
++x = 7
=== 三元運算子 ===
成績: 及格
10 + 3 = 13
10 % 3 = 1
=== 遞增/遞減 ===
x++ = 5
++x = 7
=== 三元運算子 ===
成績: 及格
輸出與輸入
printf / scanf / gets / getchar 完整指南
C — 輸出與輸入
#include <stdio.h>
int main() {
int age;
double weight;
char name[50];
// 讀取整數
printf("請輸入年齡: ");
scanf("%d", &age);
// 讀取浮點數
printf("請輸入體重(kg): ");
scanf("%lf", &weight);
// 讀取字串(不含空格)
printf("請輸入姓名: ");
scanf("%49s", name); // 防止緩衝區溢位
// 格式化輸出
printf("\n=== 個人資料 ===\n");
printf("姓名: %-10s\n", name); // 左對齊 10 字元
printf("年齡: %5d 歲\n", age); // 右對齊 5 字元
printf("體重: %6.1f kg\n", weight); // 保留1位小數
// 計算 BMI
double height = 1.70;
double bmi = weight / (height * height);
printf("BMI: %.2f\n", bmi);
return 0;
}
⚠️ 安全警告: 使用
scanf 讀取字串時,務必限制寬度(如 %49s),避免緩衝區溢位漏洞(Buffer Overflow)。在安全要求高的場合,建議使用 fgets() 代替。條件判斷
if / else if / else / switch
C — if/else 成績判斷
#include <stdio.h>
int main() {
int score = 85;
char grade;
// if / else if / else
if (score >= 90) {
grade = 'A';
printf("優秀!繼續保持\n");
} else if (score >= 80) {
grade = 'B';
printf("良好!\n");
} else if (score >= 70) {
grade = 'C';
printf("普通,需要加強\n");
} else {
grade = 'F';
printf("不及格,要努力了\n");
}
printf("分數: %d,等級: %c\n", score, grade);
// switch 陳述式
int day = 3;
switch (day) {
case 1: printf("星期一\n"); break;
case 2: printf("星期二\n"); break;
case 3: printf("星期三\n"); break;
case 6:
case 7: printf("週末!\n"); break;
default: printf("其他\n");
}
return 0;
}
▶ 輸出結果
良好!
分數: 85,等級: B
星期三
分數: 85,等級: B
星期三
迴圈
for / while / do-while / break / continue
C — 三種迴圈比較
#include <stdio.h>
int main() {
// 1. for 迴圈(已知次數)
printf("for 迴圈: ");
for (int i = 1; i <= 5; i++) {
printf("%d ", i);
}
printf("\n");
// 2. while 迴圈(條件成立)
printf("while 迴圈: ");
int n = 1;
while (n <= 5) {
printf("%d ", n);
n++;
}
printf("\n");
// 3. do-while 迴圈(至少執行一次)
printf("do-while: ");
int m = 1;
do {
printf("%d ", m);
m++;
} while (m <= 5);
printf("\n");
// break 與 continue
printf("奇數(1-10): ");
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue; // 跳過偶數
printf("%d ", i);
}
printf("\n");
// 九九乘法表
printf("九九乘法表:\n");
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 9; j++) {
printf("%d*%d=%2d ", i, j, i*j);
}
printf("\n");
}
return 0;
}
▶ 輸出結果
for 迴圈: 1 2 3 4 5
while 迴圈: 1 2 3 4 5
do-while: 1 2 3 4 5
奇數(1-10): 1 3 5 7 9
九九乘法表:
1*1= 1 1*2= 2 ... 1*9= 9
2*1= 2 ... 2*9=18
3*1= 3 ... 3*9=27
while 迴圈: 1 2 3 4 5
do-while: 1 2 3 4 5
奇數(1-10): 1 3 5 7 9
九九乘法表:
1*1= 1 1*2= 2 ... 1*9= 9
2*1= 2 ... 2*9=18
3*1= 3 ... 3*9=27
陣列
一維、二維陣列與陣列排序
C — 陣列操作完整示範
#include <stdio.h>
int main() {
// 一維陣列宣告與初始化
int scores[] = {85, 92, 78, 96, 88};
int n = sizeof(scores) / sizeof(scores[0]);
// 計算總和與平均
int sum = 0, max = scores[0], min = scores[0];
for (int i = 0; i < n; i++) {
sum += scores[i];
if (scores[i] > max) max = scores[i];
if (scores[i] < min) min = scores[i];
}
printf("總和=%d 平均=%.1f 最高=%d 最低=%d\n",
sum, (double)sum/n, max, min);
// 氣泡排序(由小到大)
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (scores[j] > scores[j+1]) {
int tmp = scores[j];
scores[j] = scores[j+1];
scores[j+1] = tmp;
}
}
}
printf("排序後: ");
for (int i = 0; i < n; i++)
printf("%d ", scores[i]);
printf("\n");
// 二維陣列(矩陣)
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
printf("3x3 矩陣:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
printf("%3d", matrix[i][j]);
printf("\n");
}
return 0;
}
▶ 輸出結果
總和=439 平均=87.8 最高=96 最低=78
排序後: 78 85 88 92 96
3x3 矩陣:
1 2 3
4 5 6
7 8 9
排序後: 78 85 88 92 96
3x3 矩陣:
1 2 3
4 5 6
7 8 9
字串
字元陣列與 string.h 函式庫
📌 C 字串本質: C 語言沒有內建字串型態。字串是以
'\0'(null terminator)結尾的字元陣列。例如 "Hello" 在記憶體中儲存為 H, e, l, l, o, \0(共6個byte)。| 函式 | 說明 | 範例 |
|---|---|---|
strlen(s) | 字串長度 | strlen("Hello") → 5 |
strcpy(dst,src) | 複製字串 | strcpy(b, "World") |
strcat(dst,src) | 串接字串 | strcat(a, b) |
strcmp(a,b) | 比較字串(0=相等) | strcmp("a","b") → -1 |
strchr(s,c) | 尋找字元 | strchr("Hello",'l') |
strstr(s,sub) | 尋找子字串 | strstr("Hello","ell") |
toupper(c) | 轉大寫字元 | toupper('a') → 'A' |
sprintf(buf,fmt,...) | 格式化寫入字串 | sprintf(s,"%d",42) |
C — 字串操作
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main() {
char s1[50] = "Hello";
char s2[] = "World";
printf("長度: %zu\n", strlen(s1));
strcat(s1, ", ");
strcat(s1, s2);
printf("串接: %s\n", s1);
// 反轉字串
char str[] = "ABCDE";
int len = strlen(str);
for (int i = 0; i < len/2; i++) {
char tmp = str[i];
str[i] = str[len-1-i];
str[len-1-i] = tmp;
}
printf("反轉: %s\n", str);
// 逐字轉小寫
char upper[] = "HELLO C";
for (int i = 0; upper[i]; i++)
upper[i] = tolower(upper[i]);
printf("小寫: %s\n", upper);
return 0;
}
▶ 輸出結果
長度: 5
串接: Hello, World
反轉: EDCBA
小寫: hello c
串接: Hello, World
反轉: EDCBA
小寫: hello c
函式
定義、傳值/傳址、遞迴函式
C — 函式全面示範
#include <stdio.h>
// 傳值(不影響原變數)
void swapByValue(int a, int b) {
int tmp = a; a = b; b = tmp;
printf("函式內: a=%d b=%d\n", a, b);
}
// 傳址(影響原變數)
void swapByRef(int *a, int *b) {
int tmp = *a; *a = *b; *b = tmp;
}
// 遞迴:計算階乘
long long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 遞迴:費氏數列
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
// 函式指標
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int main() {
int x = 10, y = 20;
swapByValue(x, y);
printf("傳值後: x=%d y=%d\n", x, y); // 不變
swapByRef(&x, &y);
printf("傳址後: x=%d y=%d\n", x, y); // 改變
printf("10! = %lld\n", factorial(10));
printf("費氏數列: ");
for(int i=0; i<10; i++)
printf("%d ", fib(i));
printf("\n");
// 函式指標
int (*op)(int, int);
op = add;
printf("add(3,4) = %d\n", op(3,4));
op = mul;
printf("mul(3,4) = %d\n", op(3,4));
return 0;
}
▶ 輸出結果
函式內: a=20 b=10
傳值後: x=10 y=20
傳址後: x=20 y=10
10! = 3628800
費氏數列: 0 1 1 2 3 5 8 13 21 34
add(3,4) = 7
mul(3,4) = 12
傳值後: x=10 y=20
傳址後: x=20 y=10
10! = 3628800
費氏數列: 0 1 1 2 3 5 8 13 21 34
add(3,4) = 7
mul(3,4) = 12
指標
C 語言最強大也最精華的特性
🔑 指標的本質: 指標是一個儲存「記憶體地址」的變數。
int *p = &x 表示 p 儲存變數 x 的地址。*p 表示取得該地址的值(解參考)。📌 記憶體模型示意圖
0x1000
42
←
int x = 42;
0x2000
0x1000
←
int *p = &x; // 儲存 x 的地址
*p == x == 42 ✓ &x == p == 0x1000 ✓
C — 指標完整示範
#include <stdio.h>
int main() {
int x = 42;
int *p = &x; // p 指向 x
printf("x 的值: %d\n", x);
printf("x 的地址: %p\n", (void*)&x);
printf("p 的值(地址): %p\n", (void*)p);
printf("*p(解參考): %d\n", *p);
// 透過指標修改值
*p = 100;
printf("修改後 x = %d\n", x);
// 指標與陣列(陣列名就是指標)
int arr[] = {10, 20, 30, 40, 50};
int *ap = arr;
printf("陣列元素: ");
for (int i = 0; i < 5; i++)
printf("%d ", *(ap + i)); // 指標算術
printf("\n");
// 雙重指標
int **pp = &p;
printf("**pp = %d\n", **pp);
// NULL 指標
int *null_p = NULL;
if (null_p == NULL)
printf("這是 NULL 指標(安全檢查)\n");
return 0;
}
▶ 輸出結果
x 的值: 42
x 的地址: 0x7fff...(每次不同)
p 的值(地址): 0x7fff...
*p(解參考): 42
修改後 x = 100
陣列元素: 10 20 30 40 50
**pp = 100
這是 NULL 指標(安全檢查)
x 的地址: 0x7fff...(每次不同)
p 的值(地址): 0x7fff...
*p(解參考): 42
修改後 x = 100
陣列元素: 10 20 30 40 50
**pp = 100
這是 NULL 指標(安全檢查)
結構體
struct / typedef / union / enum
C — 結構體完整示範
#include <stdio.h>
#include <string.h>
// 定義結構體
typedef struct {
char name[50];
int age;
double salary;
} Employee;
// 結構體函式
void printEmployee(Employee *e) {
printf("姓名:%-10s 年齡:%3d 薪資:%.0f\n",
e->name, e->age, e->salary);
}
// 列舉型態
typedef enum { MON, TUE, WED, THU, FRI, SAT, SUN } Day;
int main() {
// 初始化方式1:逐欄位
Employee emp1;
strcpy(emp1.name, "陳小明");
emp1.age = 28;
emp1.salary = 55000;
// 初始化方式2:指定初始化
Employee emp2 = {"林美玲", 32, 72000};
// 使用函式指標操作
printEmployee(&emp1);
printEmployee(&emp2);
// 結構體陣列
Employee team[] = {
{"王大同", 25, 48000},
{"張志豪", 35, 88000}
};
int size = sizeof(team)/sizeof(team[0]);
printf("=== 團隊成員 ===\n");
for(int i=0; i<size; i++)
printEmployee(&team[i]);
// 列舉使用
Day today = WED;
if (today == WED) printf("今天星期三\n");
return 0;
}
▶ 輸出結果
姓名:陳小明 年齡: 28 薪資:55000
姓名:林美玲 年齡: 32 薪資:72000
=== 團隊成員 ===
姓名:王大同 年齡: 25 薪資:48000
姓名:張志豪 年齡: 35 薪資:88000
今天星期三
姓名:林美玲 年齡: 32 薪資:72000
=== 團隊成員 ===
姓名:王大同 年齡: 25 薪資:48000
姓名:張志豪 年齡: 35 薪資:88000
今天星期三
記憶體管理
malloc / calloc / realloc / free
Stack(堆疊)
✅ 自動管理記憶體
✅ 速度極快
✅ 區域變數、函式參數
❌ 大小有限(通常幾MB)
❌ 函式結束即銷毀
✅ 速度極快
✅ 區域變數、函式參數
❌ 大小有限(通常幾MB)
❌ 函式結束即銷毀
Heap(堆積)
✅ 手動控制大小
✅ 生命週期可控
✅ 動態分配大型資料
❌ 必須手動 free
❌ 速度略慢於 stack
✅ 生命週期可控
✅ 動態分配大型資料
❌ 必須手動 free
❌ 速度略慢於 stack
C — 動態記憶體配置
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int n = 5;
// malloc: 配置 n 個 int 的空間(未初始化)
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("記憶體配置失敗!\n");
return 1;
}
for(int i=0; i<n; i++) arr[i] = (i+1) * 10;
// calloc: 配置並初始化為0
int *arr2 = (int*)calloc(n, sizeof(int));
// realloc: 重新調整大小
arr = (int*)realloc(arr, (2*n) * sizeof(int));
for(int i=n; i<2*n; i++) arr[i] = (i+1) * 10;
printf("動態陣列: ");
for(int i=0; i<2*n; i++)
printf("%d ", arr[i]);
printf("\n");
// 動態結構體陣列
char **names = (char**)malloc(n * sizeof(char*));
const char *nameList[] = {"小明","小華","小美","大雄","靜香"};
for(int i=0; i<n; i++) {
names[i] = (char*)malloc(20);
strcpy(names[i], nameList[i]);
}
printf("名單: ");
for(int i=0; i<n; i++) printf("%s ", names[i]);
// 釋放記憶體(重要!)
for(int i=0; i<n; i++) free(names[i]);
free(names);
free(arr); free(arr2);
printf("\n記憶體已釋放\n");
return 0;
}
▶ 輸出結果
動態陣列: 10 20 30 40 50 60 70 80 90 100
名單: 小明 小華 小美 大雄 靜香
記憶體已釋放
名單: 小明 小華 小美 大雄 靜香
記憶體已釋放
⚠️ 記憶體洩漏(Memory Leak): 每個
malloc/calloc/realloc 必須有對應的 free()。釋放後將指標設為 NULL,避免使用懸空指標(Dangling Pointer)。檔案處理
fopen / fread / fwrite / fclose
| 模式 | 說明 | 檔案不存在 |
|---|---|---|
"r" | 讀取 | 錯誤 |
"w" | 寫入(覆蓋) | 建立新檔 |
"a" | 附加寫入 | 建立新檔 |
"r+" | 讀取+寫入 | 錯誤 |
"w+" | 讀取+寫入(覆蓋) | 建立新檔 |
"rb" | 二進位讀取 | 錯誤 |
"wb" | 二進位寫入 | 建立新檔 |
C — 檔案讀寫操作
#include <stdio.h>
#include <stdlib.h>
int main() {
// ===== 寫入文字檔 =====
FILE *fp = fopen("data.txt", "w");
if (!fp) { perror("開啟檔案失敗"); return 1; }
fprintf(fp, "第一行:C 語言檔案處理\n");
fprintf(fp, "第二行:分數 %d\n", 95);
fputs("第三行:Hello File!\n", fp);
fclose(fp);
printf("✓ 寫入完成\n");
// ===== 讀取文字檔 =====
fp = fopen("data.txt", "r");
if (!fp) { perror("開啟失敗"); return 1; }
char line[256];
printf("檔案內容:\n");
while (fgets(line, sizeof(line), fp))
printf(" %s", line);
fclose(fp);
// ===== 二進位檔案 =====
int nums[] = {10, 20, 30, 40, 50};
fp = fopen("nums.bin", "wb");
fwrite(nums, sizeof(int), 5, fp);
fclose(fp);
int read_nums[5];
fp = fopen("nums.bin", "rb");
fread(read_nums, sizeof(int), 5, fp);
fclose(fp);
printf("二進位讀取: ");
for(int i=0; i<5; i++) printf("%d ", read_nums[i]);
printf("\n");
return 0;
}
前置處理器
#define / #include / #ifdef / 巨集
C — 前置處理器指令
#include <stdio.h>
// 常數定義
#define PI 3.14159265358979
#define MAX_SIZE 100
#define BUFFER_SIZE 1024
// 函式型巨集
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define ABS(x) ((x) < 0 ? -(x) : (x))
// 條件編譯
#define DEBUG_MODE 1
#ifdef DEBUG_MODE
#define DBG(msg) printf("[DEBUG] %s\n", msg)
#else
#define DBG(msg)
#endif
// 防止重複引入(Header Guard)
#ifndef MYLIB_H
#define MYLIB_H
// ... 標頭內容 ...
#endif
int main() {
printf("PI = %.10f\n", PI);
printf("MAX(7,12) = %d\n", MAX(7, 12));
printf("SQUARE(5) = %d\n", SQUARE(5));
printf("ABS(-42) = %d\n", ABS(-42));
DBG("程式開始執行");
// 預定義巨集
printf("編譯時間: %s %s\n", __DATE__, __TIME__);
printf("此行行號: %d\n", __LINE__);
printf("此檔名: %s\n", __FILE__);
return 0;
}
位元運算
AND / OR / XOR / NOT / 位移
| 運算子 | 名稱 | 說明 | 範例 | 結果 |
|---|---|---|---|---|
& | AND | 兩位都為1才為1 | 0b1100 & 0b1010 | 0b1000 (8) |
| | OR | 有一個為1就為1 | 0b1100 | 0b1010 | 0b1110 (14) |
^ | XOR | 不同才為1 | 0b1100 ^ 0b1010 | 0b0110 (6) |
~ | NOT | 位元反轉 | ~0b0000 | 0b1111... |
<< | 左移 | 乘以2的n次方 | 1 << 3 | 8 |
>> | 右移 | 除以2的n次方 | 16 >> 2 | 4 |
C — 位元運算實用技巧
#include <stdio.h>
int main() {
unsigned int flags = 0b00000000;
// 設定第 n 位元 (Set bit)
flags |= (1 << 2); // 設定第2位
flags |= (1 << 5); // 設定第5位
printf("設定後: %d\n", flags); // 36
// 清除第 n 位元 (Clear bit)
flags &= ~(1 << 2);
printf("清除後: %d\n", flags); // 32
// 切換第 n 位元 (Toggle bit)
flags ^= (1 << 5);
printf("切換後: %d\n", flags); // 0
// 判斷奇偶(最快速的方法)
int n = 42;
printf("%d 是 %s\n", n, (n&1) ? "奇數":"偶數");
// 乘除 2 的次方(位移比乘除更快)
printf("3 * 8 = %d (3 << 3)\n", 3 << 3);
printf("64 / 4 = %d (64 >> 2)\n", 64 >> 2);
// XOR 交換(不需暫存變數)
int a = 100, b = 200;
a ^= b; b ^= a; a ^= b;
printf("XOR交換: a=%d b=%d\n", a, b);
return 0;
}
▶ 輸出結果
設定後: 36
清除後: 32
切換後: 0
42 是 偶數
3 * 8 = 24 (3 << 3)
64 / 4 = 16 (64 >> 2)
XOR交換: a=200 b=100
清除後: 32
切換後: 0
42 是 偶數
3 * 8 = 24 (3 << 3)
64 / 4 = 16 (64 >> 2)
XOR交換: a=200 b=100
完整範例
實戰案例:排序、搜尋、鏈結串列
C — 快速排序(QuickSort)
#include <stdio.h>
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i+1], &arr[high]);
return i + 1;
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {64,25,12,22,11,90,3,55};
int n = sizeof(arr)/sizeof(arr[0]);
printf("排序前: ");
for(int i=0; i<n; i++) printf("%d ", arr[i]);
quickSort(arr, 0, n-1);
printf("\n排序後: ");
for(int i=0; i<n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
▶ 輸出結果
排序前: 64 25 12 22 11 90 3 55
排序後: 3 11 12 22 25 55 64 90
排序後: 3 11 12 22 25 55 64 90
C — 二分搜尋(Binary Search)
#include <stdio.h>
int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
int steps = 0;
while (left <= right) {
steps++;
int mid = left + (right - left) / 2;
printf(" 步驟%d: 比較 arr[%d]=%d\n", steps, mid, arr[mid]);
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
int main() {
int arr[] = {2,5,8,12,16,23,38,56,72,91};
int n = sizeof(arr)/sizeof(arr[0]);
int target = 23;
printf("搜尋 %d:\n", target);
int idx = binarySearch(arr, n, target);
if (idx != -1)
printf("找到!位置 arr[%d] = %d\n", idx, arr[idx]);
else
printf("找不到!\n");
return 0;
}
▶ 輸出結果
搜尋 23:
步驟1: 比較 arr[4]=16
步驟2: 比較 arr[7]=56
步驟3: 比較 arr[5]=23
找到!位置 arr[5] = 23
步驟1: 比較 arr[4]=16
步驟2: 比較 arr[7]=56
步驟3: 比較 arr[5]=23
找到!位置 arr[5] = 23
C — 鏈結串列(Linked List)
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node* createNode(int val) {
Node *n = (Node*)malloc(sizeof(Node));
n->data = val; n->next = NULL;
return n;
}
void push(Node **head, int val) {
Node *n = createNode(val);
n->next = *head;
*head = n;
}
void append(Node **head, int val) {
Node *n = createNode(val);
if (!*head) { *head = n; return; }
Node *cur = *head;
while (cur->next) cur = cur->next;
cur->next = n;
}
void printList(Node *head) {
while (head) {
printf("%d", head->data);
if(head->next) printf(" → ");
head = head->next;
}
printf(" → NULL\n");
}
int main() {
Node *list = NULL;
append(&list, 10);
append(&list, 20);
append(&list, 30);
push(&list, 5); // 插入頭部
printList(list);
return 0;
}
▶ 輸出結果
5 → 10 → 20 → 30 → NULL
C — 堆疊實作(Stack)
#include <stdio.h>
#define MAXSIZE 100
typedef struct {
int data[MAXSIZE];
int top;
} Stack;
void init(Stack *s) { s->top = -1; }
int isEmpty(Stack *s) { return s->top == -1; }
int isFull(Stack *s) { return s->top == MAXSIZE-1; }
void push(Stack *s, int val) {
if (isFull(s)) { printf("堆疊已滿\n"); return; }
s->data[++s->top] = val;
printf("push(%d)\n", val);
}
int pop(Stack *s) {
if (isEmpty(s)) { printf("堆疊為空\n"); return -1; }
return s->data[s->top--];
}
int peek(Stack *s) {
return isEmpty(s) ? -1 : s->data[s->top];
}
int main() {
Stack s;
init(&s);
push(&s, 10);
push(&s, 20);
push(&s, 30);
printf("頂端: %d\n", peek(&s));
printf("pop: %d\n", pop(&s));
printf("pop: %d\n", pop(&s));
return 0;
}
▶ 輸出結果
push(10)
push(20)
push(30)
頂端: 30
pop: 30
pop: 20
push(20)
push(30)
頂端: 30
pop: 30
pop: 20
C — 矩陣乘法
#include <stdio.h>
#define N 3
void multiply(int A[N][N], int B[N][N], int C[N][N]) {
for(int i=0; i<N; i++)
for(int j=0; j<N; j++) {
C[i][j] = 0;
for(int k=0; k<N; k++)
C[i][j] += A[i][k] * B[k][j];
}
}
void printMatrix(char *name, int M[N][N]) {
printf("%s:\n", name);
for(int i=0; i<N; i++) {
for(int j=0; j<N; j++)
printf("%4d", M[i][j]);
printf("\n");
}
}
int main() {
int A[N][N] = {{1,2,3},{4,5,6},{7,8,9}};
int B[N][N] = {{9,8,7},{6,5,4},{3,2,1}};
int C[N][N];
multiply(A, B, C);
printMatrix("A", A);
printMatrix("B", B);
printMatrix("C = A×B", C);
return 0;
}
▶ 輸出結果
C = A×B:
30 24 18
84 69 54
138 114 90
30 24 18
84 69 54
138 114 90
🎯 C 語言互動測驗
測試你對 C 語言的了解程度!共 10 題
第 1/10 題
得分: 0
🎉
🚀 繼續深入學習
恭喜完成 C 語言基礎課程!接下來挑戰資料結構、演算法或 C++ 進階開發。