vps掛網(wǎng)站哈爾濱網(wǎng)站建設(shè)
1. 聯(lián)合體
C語言中的聯(lián)合體(Union)是一種數(shù)據(jù)結(jié)構(gòu),它允許在同一內(nèi)存位置存儲不同類型的數(shù)據(jù)。不同于結(jié)構(gòu)體(struct
),結(jié)構(gòu)體的成員各自占有獨立的內(nèi)存空間,而聯(lián)合體的所有成員共享同一塊內(nèi)存區(qū)域。這意味著在同一時間,聯(lián)合體中只能存儲一個成員的值,其他成員會被覆蓋。
1.1 聯(lián)合體的基本語法
聯(lián)合體的聲明與結(jié)構(gòu)體相似,使用關(guān)鍵字union
來定義。
編譯器只為最大的成員分配足夠的內(nèi)存空間。聯(lián)合體的特點是所有成員共用同一塊內(nèi)存空間。所以聯(lián)合體也叫:共用體。
一個簡單的聯(lián)合體例子如下:
#include <stdio.h>union Data {int i;float f;char str[20];
};int main() {union Data data;data.i = 10;printf("data.i = %d\n", data.i);data.f = 220.5;printf("data.f = %.2f\n", data.f);// 注意,data.i的值會被覆蓋printf("data.i = %d\n", data.i); // 這個值會發(fā)生變化return 0;
}
1.2 聯(lián)合體的內(nèi)存分配與大小計算
在C語言中,聯(lián)合體的所有成員都共享同一塊內(nèi)存區(qū)域。當(dāng)你定義一個聯(lián)合體時,它的內(nèi)存空間并不會為每個成員分配獨立的內(nèi)存,而是為所有成員分配一塊共享的內(nèi)存區(qū)域。這樣,聯(lián)合體的內(nèi)存大小至少等于成員中最大類型的成員大小。
舉個例子:
#include <stdio.h>union Data {int i; // 4 字節(jié)float f; // 4 字節(jié)char str[20]; // 20 字節(jié)
};int main() {printf("Size of union Data: %lu\n", sizeof(union Data));return 0;
}
解釋:
int i
通常占用 4 字節(jié)。float f
通常也占用 4 字節(jié)。char str[20]
占用 20 字節(jié)(每個字符占 1 字節(jié))。
由于聯(lián)合體的成員共享內(nèi)存,它的大小等于其中最大成員的大小。在這個例子中,char str[20]
的大小是 20 字節(jié),因此聯(lián)合體的大小會是 20 字節(jié)。換句話說,聯(lián)合體的內(nèi)存分配通常是由它的最大成員決定的,且內(nèi)存中只能保存一個成員的數(shù)據(jù)。
輸出:
Size of union Data: 20
1.2.1 聯(lián)合體的內(nèi)存對齊
除了最大成員的大小外,還要注意內(nèi)存對齊(memory alignment)。C語言中,對于每個數(shù)據(jù)類型,通常都有對齊要求。具體對齊方式與系統(tǒng)架構(gòu)、編譯器有關(guān)(在C語言第17節(jié):自定義類型——結(jié)構(gòu)體已經(jīng)講過了,點擊鏈接即可查看)。內(nèi)存對齊的目的是為了提高訪問效率,因此編譯器往往會將數(shù)據(jù)類型按一定的字節(jié)邊界對齊(例如,4字節(jié)對齊、8字節(jié)對齊等)。
內(nèi)存對齊的示例:
假設(shè)我們使用的是32位或64位的架構(gòu),它可能要求對齊到4字節(jié)邊界。我們來觀察一下一個包含不同類型成員的聯(lián)合體的內(nèi)存分配。
// VS2022 MSVC
#include <stdio.h>union Example {char c; // 1 字節(jié)int i; // 4 字節(jié)double d; // 8 字節(jié)
};int main() {printf("Size of union Example: %lu\n", sizeof(union Example));return 0;
}
解釋:
char c
占用 1 字節(jié)。int i
占用 4 字節(jié)。double d
占用 8 字節(jié)。
但由于內(nèi)存對齊的原因,聯(lián)合體的實際大小可能會比這些單獨成員的大小之和要大。通常,為了提高訪問速度,編譯器會插入一些填充字節(jié)(padding),使得聯(lián)合體的內(nèi)存大小是最大對齊數(shù)的倍數(shù)。
輸出:
Size of union Example: 8
為什么是8字節(jié)呢?因為聯(lián)合體中最大成員是double d
,它的大小是8字節(jié),而且對齊數(shù)也是8。因此,整個聯(lián)合體的大小會是8字節(jié)。
1.2.2 聯(lián)合體內(nèi)存分配的詳細(xì)說明
- 成員共享內(nèi)存:
- 聯(lián)合體中的所有成員共享同一塊內(nèi)存區(qū)域。在任何時刻,聯(lián)合體的內(nèi)存中只會保存一個成員的值。
- 聯(lián)合體的大小通常由最大成員的大小決定,因為它必須能夠容納最大成員的數(shù)據(jù)。
- 內(nèi)存對齊:
- 編譯器為了提高數(shù)據(jù)訪問的效率,會根據(jù)平臺的對齊要求插入填充字節(jié)(padding)。內(nèi)存對齊確保數(shù)據(jù)按適當(dāng)?shù)淖止?jié)邊界存放(例如,4字節(jié)對齊、8字節(jié)對齊),從而使得CPU可以更快速地訪問這些數(shù)據(jù)。
- 在一些平臺上,數(shù)據(jù)類型可能有特定的對齊要求。
- 聯(lián)合體的大小計算:
- 聯(lián)合體的大小通常等于其最大成員的大小,但是,為了滿足內(nèi)存對齊的要求,聯(lián)合體的實際大小可能會大于最大成員的大小。它會被填充到最接近對齊要求的倍數(shù)。
- 內(nèi)存對齊填充通常是由編譯器自動管理的,但了解這一點對于理解聯(lián)合體的內(nèi)存分配非常重要。
1.2.3 進一步的例子:多成員聯(lián)合體與內(nèi)存對齊
假設(shè)我們有一個更復(fù)雜的聯(lián)合體,其中包含不同類型的數(shù)據(jù),并且考慮到內(nèi)存對齊的影響:
#include <stdio.h>union Complex {char c[21]; // 21 字節(jié)int i; // 4 字節(jié)double d; // 8 字節(jié)short s; // 2 字節(jié)
};int main() {printf("Size of union Complex: %lu\n", sizeof(union Complex));return 0;
}
分析:
char c[21]
:這是一個字符數(shù)組,它占用21字節(jié)(每個字符占1字節(jié))。沒有對齊要求。int i
:整數(shù)類型,通常占用4字節(jié)。要求 4 字節(jié)對齊,即它會被存儲在4字節(jié)對齊的位置。double d
:雙精度浮點類型,通常占用8字節(jié)。要求 8 字節(jié)對齊,因此它會被存儲在8字節(jié)對齊的位置。short s
:短整型,通常占用2字節(jié)。要求 2 字節(jié)對齊,它會被存儲在2字節(jié)對齊的位置。
聯(lián)合體的實際大小
- 聯(lián)合體的大小由最大對齊數(shù)決定。在這個例子中,最大成員是
double d
(雖然char c[21]
占用了 21 字節(jié),但是其對齊數(shù)取決于存儲的元素,即其對齊數(shù)為char
的對齊數(shù)1
),它的大小是 8 字節(jié),其對齊數(shù)為 8 。因此,聯(lián)合體的大小將是 8 字節(jié)的倍數(shù)。 - 即使
char c[21]
占用了 21 字節(jié),但聯(lián)合體的總大小會由于對齊要求被填充到適合最大成員對齊的大小。
因此,聯(lián)合體的實際大小會是 24 字節(jié)。 這是因為:
double d
會占用 8 字節(jié),并且需要 8 字節(jié)對齊,因此聯(lián)合體的總大小將被填充到最接近8字節(jié)對齊的倍數(shù),即 24 字節(jié)。
1.2.4 總結(jié)
- 聯(lián)合的大小至少是最大成員的大小。
- 當(dāng)最大成員大小不是最大對齊數(shù)的整數(shù)倍的時候,就要對齊到最大對齊數(shù)的整數(shù)倍。
1.3 聯(lián)合體的特點
-
共享內(nèi)存:聯(lián)合體的所有成員共享同一塊內(nèi)存區(qū)域,因此一個聯(lián)合變量的大小,至少是最大成員的大小。
例子:
#include <stdio.h>union Data {int i;float f;char str[20]; };int main() {union Data data;printf("&data: %p\n", &data);printf("&data.i: %p\n", &data.i);printf("&data.f: %p\n", &data.f);printf("data.str: %p\n", data.str);return 0; }
-
只能存儲一個成員的值:每次只能訪問聯(lián)合體中的一個成員。給一個成員賦值時,其他成員的值會被覆蓋。
-
節(jié)省內(nèi)存空間:聯(lián)合體在節(jié)省內(nèi)存方面非常有用,尤其是當(dāng)你需要存儲多種不同類型的數(shù)據(jù),但在任何時刻只需要其中一個類型的數(shù)據(jù)時。
例子:
#include <stdio.h> #include <string.h> union Data {int i;float f;char str[20]; };int main() {union Data data;strcpy(data.str, "abcdefgh");data.i = 0x11223344;return 0; }
![]() | ![]() |
1.4 訪問聯(lián)合體成員
聯(lián)合體成員可以通過.
(點)操作符訪問。例如:
union Data data;
data.i = 100;
printf("data.i = %d\n", data.i); // 輸出100data.f = 98.6;
printf("data.f = %.2f\n", data.f); // 輸出98.6
1.5 聯(lián)合體的使用場景
1.5.1 場景①
在網(wǎng)絡(luò)通信中,我們經(jīng)常需要處理不同類型的數(shù)據(jù)包。這些數(shù)據(jù)包的內(nèi)容可能會根據(jù)協(xié)議的不同而有所不同。例如,有些協(xié)議可能傳輸整數(shù)數(shù)據(jù),有些可能傳輸浮動數(shù)數(shù)據(jù),還有些可能傳輸字符串?dāng)?shù)據(jù)。在這種情況下,我們可以使用聯(lián)合體來處理這些不同的數(shù)據(jù)格式。
場景描述:
假設(shè)你正在開發(fā)一個通信協(xié)議處理程序,該程序需要解析網(wǎng)絡(luò)傳輸過來的數(shù)據(jù)包。每個數(shù)據(jù)包的類型和內(nèi)容可能會有所不同,但在任何時刻,每個數(shù)據(jù)包只會包含一種類型的數(shù)據(jù)。為了節(jié)省內(nèi)存,你可以使用聯(lián)合體來存儲不同類型的數(shù)據(jù)。
- 協(xié)議A 可能會傳輸一個 整數(shù)(比如用戶ID)。
- 協(xié)議B 可能會傳輸一個 浮動數(shù)(比如溫度傳感器的數(shù)據(jù))。
- 協(xié)議C 可能會傳輸一個 字符串(比如設(shè)備狀態(tài)信息)。
通過聯(lián)合體,你可以為這些數(shù)據(jù)包定義一個結(jié)構(gòu),使得它們共享同一塊內(nèi)存。每次你收到一個數(shù)據(jù)包時,根據(jù)協(xié)議類型,你可以決定是存儲整數(shù)、浮動數(shù),還是字符串,但內(nèi)存中始終只有一種數(shù)據(jù)。
優(yōu)勢:
- 節(jié)省內(nèi)存:由于不同數(shù)據(jù)類型共享內(nèi)存,只有在需要時,才會使用最大類型的內(nèi)存(例如字符串可能占用更多的字節(jié))。
- 靈活性:能夠處理不同協(xié)議的數(shù)據(jù)格式,只需要用一個聯(lián)合體存儲數(shù)據(jù)。
這個場景在 網(wǎng)絡(luò)通信、嵌入式系統(tǒng)、文件格式解析 等領(lǐng)域非常常見,特別是在需要處理多種類型數(shù)據(jù)的系統(tǒng)中。
1.5.2 場景②
姓名 | 性別 | 年齡 | 婚姻狀況 | 婚姻狀況標(biāo)記 | |||||
未婚 | 已婚 | 離婚 | |||||||
結(jié)婚日期 | 配偶姓名 | 子女?dāng)?shù)量 | 離婚日期 | 子女?dāng)?shù)量 |
struct Person // 定義職工個人信息結(jié)構(gòu)體類型
{char name[20]; // 姓名char sex; // 性別int age; // 年齡union MaritalState marital; // 婚姻狀況int marryFlag; // 婚姻狀況標(biāo)記
};union MaritalState // 定義婚姻情況共用體
{int single; // 未婚struct MarriedState married; // 已婚struct DivorceState divorce; // 離婚
};struct MarriedState // 定義已婚結(jié)構(gòu)體類型
{struct Date marryDay; // 結(jié)婚日期char spouseName[20]; // 配偶姓名int child; // 子女?dāng)?shù)量
};struct DivorceState // 定義離婚結(jié)構(gòu)體類型
{struct Date divorceDay; // 離婚日期int child; // 子女?dāng)?shù)量
};struct Date
{int year;short month;short day;
};
1.6 聯(lián)合體判斷大小端
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;//返回1是小端,返回0是大端
}
在C語言第16節(jié):數(shù)據(jù)在內(nèi)存中的存儲已經(jīng)講過,這里不再贅述。
2. 枚舉類型
C語言中的枚舉類型(enum
)是一種用戶自定義的數(shù)據(jù)類型,用于表示一組具名的常量。枚舉類型將一組相關(guān)的常量組合在一起,并賦予它們有意義的名字。使用枚舉可以使程序的代碼更加清晰、易于理解和維護。
一周的星期一到星期日是有限的7天,可以一 一列舉
性別有:男、女、保密,也可以一 一列舉
月份有12個月,也可以一 一列舉
三原色,也是可以一 一列舉
2.1 枚舉類型的基本語法
定義枚舉類型的語法如下:
enum 枚舉名 {常量1 = 值1,常量2 = 值2,常量3 = 值3,...
};
枚舉名
是枚舉類型的名稱。常量
是枚舉中的各個值,通常是一些具名常量,默認(rèn)情況下,枚舉常量從0
開始,依次遞增,除非你為它們指定了不同的值。
2.1.1 例子:基本枚舉類型
#include <stdio.h>enum Day {Sunday, // 默認(rèn)值為 0Monday, // 默認(rèn)值為 1Tuesday, // 默認(rèn)值為 2Wednesday, // 默認(rèn)值為 3Thursday, // 默認(rèn)值為 4Friday, // 默認(rèn)值為 5Saturday // 默認(rèn)值為 6
};int main() {enum Day today;today = Wednesday; // today 被賦值為 3printf("Today is day number: %d\n", today); // 輸出:Today is day number: 3return 0;
}
2.2 枚舉常量的默認(rèn)值
如果你沒有為枚舉常量指定值,默認(rèn)情況下,第一個常量的值為 0,后續(xù)常量的值依次遞增 1。例如,在上面的代碼中,Sunday
默認(rèn)值為 0,Monday
為 1,依此類推。
2.3 枚舉常量的自定義值
你可以手動為枚舉常量指定值。這意味著你可以設(shè)置任意值,而不依賴于默認(rèn)的遞增規(guī)則。
#include <stdio.h>enum Day {Sunday = 1, // 設(shè)定為 1Monday = 2, // 設(shè)定為 2Tuesday = 5, // 設(shè)定為 5Wednesday = 7, // 設(shè)定為 7Thursday, // 默認(rèn)遞增,從 8 開始Friday, // 9Saturday // 10
};int main() {enum Day today;today = Thursday; // today 被賦值為 8printf("Today is day number: %d\n", today); // 輸出:Today is day number: 8return 0;
}
在這個例子中,Sunday
和 Monday
的值分別被設(shè)置為 1 和 2,而其他常量則從 Wednesday
開始自動遞增。
2.4 枚舉類型的實際應(yīng)用
枚舉常常用于表示一些固定的狀態(tài)或選項,比如星期幾、顏色、方向、狀態(tài)碼等。它能夠使代碼更加清晰,減少硬編碼的數(shù)字。
2.4.1 例子:使用枚舉表示交通信號燈的狀態(tài)
#include <stdio.h>enum TrafficLight {Red, // 0Yellow, // 1Green // 2
};int main() {enum TrafficLight signal;signal = Green;if (signal == Green) {printf("Go!\n");} else if (signal == Yellow) {printf("Caution!\n");} else {printf("Stop!\n");}return 0;
}
在這個例子中,TrafficLight
枚舉表示了交通信號燈的三個狀態(tài):紅燈、黃燈和綠燈。每個狀態(tài)有一個默認(rèn)的整數(shù)值:Red
為 0,Yellow
為 1,Green
為 2。通過這種方式,代碼更易理解,避免了使用數(shù)字來表示信號燈的狀態(tài)。
2.5 枚舉類型的大小
在 C 語言中,枚舉類型的大小與編譯器的實現(xiàn)有關(guān)。通常,編譯器會根據(jù)枚舉常量的取值范圍來決定枚舉類型的存儲大小。如果枚舉的值在 int
類型的范圍內(nèi),編譯器通常會選擇 int
類型來存儲枚舉值。但有些編譯器可能根據(jù)需要進行優(yōu)化,使用較小的存儲類型。
可以使用 sizeof
來查看枚舉類型的大小:
#include <stdio.h>enum Color {Red = 1,Green,Blue
};int main() {printf("Size of enum Color: %lu\n", sizeof(enum Color)); // 輸出枚舉類型的大小return 0;
}
2.6 枚舉類型的轉(zhuǎn)換
枚舉常量本質(zhì)上是整數(shù),因此可以將它們轉(zhuǎn)換為整數(shù)類型,或者將整數(shù)值賦給枚舉變量。但要注意,這種做法可能會導(dǎo)致不符合預(yù)期的結(jié)果。
#include <stdio.h>enum Day {Sunday = 1,Monday = 2,Tuesday = 3
};int main() {enum Day today;today = 2; // 可以將整數(shù)賦給枚舉變量printf("Today is day number: %d\n", today); // 輸出:Today is day number: 2return 0;
}
拿整數(shù)給枚舉變量賦值在C語言中是可以的,但是在C++是不行的,C++的類型檢查比較嚴(yán)格。
2.7 枚舉的優(yōu)勢
- 提高代碼可讀性:枚舉常量有具名的標(biāo)識符,使代碼更具語義。
- 減少硬編碼數(shù)字:避免了在代碼中使用沒有含義的數(shù)字常量。
- 防止非法值:枚舉確保變量只能取定義好的值。
- 簡化調(diào)試:具名常量方便在調(diào)試時辨識。
- 增強維護性:修改枚舉值時,只需要更改枚舉定義,無需修改多個代碼位置。
- 與
switch
語句結(jié)合使用:枚舉常量使得switch
語句的條件判斷更加清晰。 - 支持位域:與位運算結(jié)合使用,可以用來表示多個標(biāo)志位。
—完—