1 struct 基礎:把多種型態包成一個複合型態
struct 讓我們把多個不同型態的資料包成一個自訂的複合型態——可以想成一個「資料夾」,把姓名、分數這些原本各自獨立的變數,裝進同一個袋子裡打包管理。
struct Student {
int id;
float score;
};
struct Student s1; // 宣告一個Student結構變數
s1.id = 1;
s1.score = 88.5;
結構裡的成員可以是任意型態的組合,也完全可以包含多個相同型態的成員(例如兩個 int、三個 float 都合法)。struct 唯一的規則是:成員必須各自有不同的「名稱」,但型態不需要互不相同。
2 結構成員存取:. 與 -> 的選擇
存取結構成員的運算子要看手上拿的是什麼:若是「結構變數」本身,用 .(例如 student.score);若是「指向結構的指標」,則用 ->(例如 p->score,等同於 (*p).score)。
Student s1;
s1.id = 1; s1.score = 88.5; // 結構變數:用 .
Student *p = &s1; // 結構指標:用 ->
p->score = 90.0; // 等同於 (*p).score = 90.0;
| 手上拿的是 | 該用哪個運算子 | 範例 |
|---|---|---|
| 結構「變數」 | . | s1.score |
| 結構「指標」 | -> | p->score,等同 (*p).score |
「箭頭穿過指標,點點停在變數上」——看到 ->(箭頭),代表左邊那個東西是「指標」,箭頭幫你自動穿過去指向的位置;看到 .(點),代表左邊那個東西已經是「結構本身」,不需要再穿過任何東西。
3 typedef:幫複雜的型態取一個簡短的別名
typedef 可以幫複雜的型態(尤其是 struct)取一個簡短的別名,之後就能直接用這個別名當作型態名稱來宣告變數,不需要每次都重複寫冗長的 struct 結構名稱。
// 寫法一:匿名結構直接取別名
typedef struct {
int id;
float score;
} Student;
Student s1; // 直接用Student宣告,不用寫struct
// 寫法二:結構有自己的標籤(tag),同時再取一個別名
typedef struct studentScore {
int id;
float score;
} SCORE;
SCORE student[50]; // 用別名SCORE宣告一個結構陣列
struct studentScore s2; // 也可以繼續用原本的標籤搭配struct宣告
SCORE 是一個型態名稱(typedef定義出來的別名),不是變數,不能對它做索引 SCORE[27] 或用 . 存取成員;真正拿來存放資料、可以用索引或 . 存取的,是用這個型態宣告出來的變數(例如 student)。
4 sizeof(struct) 與記憶體對齊(alignment)
sizeof(struct) 常常會大於各成員大小直接相加的總和,原因是記憶體對齊(alignment):CPU讀取記憶體時,某些型態(尤其是指標)習慣從特定倍數的位址開始讀取效率才高,編譯器會在成員之間自動插入一些沒有用到的「補位(padding)」位元組,讓每個成員都乖乖對齊到適合的位址。
typedef struct class_st {
int id; // 4 bytes
char *name; // 8 bytes(64位元指標)
float score; // 4 bytes
} CLASS;
// 直接相加:4+8+4=16 bytes
// 但 sizeof(CLASS) 實際上是 24 bytes(因為對齊而插入了padding)
| 位移(offset) | 內容 | 說明 |
|---|---|---|
| 0~3 | int id | 佔4 bytes |
| 4~7 | (padding) | 補4 bytes,讓下一個指標成員從8的倍數位址開始 |
| 8~15 | char *name | 佔8 bytes(64位元指標) |
| 16~19 | float score | 佔4 bytes |
| 20~23 | (padding) | 補4 bytes,讓整個struct大小是8的倍數(24) |
sizeof(int)=4、sizeof(char)=1、sizeof(任何指標型態)=8(不管是 int*、char* 還是其他型態的指標,因為指標存放的都是位址,位址在64位元系統上固定佔8 bytes)。sizeof(結構陣列) 則是「單一結構大小 × 元素個數」。
5 malloc() 動態配置記憶體與 free()
malloc() 會在程式執行期間依需要的大小向系統要一塊記憶體,回傳指向這塊記憶體的指標;這塊記憶體不會自動消失,程式用完後要自己呼叫 free() 歸還,否則會造成記憶體洩漏。
#include <stdlib.h>
typedef struct { int id; float score; } Record;
Record *r = (Record *)malloc(sizeof(Record));
r->id = 1;
r->score = 95.5;
printf("id=%d score=%.1f\n", r->id, r->score);
free(r); // 用malloc配置的記憶體,使用完務必呼叫free()釋放,避免記憶體洩漏
malloc(sizeof(Record)) 精確配置了剛好能容納一個 Record 結構的空間,不多也不少。這個特性在下一節會延伸出一個統測最愛的陷阱。
6 結構裡的指標成員:malloc 不會連帶配置它指向的空間
如果結構裡有指標成員(如 char *name),malloc(sizeof(CLASS)) 只會配置結構本身的空間(包含足夠存放 name 這個指標「位址值」本身的空間),並不會連帶替這個指標配置到它未來要指向的字串空間。這是統測結構題最愛出的陷阱。
typedef struct { int id; char *name; float score; } CLASS;
CLASS *c = (CLASS *)malloc(sizeof(CLASS)); // 只配置了CLASS本身
c->id = 1;
strcpy(c->name, "Brown"); // ❌ 危險!c->name還沒有指向任何有效的記憶體
// c->name此時是「野指標」,往它寫入資料是未定義行為
CLASS *c = (CLASS *)malloc(sizeof(CLASS));
c->id = 1;
c->name = (char *)malloc(20 * sizeof(char)); // ✅ 額外配置name指向的字串空間
strcpy(c->name, "Brown"); // 現在才是安全的寫入
結構本身是「第一層」記憶體,靠一次 malloc(sizeof(CLASS)) 就能配置好;但結構裡的指標成員所指向的內容是「第二層」,需要再額外呼叫一次 malloc 才能安全使用,兩者是獨立的兩塊記憶體,缺一不可。
7 結構陣列:管理多筆記錄
結構陣列讓我們用同一個型態一次管理多筆資料(例如全班的學生資料),每個元素都是一份完整、獨立的結構,用 . 存取各自的成員。
typedef struct { char name[10]; int score; } Student;
Student list[2] = { {"Amy", 88}, {"Ben", 92} };
for (int i = 0; i < 2; i++)
printf("%s: %d\n", list[i].name, list[i].score);
Ben: 92
如果需要記錄「每一筆資料都有好幾種屬性」(例如每隻寵物都有名字、出生年、性別、體重),用一個結構陣列(每個元素是一份完整記錄)遠比用好幾個各自獨立的陣列(name陣列、birth_year陣列……)來得正確又好維護,尤其是像「中文名字」這種本身就需要好幾個字元才能存放的欄位,單一個字元陣列只能存一筆,無法對應到100筆各自不同的名字。
8 111~115 歷屆統測真題全選項深度剖析(共 7 題)
以下收錄統測專業科目(二)近年全部 7 題結構、typedef與動態記憶體真題。每題均提供高解析度原始考題掃描圖、互動選項按鈕與所有選項逐項深入解析:
🔍 四個選項逐項深度剖析
#include <stdio.h>
#define N 50
void main(){
typedef struct studentScore {
int id; //學號
float score; //成績
} SCORE;
SCORE student[N], *p;
float sscore;
p = student+28;
...
🔍 四個選項逐項深度剖析
student[28-1]=student[27]。student 是結構陣列,取用其 score 成員應以 . 運算子存取,即 student[27].score,選 (A)。[27] 或用 . 存取成員;student 才是真正的結構陣列變數,這個選項把型態名稱跟變數名稱的角色完全弄反了,語法不合法。-> 運算子存取(-> 是給指標用的,陣列要用 . 搭配索引);而且 score[27] 也是錯的寫法——score 是單一 float 成員,不是陣列,不能對它取索引。-> 和 score[29] 索引語法錯誤的問題,索引數字29對應到的其實會是學號30的資料(student[29]對應學號30),跟題目要求的學號28(對應student[27])也不符。🔍 四個選項逐項深度剖析
n(n-1)/2。n=N=50,故比較次數為 50×49/2=1225,選 (B)。n(n-1)/2。#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct class_st {
int id;
char *name;
float score;
} CLASS;
CLASS *class;
int main(){
class = (CLASS *)malloc(sizeof(CLASS));
(*class).id = 1;
strcpy((*class).name, "Brown");
printf("%s", class->name);
return 0;
}
🔍 四個選項逐項深度剖析
malloc(sizeof(CLASS)) 只配置了 CLASS 結構本身(含 name 這個指標欄位所佔的位元組空間,用來存放一個位址),並沒有替 name 指標另外配置它「所指向」的字串緩衝區。(*class).name 此時是一個未初始化的野指標,strcpy 試圖把 "Brown" 寫入這塊無效、未配置的記憶體,會導致未定義行為,選 (A)。CLASS *class),class.name 這種寫法對指標使用 . 運算子是不合法的語法,指標必須用 -> 或先解參考 (*class) 再用 .;這個選項提出的修改方式本身就無法通過編譯,也沒有解決 name 指標未配置記憶體的根本問題。class->name 與 (*class).name 是完全等價的兩種寫法,printf 使用哪一種寫法都不影響結果;問題根本不在 printf 使用哪種存取語法,而在於 name 指標從一開始就沒有指向任何有效的、已配置好的記憶體空間。typedef struct class {
int id; char *name; float score;
} CLASS;
CLASS classB[49];
int main(){
CLASS *pC, classA[50], classB[60];
int i=10, *pi, m, n, p, a, b, c, d, e, f, g;
m=sizeof(int); n=sizeof(char); p=sizeof(int *);
a = sizeof(classA); b = sizeof(classB[59]);
c = sizeof(pC); d = sizeof(i);
e = sizeof(&i); f = sizeof(pi);
g = sizeof(CLASS);
}
🔍 四個選項逐項深度剖析
m<n(4<1)明顯不成立,這個不等式關係整個方向反了——實際上 m(4) 遠大於 n(1),不是小於。m≧n 即 4≧1 成立,選 (B)。m>n(4>1)成立沒錯,但後半段 n>p(1>8)不成立——n(1) 遠小於 p(8),不是大於,這個不等式在後半段方向反了。p≦n 即 8≦1 明顯不成立,p(8) 遠大於 n(1),不是小於等於,這個不等式的方向也反了。🔍 四個選項逐項深度剖析
int id(4 bytes)+char *name(8 bytes指標,因對齊在前面補4 bytes padding)+float score(4 bytes),sizeof(CLASS)=g=24 bytes。a=sizeof(classA),classA 是 main() 內宣告的CLASS結構陣列共50個元素,故 a=50×24=1200,選 (A)。🔍 四個選項逐項深度剖析
name[100] 只是「一個」字元陣列(一個字串),最多只能存放一隻毛小孩的名字,並不是100個獨立的名字欄位;其餘三個陣列雖然各自能存100筆資料,但name無法對應存放100隻毛小孩各自的名字,整體來說無法完整記錄100隻毛小孩的資訊。name[20] 依然只是「一個」字元陣列(一個字串),只能存放一隻毛小孩的名字,即使長度縮短成20也沒有改變它只能存一筆名字資料的本質,無法對應100隻毛小孩各自不同的名字。struct pet{...} 定義好一隻毛小孩需要的四項資料打包成一個結構型態,再宣告 Pets[100] 為這個結構的陣列,每個陣列元素 Pets[i] 都是一份完整、獨立的毛小孩資料(各自的name、birth_year、gender、weight),可以正確地各自儲存100隻毛小孩完整的四項資訊,選 (C)。struct pet[100]{...} 這種語法不合法,C 語言不能在定義struct標籤(宣告結構型態的當下)直接於標籤名稱後面接上陣列大小 [100],陣列大小必須接在「變數名稱」後面(如選項C的 Pets[100]),這是編譯不會通過的語法錯誤。9 高職段考與模擬精選實戰題(5 題全選項解析)
透過以下 5 題精選模擬試題,全面檢驗你對struct基礎、成員存取、typedef、記憶體對齊與malloc的掌握度:
struct Point {
int x;
int y;
};
struct Point p1 = {3, 4};
printf("%d %d", p1.x, p1.y);
🔍 四個選項逐項深度剖析
. 運算子直接存取印出 "3 4",選 (A)。{3,4} 是依照成員宣告順序對應賦值,第一個值3對應第一個宣告的成員x,第二個值4對應第二個宣告的成員y,不會自動對調。{3,4} 這種初始化語法沒有真正生效,但 p1 確實透過大括號初始化被賦予了3和4這兩個值,不會維持0。struct Book { char title[20]; int pages; };
struct Book b1 = {"C Programming", 300};
struct Book *pb = &b1;
printf("%d %d", b1.pages, pb->pages);
🔍 四個選項逐項深度剖析
. 存取 b1.pages 得到300;pb 是指向b1的結構指標,用 -> 存取 pb->pages,等同於 (*pb).pages,同樣取得b1的pages成員,也是300。兩種存取方式指向同一份資料,結果自然相同,選 (A)。pb->pages 這種指標存取方式沒有正確取得b1實際的值。但 pb 明確是透過 &b1 指向b1,pb->pages 就是在存取 b1.pages 這同一塊記憶體,結果必然一致,不會是0。. 用在結構變數、-> 用在結構指標都是完全合法的標準C語法,這段程式碼可以正常編譯執行,並沒有語法錯誤。. 與 -> 都能正確存取到同一份資料這個事實的誤解。typedef struct { int day, month, year; } Date;
Date today = {18, 8, 2026};
printf("%d/%d/%d", today.month, today.day, today.year);
🔍 四個選項逐項深度剖析
typedef struct {...} Date; 定義了一個匿名結構,並直接把型態別名取為Date,之後可以直接用Date當作型態名稱宣告變數。today 依初始化列表依序對應 day=18, month=8, year=2026。printf 依引數順序印出 today.month(8)、today.day(18)、today.year(2026),得 "8/18/2026",選 (A)。today.month, today.day, today.year(先印month再印day),不能自行對調。typedef struct {...} Date; 是完全合法的typedef+匿名結構寫法,這正是typedef最常見的用途——讓使用者之後可以直接用Date這個簡短的別名宣告變數,不需要每次都寫冗長的 struct 結構名稱,不會產生編譯錯誤。struct Mix {
char c;
int n;
};
printf("%d", sizeof(struct Mix) >= sizeof(char) + sizeof(int));
🔍 四個選項逐項深度剖析
sizeof(struct Mix) 通常會是8 bytes,大於單純把成員大小相加的 1+4=5 bytes。所以判斷式為真(非0),printf印出1,選 (A)。printf("%d", 判斷式) 印出的是判斷式的「真假結果」(1或0),不是 sizeof(char)+sizeof(int) 這個總和本身的數值,這個答案誤把要印出的內容當成了純粹的大小總和。sizeof(struct Mix) 本身的數值,但程式碼裡printf要印的是一個比較判斷式(>= 運算的結果,只會是1或0),不是某個sizeof的原始數值。typedef struct { char *msg; } Note;
Note *n = (Note *)malloc(sizeof(Note));
n->msg = (char *)malloc(20 * sizeof(char));
strcpy(n->msg, "Hi");
printf("%s", n->msg);
🔍 四個選項逐項深度剖析
malloc(sizeof(Note)) 配置Note結構本身的空間,接著額外再呼叫一次malloc,特別替 n->msg 這個指標配置了20個字元的緩衝區,讓它真正指向一塊有效、可以寫入的記憶體,之後strcpy才能安全地把"Hi"複製進去,printf正常輸出"Hi",選 (A)。strcpy(n->msg, "Hi") 確實把"Hi"(連同結尾的'\0')複製進了msg指向的緩衝區,msg並不是空字串,printf會正常印出"Hi"這兩個字元,不會是空字串。10 考前衝刺 10 秒速記口訣 & 核心知識檢核清單
結構成員可以是任意型態組合,完全允許多個相同型態的成員。
「箭頭穿過指標,點點停在變數上」——結構變數用.,結構指標用->。
型態別名(如SCORE)不能被索引或用.存取,真正的結構陣列變數才可以。
sizeof(struct)常大於成員大小總和,因編譯器插入padding對齊指標等成員。
malloc(sizeof(結構))只配置結構本身,指標成員指向的空間要再malloc一次。
動態配置的記憶體不會自動釋放,用完務必呼叫free()歸還,避免記憶體洩漏。
記錄多筆、多欄位的資料時,一個結構陣列比好幾個各自獨立的陣列更正確好維護。