C 基礎速查
C 的程式由函式組成,從 main 開始依序執行。這一頁整理寫題目最常用的語法,每一項都附上最容易踩的坑。型態的部分另外整理在資料型態與範圍。
#include <stdio.h> /* 要用的函式庫 */
int add(int a, int b); /* 函式宣告:告訴編譯器這個函式長什麼樣子 */
int main(void) { /* 程式從這裡開始 */ int x = add(2, 3); printf("%d\n", x); return 0; /* 0 代表成功結束 */}
int add(int a, int b) { /* 函式定義:真正的內容 */ return a + b;}函式要先宣告(或先定義)才能使用。如果 add 寫在 main 後面,前面就要有一行宣告。
| 種類 | 運算子 | 要注意的 |
|---|---|---|
| 算術 | + - * / % | 整數相除會捨去小數:7 / 2 == 3。% 結果的正負號跟著被除數:-7 % 3 == -1 |
| 比較 | == != < > <= >= | 結果是 1(真)或 0(假)。if (x = 5) 是指定,不是比較 |
| 邏輯 | && || ! | 會短路:a && b 在 a 為假時不會計算 b |
| 位元 | & | ^ ~ << >> | 優先順序比 == 低:x & 1 == 0 其實是 x & (1 == 0),要加括號 |
| 指定 | = += -= *= /= %= | a += b 等於 a = a + b |
| 遞增 | ++ -- | i++ 先用再加,++i 先加再用;不要在同一個式子裡對同一個變數用兩次 |
| 條件 | 條件 ? A : B | 簡短的 if / else |
不確定優先順序時,加括號就對了。
printf 常用格式
Section titled “printf 常用格式”| 格式 | 用在 | 例子 |
|---|---|---|
%d | int | printf("%d", 42) |
%lld | long long | printf("%lld", 10000000000LL) |
%u | unsigned int | |
%zu | size_t(sizeof、strlen 的結果) | printf("%zu", sizeof(int)) |
%f、%.2f | double(小數點後 6 位、2 位) | printf("%.2f", 3.14159) → 3.14 |
%c | 一個字元 | |
%s | 字串(char 陣列) | |
%p | 位址 | |
%% | 印出 % 本身 |
格式可以加寬度和旗標:%5d 是至少 5 格、靠右對齊;%-5d 是靠左;%05d 是不足的地方補 0。詳細的例子在第 03 題的筆記。
- 要傳位址:
scanf("%d", &x);。字串陣列本身就是位址,所以不用加&:scanf("%s", name); - 讀
double要用%lf(printf 印double卻是用%f) " %c":%c前面加一個空白,會先跳過空白和換行再讀字元- scanf 回傳成功讀到幾個值,讀到輸入結束時回傳
EOF。用它來寫「讀到沒有輸入為止」的迴圈:
/* 讀到輸入結束為止:scanf 回傳「成功讀到幾個」,讀不到時回傳 EOF(-1)。 */#include <stdio.h>
int main(void) { int x, count = 0; long long sum = 0; while (scanf("%d", &x) == 1) { sum += x; count++; } printf("count = %d, sum = %lld\n", count, sum); return 0;}3 5710 20在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
count = 5, sum = 45在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
在終端機手動輸入時,要按 Ctrl+Z 再按 Enter 才代表輸入結束。
| 語法 | 什麼時候用 |
|---|---|
if / else if / else | 依條件分支 |
switch | 依「一個整數的值」分成很多種情況 |
for (初始; 條件; 更新) | 知道要跑幾次 |
while (條件) | 不知道要跑幾次,條件成立就繼續 |
do { } while (條件); | 至少要跑一次 |
break / continue | 跳出迴圈 / 直接進入下一圈 |
switch 最容易出錯的地方是忘了寫 break:
/* switch 的 case 只是「入口」:沒有 break 就會一路往下執行(fall through)。 */#include <stdio.h>
int main(void) { for (int n = 1; n <= 3; n++) { printf("n = %d:", n); switch (n) { case 1: printf(" one"); /* 忘了 break:會繼續執行 case 2 */ case 2: printf(" two"); break; case 3: printf(" three"); break; } printf("\n"); } return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
n = 1: one twon = 2: twon = 3: three在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
- C 的參數一律傳值:函式拿到的是複本,在函式裡修改參數不會影響呼叫的人。想讓函式修改外面的變數,要傳指標,見控制記憶體的必備指令。
- 不需要回傳值的函式,回傳型態寫
void。 - 沒有參數的函式,C 要寫成
int f(void)。
int a[5] = {1, 2}; /* 沒寫到的元素補 0:{1, 2, 0, 0, 0} */int b[5] = {0}; /* 全部是 0 */int grid[3][4]; /* 二維陣列:3 列 4 行 */size_t n = sizeof a / sizeof a[0]; /* 元素個數:5 */- 索引從 0 到 n − 1,用到
a[n]就是越界。C 不會幫你檢查,結果是未定義行為;開 Sanitizer 執行會直接抓到。 - 陣列傳進函式時,會「退化」成指向第一個元素的指標,所以要另外把長度傳進去:
/* 陣列傳進函式時,會「退化」成指向第一個元素的指標,函式裡就量不到陣列大小了。 */#include <stdio.h>
void show_size(int arr[]) { /* 這裡的 arr 其實是 int *,sizeof 拿到的是指標大小(編譯器會警告) */ printf("inside function: sizeof(arr) = %zu\n", sizeof(arr));}
int main(void) { int arr[10] = {0}; printf("in main: sizeof(arr) = %zu\n", sizeof(arr)); printf("number of elements = %zu\n", sizeof(arr) / sizeof(arr[0])); show_size(arr); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
in main: sizeof(arr) = 40number of elements = 10inside function: sizeof(arr) = 8在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
C 的字串就是以 '\0' 結尾的 char 陣列。"Roy" 實際上佔 4 個 bytes:'R' 'o' 'y' '\0'。
函式(<string.h>) | 作用 | 要注意的 |
|---|---|---|
strlen(s) | 字串長度,不含 '\0' | 每次呼叫都要從頭數,不要放在迴圈條件裡重複呼叫 |
strcmp(a, b) | 比較內容:相等回傳 0 | 不能用 == 比較字串 |
strcpy(dst, src) | 複製 | 不會檢查 dst 夠不夠大 |
strcat(dst, src) | 接在後面 | 不會檢查空間 |
strchr(s, c)、strstr(s, t) | 找字元、找子字串 | 找不到回傳 NULL |
snprintf(buf, size, ...) | 用 printf 的格式組字串 | 會自動截斷並補 '\0',比 strcpy / strcat 安全 |
/* C 的字串是以 '\0' 結尾的 char 陣列。 */#include <stdio.h>#include <string.h>
int main(void) { char name[16] = "Roy"; printf("strlen = %zu, sizeof = %zu\n", strlen(name), sizeof(name)); /* 3 和 16 */
/* 比較字串內容要用 strcmp;== 比的是兩個陣列的位址 */ char a[] = "abc", b[] = "abc"; printf("a == b ? %s\n", a == b ? "yes" : "no"); printf("strcmp(a, b) == 0 ? %s\n", strcmp(a, b) == 0 ? "yes" : "no");
/* 組合字串要確認空間夠:snprintf 會自動截斷並補上 '\0',比 strcpy / strcat 安全 */ char buf[8]; snprintf(buf, sizeof buf, "%s-%s", "hello", "world"); printf("buf = \"%s\" (truncated to fit %zu bytes)\n", buf, sizeof buf); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
strlen = 3, sizeof = 16a == b ? nostrcmp(a, b) == 0 ? yesbuf = "hello-w" (truncated to fit 8 bytes)在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
判斷字元種類用 <ctype.h>:isdigit、isalpha、isspace、tolower、toupper。傳進去之前要先轉成 unsigned char,例如 tolower((unsigned char)c),避免負數的字元值造成未定義行為。
struct
Section titled “struct”struct Student { char name[21]; int score;};
struct Student s = {"amy", 90};s.score += 5; /* 用 . 存取成員 */
struct Student *p = &s;p->score += 5; /* 透過指標用 ->,等於 (*p).score */
typedef struct Student Student; /* 之後可以只寫 Student,不用寫 struct Student */前置處理指令
Section titled “前置處理指令”| 指令 | 作用 |
|---|---|
#include <檔案> | 把檔案內容貼進來 |
#define N 100 | 之後所有的 N 都換成 100(純文字替換) |
#define SQ(x) ((x) * (x)) | 有參數的巨集 |
#ifdef X … #endif | 有定義 X 才編譯這一段 |
巨集是純文字替換,參數一定要加括號:
/* #define 是純文字替換,不是函式:參數和整個式子都要加括號。 */#include <stdio.h>
#define SQUARE_BAD(x) x * x#define SQUARE_GOOD(x) ((x) * (x))
int main(void) { /* SQUARE_BAD(1 + 2) 會被換成 1 + 2 * 1 + 2,結果是 5 */ printf("SQUARE_BAD(1 + 2) = %d\n", SQUARE_BAD(1 + 2)); printf("SQUARE_GOOD(1 + 2) = %d\n", SQUARE_GOOD(1 + 2)); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
SQUARE_BAD(1 + 2) = 5SQUARE_GOOD(1 + 2) = 9在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
if (x = 5):把 5 指定給x,條件永遠成立。比較要用==。- 整數除法:
1 / 2 == 0。要小數的話寫1.0 / 2,或先轉型(double)a / b。 - scanf 忘了
&:scanf("%d", x);會把資料寫到奇怪的位址。 - 陣列越界:迴圈寫成
i <= n而不是i < n。 - switch 忘了
break。 - 字串用
==比較。
用到這個主題的題目
Section titled “用到這個主題的題目”- 01|Hello, World! — ✔ 已完成
- 02|兩數相加 — ✔ 已完成
- 03|時間換算 — ✔ 已完成
- 04|閏年判斷 — 還沒完成
- 05|簡易計算機 — 還沒完成
- 06|FizzBuzz — 還沒完成
- 07|質數判斷 — 還沒完成
- 08|陣列反轉與統計 — 還沒完成
- 09|回文判斷 — 還沒完成
- 10|成績排序 — 還沒完成
(還沒寫)