本文實例講述了C++聯合體union用法。分享給大家供大家參考。具體如下:
我們應該按照C中的convention去使用union,這是我這篇文章要給出的觀點。雖然C++使得我們可以擴展一些新的東西進去,但是,我建議你不要那樣去做,看完這篇文章之後,我想你大概也是這麼想的。
C由於沒有類的概念,所有類型其實都可以看作是基本類型的組合,因此在union中包含struct也就是一件很自然的事情了,到了C++之後,既然普遍認為C++中的struct與class基本等價,那麼union中是否可以有類成員呢?先來看看如下的代碼:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct TestUnion { TestUnion() {} }; typedef union { TestUnion obj; } UT; int main (void) { return 0; }編譯該程序,我們將被告知:
error C2620: union '__unnamed' : member 'obj' has user-defined constructor or non-trivial default constructor
而如果去掉那個什麼也沒干的構造函數,則一切OK。
為什麼編譯器不允許我們的union成員有構造函數呢?我無法找到關於這個問題的比較權威的解釋,對這個問題,我的解釋是:
如果C++標准允許我們的union有構造函數,那麼,在進行空間分配的時候要不要執行這個構造函數呢?如果答案是yes,那麼如果TestUnion 的構造函數中包含了一些內存分配操作,或者其它對整個application狀態的修改,那麼,如果我今後要用到obj的話,事情可能還比較合理,但是如果我根本就不使用obj這個成員呢?由於obj的引入造成的對系統狀態的修改顯然是不合理的;反之,如果答案是no,那麼一旦我們今後選中了obj來進行 操作,則所有信息都沒有初始化(如果是普通的struct,沒什麼問題,但是,如果有虛函數呢?)。更進一步,假設現在我們的union不是只有一個 TestUnion obj,還有一個TestUnion2 obj2,二者均有構造函數,並且都在構造函數中執行了一些內存分配的工作(甚至干了很多其它事情),那麼,如果先構造obj,後構造obj2,則執行的 結果幾乎可以肯定會造成內存的洩漏。
鑒於以上諸多麻煩(可能還有更多麻煩),在構造union時,編譯器只負責分配空間,而不負責去執行附加的初始化工作,為了簡化工作,只要我們提供了構造函數,就會收到上面的error。
同理,除了不能加構造函數,析構函數/拷貝構造函數/賦值運算符也是不可以加。
此外,如果我們的類中包含了任何virtual函數,編譯時,我們將收到如下的錯誤信息:
error C2621: union '__unnamed' : member 'obj' has copy constructor
所以,打消在union中包含有構造函數/析構函數/拷貝構造函數/賦值運算符/虛函數的類成員變量的念頭,老老實實用你的C風格struct吧!
不過,定義普通的成員函數是OK的,因為這不會使得class與C風格的struct有任何本質區別,你完全可以將這樣的class理解為一個C風格的struct + n個全局函數。
現在,再看看在類中包含內部union時會有什麼不同。看看下面的程序,並請注意閱讀程序提示:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class TestUnion { union DataUnion { DataUnion(const char*); DataUnion(long); const char* ch_; long l_; } data_; public: TestUnion(const char* ch); TestUnion(long l); }; TestUnion::TestUnion(const char* ch) : data_(ch) // if you want to use initialzing list to initiate a nested-union member, the union must not be anonymous and must have a constructor。 {} TestUnion::TestUnion(long l) : data_(l) {} TestUnion::DataUnion::DataUnion(const char* ch) : ch_(ch) {} TestUnion::DataUnion::DataUnion(long l) : l_(l) {} int main (void) { return 0; }正如上面程序所示,C++中的union也可以包含構造函數,但是,這雖然被語言所支持,但實在是一種不佳的編程習慣,因此, 我不打算對上面的程序進行過多的說明。我更推薦如下的編程風格:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class TestUnion { union DataUnion { const char* ch_; long l_; } data_; public: TestUnion(const char* ch); TestUnion(long l); }; TestUnion::TestUnion(const char* ch) { data_.ch_ = ch; } TestUnion::TestUnion(long l) { data_.l_ = l; } int main (void) { return 0; }它完全是C風格的。
所以,接受這個結論吧:
請按照C中的convention去使用union,盡量不要嘗試使用任何C++附加特性。
union是個好東西,union是個struct,裡面所有成員共享一塊內存,大小由size最大的member決定,存取成員的時候會以成員的類型來解析這塊內存;在gamedev中,union可以在這些方面有所作為:
1. 換名:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct Rename { public: union { struct { float x,y,z,w; }; struct { float vec[4]; }; }; };這樣我們既可以根據具體的含義來訪問變量,也可以象數組一樣的loop;
2 .壓縮:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Compression { public: bool operator==(const Compression& arg) const { return value == arg.value; } union { struct { char a,b,c,d,e,f,g; }; struct { long long value; }; }; };這樣對於集中處理的情況,比如==,就會大幅度提高效率,象在64位機上,只要一次,或者傳輸數據的情況,壓縮解壓縮都非常方便;
3. 危險:
匿名的union用法,不是standard,所以在compiler上要確認==>編譯器移植性不好;
不同的機器操作系統上數據的size都是不一樣,表示不一樣,那麼在用union的時候,尤其是在移植的時候,都是危險的情況;
但是如果系統,compiler都是一樣的話,在合適的地方使用union還是可以的。
聯合(union)在C/C++裡面見得並不多,但是在一些對內存要求特別嚴格的地方,聯合又是頻繁出現,那麼究竟什麼是聯合、怎麼去用、有什麼需要注意的地方呢?就這些問題,我試著做一些簡單的回答,裡面肯定還有不當的地方,歡迎指出!
1、什麼是聯合?
“聯合”是一種特殊的類,也是一種構造類型的數據結構。在一個“聯合”內可以定義多種不同的數據類型, 一個被說明為該“聯合”類型的變量中,允許裝入該“聯合”所定義的任何一種數據,這些數據共享同一段內存,已達到節省空間的目的(還有一個節省空間的類型:位域)。 這是一個非常特殊的地方,也是聯合的特征。另外,同struct一樣,聯合默認訪問權限也是公有的,並且,也具有成員函數。
2、聯合與結構的區別?
“聯合”與“結構”有一些相似之處。但兩者有本質上的不同。在結構中各成員有各自的內存空間, 一個結構變量的總長度是各成員長度之和(空結構除外,同時不考慮邊界調整)。而在“聯合”中,各成員共享一段內存空間, 一個聯合變量的長度等於各成員中最長的長度。應該說明的是, 這裡所謂的共享不是指把多個成員同時裝入一個聯合變量內, 而是指該聯合變量可被賦予任一成員值,但每次只能賦一種值, 賦入新值則沖去舊值。
下面舉一個例了來加對深聯合的理解。
例4:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> void main() { union number { /*定義一個聯合*/ int i; struct { /*在聯合中定義一個結構*/ char first; char second; }half; }num; num.i=0x4241; /*聯合成員賦值*/ printf("%c%cn", num.half.first, num.half.second); num.half.first='a'; /*聯合中結構成員賦值*/ num.half.second='b'; printf("%xn", num.i); getchar(); }輸出結果為:
AB
6261
從上例結果可以看出: 當給i賦值後, 其低八位也就是first和second的值; 當給first和second賦字符後, 這兩個字符的ASCII碼也將作為i 的低八位和高八位。
3、如何定義?
例如:
?
1 2 3 4 5 6 union test { test() { } int office; char teacher[5]; };定義了一個名為test的聯合類型,它含有兩個成員,一個為整型,成員名office;另一個為字符數組,數組名為teacher。聯合定義之後,即可進行聯合變量說明,被說明為test類型的變量,可以存放整型量office或存放字符數組teacher。
4、如何說明?
聯合變量的說明有三種形式:先定義再說明、定義同時說明和直接說明。
以test類型為例,說明如下:
1)
?
1 2 3 4 5 6 union test { int office; char teacher[5]; }; union test a,b; /*說明a,b為test類型*/2)
?
1 2 3 4 5 union test { int office; char teacher[5]; } a,b;3)
?
1 2 3 4 5 union { int office; char teacher[5]; } a,b;經說明後的a,b變量均為test類型。a,b變量的長度應等於test的成員中最長的長度,即等於teacher數組的長度,共5個字節。a,b變量如賦予整型值時,只使用了4個字節,而賦予字符數組時,可用5個字節。
5、如何使用?
對聯合變量的賦值,使用都只能是對變量的成員進行。聯合變量的成員表示為:
聯合變量名.成員名
例如,a被說明為test類型的變量之後,可使用a.class、a.office
不允許只用聯合變量名作賦值或其它操作,也不允許對聯合變量作初始化賦值,賦值只能在程序中進行。
還要再強調說明的是,一個聯合變量,每次只能賦予一個成員值。換句話說,一個聯合變量的值就是聯合變員的某一個成員值。
6、匿名聯合
匿名聯合僅僅通知編譯器它的成員變量共同享一個地址,而變量本身是直接引用的,不使用通常的點號運算符語法.
例如:
?
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> void main() { union{ int test; char c; }; test=5; c='a'; std::cout<<i<<" "<<c; }正如所見到的,聯合成分象聲明的普通局部變量那樣被引用,事實上對於程序而言,這也正是使用這些變量的方式.另外,盡管被定義在一個聯合聲明中,他們與同一個程序快那的任何其他局部變量具有相同的作用域級別.這意味這匿名聯合內的成員的名稱不能與同一個作用域內的其他一直標志符沖突.
對匿名聯合還存在如下限制:
因為匿名聯合不使用點運算符,所以包含在匿名聯合內的元素必須是數據,不允許有成員函數,也不能包含私有或受保護的成員。還有,全局匿名聯合必須是靜態(static)的,否則就必須放在匿名名字空間中。
7、幾點需要討論的地方:
1)聯合裡面那些東西不能存放?
我們知道,聯合裡面的東西共享內存,所以靜態、引用都不能用,因為他們不可能共享內存。
2)類可以放入聯合嗎?
我們先看一個例子:
?
1 2 3 4 5 6 7 8 9 10 11 class Test { public: Test():data(0) { } private: int data; }; typedef union _test { Test test; }UI;編譯通不過,為什麼呢?
因為聯合裡不允許存放帶有構造函數、析夠函數、復制拷貝操作符等的類,因為他們共享內存,編譯器無法保證這些對象不被破壞,也無法保證離開時調用析夠函數。
3)又是匿名惹的禍??
我們先看下一段代碼:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class test { public: test(const char* p); test(int in); const operator char*() const {return data.ch;} operator long() const {return data.l;} private: enum type {Int, String }; union { const char* ch; int i; }datatype; type stype; test(test&); test& operator=(const test&); }; test::test(const char *p):stype (String),datatype.ch(p) { } test::test(int in):stype(Int),datatype.l(i) { }看出什麼問題了嗎?呵呵,編譯通不過。為什麼呢?難道datatype.ch(p)和datatype.l(i)有問題嗎?
哈哈,問題在哪呢?讓我們來看看構造test對象時發生了什麼,當創建test對象時,自然要調用其相應的構造函數,在構造函數中當然要調用其成員的構造函數,所以其要去調用datatype成員的構造函數,但是他沒有構造函數可調用,所以出
錯。
注意了,這裡可並不是匿名聯合!因為它後面緊跟了個data!
4)如何有效的防止訪問出錯?
使用聯合可以節省內存空間,但是也有一定的風險:通過一個不適當的數據成員獲取當前對象的值!例如上面的ch、i交錯訪問。
為了防止這樣的錯誤,我們必須定義一個額外的對象,來跟蹤當前被存儲在聯合中的值得類型,我們稱這個額外的對象為:union的判別式。
一個比較好的經驗是,在處理作為類成員的union對象時,為所有union數據類型提供一組訪問函數。
希望本文所述對大家的C++程序設計有所幫助。