跳到內容

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

不確定優先順序時,加括號就對了。

格式用在例子
%dintprintf("%d", 42)
%lldlong longprintf("%lld", 10000000000LL)
%uunsigned int
%zusize_t(sizeof、strlen 的結果)printf("%zu", sizeof(int))
%f、%.2fdouble(小數點後 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。用它來寫「讀到沒有輸入為止」的迴圈:
read_until_eof.c
/* 讀到輸入結束為止: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 5
7
10 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_fallthrough.c
/* 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 two
n = 2: two
n = 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 執行會直接抓到。
  • 陣列傳進函式時,會「退化」成指向第一個元素的指標,所以要另外把長度傳進去:
array_decay.c
/* 陣列傳進函式時,會「退化」成指向第一個元素的指標,函式裡就量不到陣列大小了。 */
#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) = 40
number of elements = 10
inside 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 安全
strings.c
/* 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 = 16
a == b ? no
strcmp(a, b) == 0 ? yes
buf = "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 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 */
指令作用
#include <檔案>把檔案內容貼進來
#define N 100之後所有的 N 都換成 100(純文字替換)
#define SQ(x) ((x) * (x))有參數的巨集
#ifdef X … #endif有定義 X 才編譯這一段

巨集是純文字替換,參數一定要加括號:

macro_pitfall.c
/* #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) = 5
SQUARE_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。
  • 字串用 == 比較。

(還沒寫)