數組要求元素類型統一,而結構體(struct)打破此限制,允許將不同類型的數據(成員)組合成單一實體。結構體變量屬於標量,支持整體賦值、取址、函數傳遞等操作,是構建復雜數據模型的基石。
struct Date {
int month;
int day;
int year;
};
struct Date d1, d2; // 必須帶 struct 關鍵字
最通用,便於跨文件復用,推薦作為默認選擇。
struct {
int month, day, year;
} d1, d2, *ptr;
無標籤名,無法在別處復用類型,適用於一次性、局部場景。注意:即使成員相同,兩個匿名結構體也被視為不同類型,變量間不可互賦。
struct Date { int m, d, y; } d1, d2;
緊湊但降低可讀性,不推薦大型項目使用。
typedef 別名化(最佳實踐)typedef struct Date {
int month, day, year;
} Date; // 新類型名
Date today = {8, 15, 2024}; // 直接使用 Date,無需 struct
配合匿名結構更簡潔:typedef struct { ... } Date;。這是現代C代碼的主流風格。
Date today = {8, 15, 2024}; 按聲明順序Date today = {.month=8, .year=2024}; 未指定成員自動歸零today = (Date){9, 1, 2024};局部結構體變量若不初始化,值不可預測,務必顯式賦值。
結構體變量用 .,指針用 ->(p->member 等價於 (*p).member)。
Date today = {8, 15, 2024};
Date *p = &today;
printf("%d-%d-%d\n", today.month, p->day, today.year);
結構體支持整體賦值(逐成員淺拷貝)、取址(&)、作為函數參數/返回值。這與數組「退化為指針」形成鮮明對比,使結構體行為更像基本類型。
輸入函數模式推薦:
// 模式1:返回結構體副本
Date getDate() {
Date d; scanf("%d%d%d", &d.month, &d.day, &d.year); return d;
}
// 模式2:傳出參數指針(鏈式調用友好)
Date* getDatePtr(Date *p) {
scanf("%d%d%d", &p->month, &p->day, &p->year); return p;
}
int main() { Date d; printDate(getDatePtr(&d)); }
typedef struct { int x, y; } Point;
typedef struct { Point p1, p2; } Rectangle;
Rectangle r = {{1,2}, {3,4}};
printf("%d %d\n", r.p1.x, r.p2.y); // 多級點運算符訪問
若有指針 Rectangle *rp = &r;,則 rp->p1.x、(rp->p1).x 均合法,但 rp->p1->x 錯誤(p1 非指針)。
Date schedule[5] = {{1,1,2024}, {2,14,2024}, ...};
for (int i=0; i<5; i++)
printf("%.2d/%.2d/%d\n", schedule[i].month, schedule[i].day, schedule[i].year);
結構體不能直接包含自身類型成員(無限遞歸),但可包含指向自身類型的指針:
// 錯誤
struct Node { int data; struct Node next; };
// 正確:自引用指針
struct Node { int data; struct Node *next; };
// 配合 typedef 需前向聲明標籤
typedef struct Node {
int data;
struct Node *next; // 必須用 struct Node,因為 Node 別名尚未生效
} Node;
這正是鏈表、樹等動態數據結構的核心定義模式。
結構體類型聲明若在函數內部,則僅該函數可見;通常應置於文件頂層或頭文件,供多模塊共享。結構體變量遵循標準塊作用域規則。
typedef struct Tag { ... } TypeName; 模式const 保護只讀參數