目錄
第一性原理出發:我們要解決什么問題?
定義結構體(Defining Structures)
問題:名字太長怎么辦?
如何定義結構體變量?
結構體的大小(Size of Structures)
初始化結構體(Initialize Structures)
訪問結構體成員(Access Structures)
結構體的指針(Pointers to Structures)
如何通過指針訪問結構體成員?
👣 分析 (*p).width 的由來:
結構體作為函數參數(Structures as Parameters)
為什么結構體很重要?
第一性原理出發:我們要解決什么問題?
第一性原理是從根本出發,不依賴已有抽象來理解一個問題。
問題:我們如何表達復雜的數據?
在 C 語言里,我們已經知道可以用變量存儲數據,比如:
int width = 10;
int height = 5;
這兩個變量表示一個矩形的寬和高。但如果我們有多個矩形,就可能會有一堆變量:
int r1_width = 10, r1_height = 5;
int r2_width = 20, r2_height = 10;
?這會讓代碼變得混亂、不好維護。
💡第一性答案:我們要把相關數據打包成一個“新類型”
我們想要的是:把一個矩形的寬和高“綁定”在一起,像一個單元一樣使用它。
這就引出了結構體(Structure)的概念:
結構體是 C 語言提供的一種機制,它允許我們把多個不同或相同類型的數據組合在一起,形成一個“新的數據類型”。
定義結構體(Defining Structures)
我們可以用 struct
關鍵字定義一個矩形結構體:
struct Rectangle {int width;int height;
};
這段代碼做了什么?
-
它定義了一個新的類型,叫做
struct Rectangle,
可以用它創建變量 -
這個類型有兩個成員:
-
width
:整型,表示矩形的寬 -
height
:整型,表示矩形的高
-
可以把它理解為一個“結構體工廠”,以后我們可以用它制造一個一個“矩形對象”。
問題:名字太長怎么辦?
每次都寫 struct Rectangle
很麻煩,所以我們可以使用 typedef
來定義一個別名,簡化使用:
typedef struct Rectangle {int width;int height;
} Rectangle;
-
struct Rectangle
是原始名字 -
Rectangle
是你定義的新別名
這樣你以后可以直接寫:
Rectangle r1;
//等價于
struct Rectangle r1;
💡更進一步:定義和別名可以合并寫成匿名結構體
如果你不打算用 struct Rectangle
這種原始名,可以這樣簡寫:
typedef struct {int width;int height;
} Rectangle;
這也是合法的結構體定義方式,結構體沒有“名”,但有了一個別名 Rectangle
。?
如何定義結構體變量?
1?? 先定義結構體,再定義變量:
struct Rectangle {int width;int height;
};struct Rectangle r1, r2;
2?? 定義時直接創建變量:
struct Rectangle {int width;int height;
} r1, r2;
?3?? 使用 typedef 簡化后:
typedef struct {int width;int height;
} Rectangle;Rectangle r1, r2;
我們可以這樣用這個結構體定義實際的矩形:
struct Rectangle r1;
r1.width = 10;
r1.height = 5;
這段代碼:
-
創建了一個結構體變量
r1
-
給它的
width
和height
賦值
?現在 r1
是一個“真正的矩形”,有自己的寬和高!
我們可以像這樣使用它:
int area = r1.width * r1.height;
printf("Area: %d\n", area);
結構體的大小(Size of Structures)
🔎 問題:一個結構體到底占用多少內存?
我們可以用 sizeof()
運算符來查看:
#include <stdio.h>typedef struct {int width;int height;
} Rectangle;int main() {printf("Size of Rectangle: %lu\n", sizeof(Rectangle));return 0;
}
分析:它的大小是所有成員的大小之和嗎?
直覺上你可能以為是:
-
int width
:4 字節 -
int height
:4 字節 -
總共:8 字節
?這個答案大多數時候是對的,但是……
🧠現實中會有對齊(Padding)
有時結構體會包含“填充字節”(padding),為了讓內存對齊(alignment),提高訪問效率。
比如下面這個結構體:
struct Example {char a;int b;
};
你可能以為大小是 1 + 4 = 5
字節,但其實 sizeof(struct Example)
很可能是 8。
原因是:
-
char
占 1 字節 -
為了讓
int
對齊到 4 字節位置,會插入 3 個字節的“空白” -
所以實際內存布局是:
字節偏移 | 內容 |
---|---|
0 | a |
1~3 | 填充 |
4~7 | b |
如何減少結構體大小?
把小的字段放在一起,有時可以減少對齊浪費:
struct Compact {char a;char b;int c;
};
這樣會比 char
+ int
的組合更緊湊一些。?
初始化結構體(Initialize Structures)
有幾種方式可以給結構體變量賦初值。
?方法一:逐個成員賦值
r1.width = 10;
r1.height = 5;
方法二:使用初始化列表
可以直接一次性賦值:
Rectangle r2 = {20, 15};
它會按順序對應結構體中的成員:第一個是 width
,第二個是 height
。?
??結構體不能直接比較?
if (r1 == r2) { // 錯誤,結構體不能用 == 比較// ...
}
要比較結構體,得自己比較各個字段:
if (r1.width == r2.width && r1.height == r2.height) {// ...
}
訪問結構體成員(Access Structures)
訪問結構體成員有兩種主要方式:
1. 用點運算符 .
(用于結構體變量)
Rectangle r1 = {10, 5};
printf("Width: %d\n", r1.width);
printf("Height: %d\n", r1.height);
.
是成員訪問運算符,用于訪問結構體變量的成員。?
可以理解為:
-
你有一個“復合數據類型”(結構體)
-
用
.
告訴編譯器:“我要訪問這個結構里的某一個字段”
2. 用箭頭運算符 ->
(用于結構體指針)?
Rectangle *p = &r1;
printf("Width: %d\n", p->width);
printf("Height: %d\n", p->height);
這兩個方式的差別在于:
-
.
用于結構體變量本身 -
->
用于結構體指針
等價的寫法也可以是:
(*p).width // 與 p->width 等價,但不常用
結構體的指針(Pointers to Structures)
為什么我們需要結構體指針?
結構體通常存儲多個數據,傳遞或處理時如果結構體很大(有很多字段),傳結構體副本效率低、內存開銷大。
所以就像數組、字符串一樣,我們經常用“結構體指針”來:
-
節省內存
-
修改原始結構體的數據
-
在函數之間高效傳遞結構體
如何聲明和使用結構體指針?
假設我們有這個結構體:
typedef struct {int width;int height;
} Rectangle;Rectangle r1 = {10, 5};
聲明結構體指針,并指向變量 r1:
Rectangle *p = &r1; // p 是一個指向 Rectangle 的指針
-
p
存儲的是r1
的地址 -
*p
表示通過指針訪問 r1
如何通過指針訪問結構體成員?
有兩種方式:
方式一:箭頭運算符 ->
p->width // 訪問 r1 的 width
p->height // 訪問 r1 的 height
這是最常用的方式,代碼簡潔可讀性好。?
方式二:使用 (*p).member
?
👣 分析 (*p).width
的由來:
-
p
是一個結構體指針,指向r1
-
*p
就是解引用,表示r1
本體 -
(*p).width
的意思是:
-
先從指針
p
拿到結構體r1
-
再取
r1.width
💡 但注意括號一定要加!
如果寫成 *p.width
是錯誤的,解釋如下:
-
運算符優先級中
.
的優先級高于*
-
所以
*p.width
實際上是*(p.width)
,這表示的是 “p 的成員 width 的值,再解引用”,根本不是我們要的意思!
🔁 所以:正確寫法是 (*p).member,
等價于:p->member
你可以理解為:p->width ? ≡ ? (*p).width
但 ->
是語法糖,更方便。
結構體作為函數參數(Structures as Parameters)
在 C/C++ 語言中,結構體可以作為函數的參數傳遞進去。根據不同的傳遞方式,它的行為會有非常大的區別。主要有三種方式:
按值傳遞(Pass by Value)
結構體按值傳遞時,函數接收到的是結構體的一份副本(copy),修改這個副本不會影響原始結構體。
#include <stdio.h>typedef struct {int width;int height;
} Rectangle;void change(Rectangle r) {r.width = 999;
}int main() {Rectangle r1 = {10, 5};change(r1);printf("r1.width = %d\n", r1.width); // 輸出 10,而不是 999return 0;
}
-
change(r1);
把r1
拷貝了一份,傳給了函數 -
函數里
r.width = 999;
修改的是副本,不影響r1
本體
?按指針傳遞(Pass by Pointer)
把結構體變量的地址傳遞給函數。函數可以通過這個地址訪問并修改結構體的內容。
#include <stdio.h>typedef struct {int width;int height;
} Rectangle;void change(Rectangle *p) {p->width = 999; // 或 (*p).width = 999;
}int main() {Rectangle r1 = {10, 5};change(&r1); // 注意傳的是地址printf("r1.width = %d\n", r1.width); // 輸出 999return 0;
}
-
change(&r1)
傳遞結構體的地址 -
p->width = 999;
直接修改原始結構體變量r1
-
指針傳遞效率高,尤其是結構體比較大時非常有用
按引用傳遞(?Pass by Reference)
使用 &
,函數接收的是真實結構體的“別名”,不會產生拷貝,函數內部的修改會作用于原始變量。
#include <iostream>
using namespace std;struct Rectangle {int width;int height;
};void change(Rectangle &r) {r.width = 999;
}int main() {Rectangle r1 = {10, 5};change(r1);cout << "r1.width: " << r1.width << endl; // 輸出 999 return 0;
}
-
省去了拷貝,性能高
-
可以直接修改原始數據
也可以升級為按常量引用傳遞?(Pass by const
Reference)
這是 C++ 最推薦的方式之一,特別是只讀訪問結構體時。
void printArea(const Rectangle &r) {cout << "Area: " << r.width * r.height << endl;
}
為什么結構體很重要?
結構體帶來的核心好處是:
-
組織數據:相關的數據(比如矩形的寬和高)可以組合在一起
-
可讀性強:代碼語義更清晰
-
可擴展性強:以后如果要添加更多屬性(比如顏色、邊框樣式),直接在結構體中加字段就行
例如,我們要擴展 Rectangle
加一個顏色:
struct Rectangle {int width;int height;char color[20];
};
結構體 + 函數 = 更強大!
你甚至可以定義一個函數來計算矩形面積,結構體作為參數傳入:
int getArea(struct Rectangle r) {return r.width * r.height;
}int area = getArea(r1);