枚舉
枚舉是用戶定義的類型,其中包含一組稱為枚舉器的命名的整型常數。
語法
// unscoped enum:enum [identifier] [: type]{enum-list}; // scoped enum:enum [class|struct] [identifier] [: type] {enum-list};// Forward declaration of enumerations (C++11):enum A : int; // non-scoped enum must have type specifiedenum class B; // scoped enum defaults to intenum class C : short;
參數
identifier
指定給與枚舉的類型名稱。
type
枚舉器的基礎類型;所有枚舉器都具有相同的基礎類型??赡苁侨魏握?。
enum-list
枚舉中以逗號分隔的枚舉器列表。范圍中的每個枚舉器或變量名必須是唯一的。但是,值可以重復。在未區分范圍的枚舉中,范圍是周邊范圍;在區分范圍的枚舉中,范圍是 enum-list 本身。
class
可使用聲明中的此關鍵字指定枚舉區分范圍,并且必須提供 identifier。還可使用 struct 關鍵字來代替 class,因為在此上下文中它們在語義上等效。
備注
枚舉提供上下文來描述以命名常數表示的一系列值,這些值也稱為枚舉器。在原始 C 和 C++ 枚舉類型中,非限定枚舉器在聲明枚舉的整個范圍中可見。在區分范圍的枚舉中,枚舉器名稱必須由枚舉類型名稱限定。以下示例演示兩種枚舉之間的基本差異:
namespace CardGame_Scoped{ enum class Suit { Diamonds, Hearts, Clubs, Spades }; void PlayCard(Suit suit) { if (suit == Suit::Clubs) // Enumerator must be qualified by enum type { /*...*/} }}namespace CardGame_NonScoped{ enum Suit { Diamonds, Hearts, Clubs, Spades }; void PlayCard(Suit suit) { if (suit == Clubs) // Enumerator is visible without qualification { /*...*/ } }}
將為枚舉中的每個名稱分配一個整數值,該值與其在枚舉中的順序相對應。默認情況下,為第一個值分配 0,為下一個值分配 1,以此類推,但你可以顯式設置枚舉器的值,如下所示:
enum Suit { Diamonds = 1, Hearts, Clubs, Spades };
為枚舉器 Diamonds 分配值 1。后續枚舉器接收的值會在前一個枚舉器的值的基礎上加一(如果沒有顯式賦值)。在前面的示例中,Hearts 將具有值 2,Clubs 將具有值 3,依此類推。
每個枚舉器將被視為常數,并且必須在定義 enum 的范圍內(對于未區分圍的枚舉)或在枚舉本身中(對于區分范圍的枚舉)具有唯一名稱。為這些名稱指定的值不必是唯一的。例如,如果一個未區分范圍的枚舉 Suit 的聲明如下:
enum Suit { Diamonds = 5, Hearts, Clubs = 4, Spades };
Diamonds、Hearts、Clubs 和 Spades 的值分別是 5、6、4 和 5。請注意,5 使用了多次;盡管這并不符合預期,但是允許的。對于區分范圍的枚舉來說,這些規則是相同的。
強制轉換規則
未區分范圍的枚舉常數可以隱式轉換為 int,但是 int 不可以隱式轉換為枚舉值。下面的示例顯示了如果嘗試為 hand 分配一個不是 Suit 的值可能出現的情況:
int account_num = 135692;Suit hand;hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit'
將 int 轉換為區分范圍或未區分范圍的枚舉器時,需要強制轉換。但是,你可以將區分范圍的枚舉器提升為整數值,而不進行強制轉換。
int account_num = Hearts; //OK if Hearts is in a unscoped enum
按照這種方式使用隱式轉換可能導致意外副作用。若要幫助消除與區分范圍的枚舉相關的編程錯誤,區分范圍的枚舉值必須是強類型值。區分范圍的枚舉器必須由枚舉類型名稱(標識符)限定,并且無法進行隱式轉換,如以下示例所示:
namespace ScopedEnumConversions{ enum class Suit { Diamonds, Hearts, Clubs, Spades }; void AttemptConversions() { Suit hand; hand = Clubs; // error C2065: 'Clubs' : undeclared identifier hand = Suit::Clubs; //Correct. int account_num = 135692; hand = account_num; // error C2440: '=' : cannot convert from 'int' to 'Suit' hand = static_cast<Suit>(account_num); // OK, but probably a bug!!! account_num = Suit::Hearts; // error C2440: '=' : cannot convert from 'Suit' to 'int' account_num = static_cast<int>(Suit::Hearts); // OK}
注意,hand = account_num; 行仍會導致對未區分范圍的枚舉發生的錯誤,如前面所示。它可以與顯式強制轉換一起使用。但是,借助區分范圍的枚舉,不再允許在沒有顯式強制轉換的情況下在下一條語句 account_num = Suit::Hearts; 中嘗試轉換。
聯合
union 是用戶定義的類型,其中所有成員都共享同一個內存位置。 這意味著在任何給定時間,聯合都不能包含來自其成員列表的多個對象。 這還意味著無論聯合具有多少成員,它始終僅使用足以存儲最大成員的內存。
具有大量對象和/或內存有限時,聯合可用于節省內存。 但是,需要格外小心才能正確使用它們,因為由你負責確??墒冀K訪問寫入的最后一個成員。 如果任何成員類型具有不常用構造函數,則必須編寫附加代碼來顯式構造和銷毀該成員。 使用聯合之前,應考慮是否可以使用基類和派生類來更好地表示嘗試解決的問題。
union [name] { member-list };
參數
name
為聯合提供的類型名稱。
member-list
聯合可以包含的成員。 請參閱“備注”。
備注
聲明聯合
利用 union 關鍵字開始聯合的聲明,并用大括號包含成員列表:
// declaring_a_union.cppunion RecordType // Declare a simple union type{ char ch; int i; long l; float f; double d; int *int_ptr;}; int main(){ RecordType t; t.i = 5; // t holds an int t.f = 7.25 // t now holds a float }
使用聯合
在前面的示例中,任何訪問聯合的代碼都需要了解保存數據的成員。 此問題最常見的解決方案是將聯合以及其他枚舉成員(指示當前存儲在聯合中的數據的類型)放入一個結構中。 這稱為可區分的聯合,下面的示例演示了基本模式。
#include "stdafx.h"#include <queue>using namespace std;enum class WeatherDataType{ Temperature, Wind};struct TempData{ int StationId; time_t time; double current; double max; double min;};struct WindData{ int StationId; time_t time; int speed; short direction;};struct Input{ WeatherDataType type; union { TempData temp; WindData wind; };};// Functions that are specific to data typesvoid Process_Temp(TempData t) {}void Process_Wind(WindData w) {}// Container for all the data recordsqueue<Input> inputs;void Initialize();int main(int argc, char* argv[]){ Initialize(); while (!inputs.empty()) { Input i = inputs.front(); switch (i.type) { case WeatherDataType::Temperature: Process_Temp(i.temp); break; case WeatherDataType::Wind: Process_Wind(i.wind); break; default: break; } inputs.pop(); } return 0;}void Initialize(){ Input first, second; first.type = WeatherDataType::Temperature; first.temp = { 101, 1418855664, 91.8, 108.5, 67.2 }; inputs.push(first); second.type = WeatherDataType::Wind; second.wind = { 204,1418859354, 14, 27 }; inputs.push(second);}
在前面的示例中,請注意 Input 結構中的聯合沒有名稱。 這是匿名聯合,可以訪問其成員,如同它們是結構的直接成員一樣。 有關匿名聯合的詳細信息,請參閱下面一節。
當然,上面的示例演示的問題也可以通過以下方法解決:使用派生自公共基類的類,并基于容器中每個對象的運行時類型對代碼進行分支。 這可以生成更易于維護和理解的代碼,但是也可能比使用聯合更慢。 此外,通過聯合可以存儲完全不相關的類型,并動態更改存儲的值的類型,而無需更改聯合變量本身的類型。 因此可以創建其元素存儲不同類型的不同值的 MyUnionType 異類數組。
請注意,可能會很容易誤用前面示例中的 Input 結構。 完全由用戶負責正確使用鑒別器來訪問保存數據的成員。 你可以通過使聯合成為專用并提供特殊訪問函數(如下一個示例所示)來防止誤用。
無限制的聯合 (C++11)
在 C++03 及更低版本中,聯合可以包含具有類類型的非靜態數據成員,只要該類型沒有用戶提供的構造函數、析構函數或賦值運算符即可。 在 C++11 中,消除了這些限制。 如果在聯合中包含這樣一個成員,則編譯器會自動將不是用戶提供的任何特殊成員函數標記為已刪除。 如果聯合是類或結構中的匿名聯合,則類或結構的不是用戶提供的任何特殊成員函數都會標記為已刪除。 下面的示例演示如何處理聯合的某個成員具有需要此特殊處理的成員的情況:
// for MyVariant#include <crtdbg.h>#include <new>#include <utility>// for sample objects and output#include <string>#include <vector>#include <iostream>using namespace std;struct A { A() = default; A(int i, const string& str) : num(i), name(str) {} int num; string name; //...};struct B { B() = default; B(int i, const string& str) : num(i), name(str) {} int num; string name; vector<int> vec; // ...};enum class Kind { None, A, B, Integer };#pragma warning (push)#pragma warning(disable:4624)class MyVariant{public: MyVariant() : kind_(Kind::None) { } MyVariant(Kind kind) : kind_(kind) { switch (kind_) { case Kind::None: break; case Kind::A: new (&a_) A(); break; case Kind::B: new (&b_) B(); break; case Kind::Integer: i_ = 0; break; default: _ASSERT(false); break; } } ~MyVariant() { switch (kind_) { case Kind::None: break; case Kind::A: a_.~A(); break; case Kind::B: b_.~B(); break; case Kind::Integer: break; default: _ASSERT(false); break; } kind_ = Kind::None; } MyVariant(const MyVariant& other) : kind_(other.kind_) { switch (kind_) { case Kind::None: break; case Kind::A: new (&a_) A(other.a_); break; case Kind::B: new (&b_) B(other.b_); break; case Kind::Integer: i_ = other.i_; break; default: _ASSERT(false); break; } } MyVariant(MyVariant&& other) : kind_(other.kind_) { switch (kind_) { case Kind::None: break; case Kind::A: new (&a_) A(move(other.a_)); break; case Kind::B: new (&b_) B(move(other.b_)); break; case Kind::Integer: i_ = other.i_; break; default: _ASSERT(false); break; } other.kind_ = Kind::None; } MyVariant& operator=(const MyVariant& other) { if (&other != this) { switch (other.kind_) { case Kind::None: this->~MyVariant(); break; case Kind::A: *this = other.a_; break; case Kind::B: *this = other.b_; break; case Kind::Integer: *this = other.i_; break; default: _ASSERT(false); break; } } return *this; } MyVariant& operator=(MyVariant&& other) { _ASSERT(this != &other); switch (other.kind_) { case Kind::None: this->~MyVariant(); break; case Kind::A: *this = move(other.a_); break; case Kind::B: *this = move(other.b_); break; case Kind::Integer: *this = other.i_; break; default: _ASSERT(false); break; } other.kind_ = Kind::None; return *this; } MyVariant(const A& a) : kind_(Kind::A), a_(a) { } MyVariant(A&& a) : kind_(Kind::A), a_(move(a)) { } MyVariant& operator=(const A& a) { if (kind_ != Kind::A) { this->~MyVariant(); new (this) MyVariant(a); } else { a_ = a; } return *this; } MyVariant& operator=(A&& a) { if (kind_ != Kind::A) { this->~MyVariant(); new (this) MyVariant(move(a)); } else { a_ = move(a); } return *this; } MyVariant(const B& b) : kind_(Kind::B), b_(b) { } MyVariant(B&& b) : kind_(Kind::B), b_(move(b)) { } MyVariant& operator=(const B& b) { if (kind_ != Kind::B) { this->~MyVariant(); new (this) MyVariant(b); } else { b_ = b; } return *this; } MyVariant& operator=(B&& b) { if (kind_ != Kind::B) { this->~MyVariant(); new (this) MyVariant(move(b)); } else { b_ = move(b); } return *this; } MyVariant(int i) : kind_(Kind::Integer), i_(i) { } MyVariant& operator=(int i) { if (kind_ != Kind::Integer) { this->~MyVariant(); new (this) MyVariant(i); } else { i_ = i; } return *this; } Kind GetKind() const { return kind_; } A& GetA() { _ASSERT(kind_ == Kind::A); return a_; } const A& GetA() const { _ASSERT(kind_ == Kind::A); return a_; } B& GetB() { _ASSERT(kind_ == Kind::B); return b_; } const B& GetB() const { _ASSERT(kind_ == Kind::B); return b_; } int& GetInteger() { _ASSERT(kind_ == Kind::Integer); return i_; } const int& GetInteger() const { _ASSERT(kind_ == Kind::Integer); return i_; }private: Kind kind_; union { A a_; B b_; int i_; };};#pragma warning (pop)int main(){ A a(1, "Hello from A"); B b(2, "Hello from B"); MyVariant mv_1 = a; cout << "mv_1 = a: " << mv_1.GetA().name << endl; mv_1 = b; cout << "mv_1 = b: " << mv_1.GetB().name << endl; mv_1 = A(3, "hello again from A"); cout << R"aaa(mv_1 = A(3, "hello again from A"): )aaa" << mv_1.GetA().name << endl; mv_1 = 42; cout << "mv_1 = 42: " << mv_1.GetInteger() << endl; b.vec = { 10,20,30,40,50 }; mv_1 = move(b); cout << "After move, mv_1 = b: vec.size = " << mv_1.GetB().vec.size() << endl; cout << endl << "Press a letter" << endl; char c; cin >> c;}#include <queue>#include <iostream>using namespace std;enum class WeatherDataType{ Temperature, Wind};struct TempData{ TempData() : StationId(""), time(0), current(0), maxTemp(0), minTemp(0) {} TempData(string id, time_t t, double cur, double max, double min) : StationId(id), time(t), current(cur), maxTemp(max), minTemp(0) {} string StationId; time_t time = 0; double current; double maxTemp; double minTemp;};struct WindData{ int StationId; time_t time; int speed; short direction;};struct Input{ Input() {} Input(const Input&) {} ~Input() { if (type == WeatherDataType::Temperature) { temp.StationId.~string(); } } WeatherDataType type; void SetTemp(const TempData& td) { type = WeatherDataType::Temperature; // must use placement new because of string member! new(&temp) TempData(td); } TempData GetTemp() { if (type == WeatherDataType::Temperature) return temp; else throw logic_error("Can't return TempData when Input holds a WindData"); } void SetWind(WindData wd) { // Explicitly delete struct member that has a // non-trivial constructor if (type == WeatherDataType::Temperature) { temp.StationId.~string(); } wind = wd; //placement new not required. } WindData GetWind() { if (type == WeatherDataType::Wind) { return wind; } else throw logic_error("Can't return WindData when Input holds a TempData"); }private: union { TempData temp; WindData wind; };};
聯合不能存儲引用。 聯合不支持繼承,因此聯合本身不能用作基類、繼承自另一個類或具有虛函數。
初始化聯合
可以通過指定包含在括號中的表達式來在相同語句中聲明并初始化聯合。 計算該表達式并將其分配給聯合的第一個字段。
#include <iostream>using namespace std;union NumericType{ short iValue; long lValue; double dValue; };int main(){ union NumericType Values = { 10 }; // iValue = 10 cout << Values.iValue << endl; Values.dValue = 3.1416; cout << Values.dValue) << endl;}/* Output: 10 3.141600*/
NumericType 聯合排列在內存中(概念性的),如下圖所示。
匿名聯合
匿名聯合是聲明的沒有 class-name 或 declarator-list 的聯合。
union { member-list }
匿名聯合中聲明的名稱可直接使用,就像非成員變量一樣。 因此,匿名聯合中聲明的名稱必須在周邊范圍中是唯一的。
除了聯合成員數據中列出的限制之外,匿名聯合還受其他限制:
如果在文件或命名空間范圍內聲明聯合,則還必須將它們聲明為“靜態的”。
它們可以只具有公共成員;匿名聯合中的私有成員和受保護的成員會生成錯誤。
它們不能具有函數成員。