在數(shù)據(jù)結(jié)構(gòu) -- 結(jié)構(gòu)體Struct一文中詳細(xì)介紹了結(jié)構(gòu)體的定義以及內(nèi)存對齊。在C
語言中梅割,還有另外一種和結(jié)構(gòu)體非常類似的語法,叫做共用體(Union
)凡桥,也稱為聯(lián)合體。它的定義格式為:
union 共用體名{
成員列表
};
1. 定義共用體變量
和結(jié)構(gòu)體一樣蚀同,共用體也是一種自定義的數(shù)據(jù)類型缅刽,是創(chuàng)建變量的模板,不占用內(nèi)存空間唤崭。共用體變量才包含了實(shí)實(shí)在在的數(shù)據(jù)拷恨,需要內(nèi)存空間來存儲。共用體可以通過下面兩種方式來定義:
- 方式一:先定義共用體谢肾,再定義共用體變量
//定義data共用體
union data{
int n;
char ch;
double f;
};
//定義兩個(gè)共用體變量
union data a, b;
data
為共用體名,里面包含n小泉、ch芦疏、f這3個(gè)成員冕杠。a
、b
則為兩個(gè)data類型的共用體變量酸茴。
- 方式二:在定義共用體的同時(shí)定義共用體變量
union data{
int n;
char ch;
double f;
} a, b;
直接將變量放在共用體的最后即可分预。
如果只需要 a、b兩個(gè)變量薪捍,后面不需要再使用共用體名定義其他變量笼痹,那么在定義時(shí)也可以省略共用體名。
2. 成員的獲取和賦值
共用體使用了內(nèi)存覆蓋機(jī)制酪穿,同一時(shí)刻只能保存一個(gè)成員的值凳干,如果對新的成員賦值,就會把原來成員的值覆蓋掉被济。所以共用體不能整體賦值救赐,只能使用點(diǎn)號.獲取單個(gè)成員,然后再進(jìn)行賦值操作只磷。
//注:下面每一次賦值后经磅,n、ch钮追、f三個(gè)變量的值均會改變
a.n = 40;
a.ch = '9';
a.f = 135;
3. 共用體的內(nèi)存分配
共用體變量占用的內(nèi)存大小等于最長的成員占用的內(nèi)存大小预厌,各成員都會從offset為0處開始存放,修改其中一個(gè)成員會影響其余所有成員元媚。來看下面的實(shí)例:
//定義一個(gè)data共用體
union data{
int n;
char ch;
short m;
};
int main(){
//創(chuàng)建一個(gè)共用體變量a
union data a;
printf("a.size = %d, data.size = %d\n", sizeof(a), sizeof(union data) );
//對成員a賦值
a.n = 0x40;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//對成員b賦值
a.ch = '9';
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//對成員c賦值
a.m = 0x2059;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
//再對成員a賦值
a.n = 0x3E25AD54;
printf("n = %#X, ch = %c, m = %#hX\n", a.n, a.ch, a.m);
return 0;
}
在上面代碼中創(chuàng)建了一個(gè)共用體變量a轧叽,里面有n、ch惠毁、m這三種不同數(shù)據(jù)類型的成員犹芹,分別對齊賦值,打印每一次賦值后的三個(gè)成員鞠绰。
%X
:表示將int類型數(shù)據(jù)以大寫字母的形式輸出十六位進(jìn)制數(shù)腰埂。
%c
:表示輸出字符。
%hX
:表示將short類型數(shù)據(jù)以大寫字母的形式輸出十六位進(jìn)制數(shù)蜈膨。
#
:表示輸出時(shí)加上前綴屿笼,用于區(qū)分不同進(jìn)制的數(shù)字。比如:0x16翁巍,不加#時(shí)輸出為16驴一,加上輸出為0x16。
打印結(jié)果為:
a.size = 4, data.size = 4
n = 0X40, ch = @, m = 0X40
n = 0X39, ch = 9, m = 0X39
n = 0X2059, ch = Y, m = 0X2059
n = 0X3e25ad54, ch = T, m = 0XAD54
從打印結(jié)果可知灶壶,共用體a的內(nèi)存等于其最長成員int n
所占的內(nèi)存大小肝断,為4字節(jié)。每次修改共用體中的某個(gè)成員,都會影響到其他成員的值胸懈。
4. 分析共用體的內(nèi)存分配
共用體的每個(gè)成員都從其內(nèi)存offset為0的地方存放担扑,根據(jù)成員數(shù)據(jù)類型各占用對應(yīng)字節(jié)大小的內(nèi)存,每次為成員賦值時(shí)趣钱,都會覆蓋修改所占內(nèi)存涌献,因此,其他成員的值也跟著變動(dòng)了首有。以上面的共存體變量a為例燕垃,分別對其成員賦值,內(nèi)存空間的變化情況就如下圖所示:
圖1:表示共用體a及其成員的內(nèi)存分配情況:
- 每一格代表一個(gè)字節(jié)井联,從上到下表示地址由低到高分布卜壕。
- a總共4字節(jié),成員n低矮、m印叁、ch均從offset為0的地方開始存放,分別占用4字節(jié)军掂、2字節(jié)轮蜕、1字節(jié)的內(nèi)存大小,內(nèi)存存在重合部分蝗锥。
圖2:表示當(dāng)賦值a.n = 0x40
后跃洛,共用體a的內(nèi)存空間變化:
1byte = 8bit,所以1個(gè)字節(jié)最大值為255终议,換算成十六進(jìn)制為0xFF汇竭,而0x40小于0xFF,所以只需一個(gè)字節(jié)保存穴张。存儲系統(tǒng)的分布方式大多采用小端模式细燎,即在內(nèi)存的第一個(gè)字節(jié)里保存0x40,此時(shí)成員n皂甘、ch玻驻、m的值均為0x40。
存儲系統(tǒng)分布方式偿枕,以0x12345678為例:
1.大端模式:高位在低地址璧瞬,低位在高地址。即12在低地址渐夸,78在高地址嗤锉。
2.小端模式:高位在高地址,低位在低地址墓塌。即12在高地址瘟忱,78在低地址奥额。
因此,當(dāng)賦值a.n = 0x40
后酷誓,打印結(jié)果為:n = 0X40, ch = @, m = 0X40披坏。(0x40對應(yīng)的字符為@
)
圖3:表示當(dāng)賦值a.ch = '9'
后态坦,共用體a的內(nèi)存空間變化:
字符
9
的十六進(jìn)制ASCII碼值為0x39盐数,在賦值后,內(nèi)存里第一個(gè)字節(jié)的0x40被覆蓋伞梯,變?yōu)?x39玫氢。
因此當(dāng)賦值a.ch = '9'
后,打印結(jié)果為:n = 0X39, ch = 9, m = 0X39谜诫。
圖4:表示當(dāng)賦值a.m = 0x2059
后漾峡,共用體a的內(nèi)存空間變化:
由于0x2059小于0xFFFF,所以需要兩個(gè)字節(jié)來保存喻旷,在賦值后生逸,內(nèi)存里第一個(gè)字節(jié)里的39被覆蓋,前兩個(gè)字節(jié)分別保存0x59且预、0x20槽袄。
因此當(dāng)賦值a.m = 0x2059
后,ch為0x59锋谐,對應(yīng)字符Y
遍尺,所以打印結(jié)果為:n = 0X2059, ch = Y, m = 0X2059。
圖5:表示當(dāng)賦值a.n = 0x3E25AD54
后涮拗,共用體a的內(nèi)存空間變化:
由于0x3E25AD54小于0xFFFFFFFF乾戏,所以需要4字節(jié)來保存。在賦值后三热,內(nèi)存的4個(gè)字節(jié)分別保存54鼓择、AD、25就漾、3E呐能。
因此當(dāng)賦值a.n = 0x3E25AD54
后,ch為0x54从藤,對應(yīng)字符T
催跪,m為0xAD54,所以打印結(jié)果為:n = 0X3E25AD54, ch = T, m = 0XAD54夷野。
5. 共用體和結(jié)構(gòu)體的區(qū)別
結(jié)構(gòu)體的第一個(gè)成員會從offset為0的地方開始存放懊蒸,其它成員按順序存儲,各成員會占用不同的內(nèi)存悯搔,互相之間沒有影響骑丸;
共用體的所有成員都會從offset為0的地方存放,各成員內(nèi)存會存在重,修改一個(gè)成員會影響其余所有成員通危。結(jié)構(gòu)體占用的內(nèi)存大于等于所有成員占用的內(nèi)存的總和(成員之間可能會存在縫隙)铸豁。
共用體占用的內(nèi)存等于最長的成員占用的內(nèi)存。共用體使用了內(nèi)存覆蓋技術(shù)菊碟,同一時(shí)刻只能保存一個(gè)成員的值节芥,如果對新的成員賦值,就會把原來成員的值覆蓋掉逆害。
推薦閱讀
1. 數(shù)據(jù)結(jié)構(gòu) -- 結(jié)構(gòu)體Struct
2. 數(shù)據(jù)結(jié)構(gòu) -- 位域