目錄
C++ 中的列舉型別
在 C++ 中,有列舉這個型別,其實就是將數個參考值或名稱集合在一起。這種集合的方法,並不是陣列,也不屬於容器。通常我們之它為列舉:
enum Suit { club, diamond, heart, spade };
enum Month { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } month;
enum What { abc = 1, def = 10, ghi = -3, jkl = 2, mno = 10 };
typedef enum Bool { false, true } bool;
for (month = JAN; month <= DEC; month++) { // month++ 不一定都能順利被編譯
printf("%d ", month);
}
在 enum 中第一個名稱若沒有設定初始值時,其預設值為 0,下一位為 1,依此類推。所以 Suit
中,club = 0, diamond = 2, heart = 3, spade = 4
。同時上例中的 false = 0, true = 1
,因為在 C++ 中早期並沒有布林型別,所以通常我們都會自己使用列舉來模擬。另外從列舉型別 What
中也可看出,列舉的值可正可負,甚至可以重覆,其數值容許型別為 signed int
。
列舉主要是拿來當常數使用,把相同性質的名稱集合在一起(有時會將名稱給定數值,像上例的 Month
),用同一種型別處理各種不同的情況,就像是 Month
型別,有十二種可能的數值。當然,因為是常數,所以你沒辦法再更動每一個名稱的值,像是 JAN = 1
,就無法在程式中再修改它的值(除非你直接修改它的初始值定義)。除了 enum
之外,常數也可用 #define
和 const
設定,不過這就不在我們討論範圍之內了。
Java 的列舉型別
Java 中新增的列舉型別和 C++ 的概念類似,不過在用法上有很大的不同。首先 C++ 中給定初始值是使用 JAN = 1
,但是在 Java 中則需要一些複雜的設定。另外 C++ 對列舉的處理比較偏向於看成基本型別 signed int
的常數集合,而 Java 中則是將 enum
看成類別:
public class EnumeratedTypes1 {
public enum Month { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
public static void main(String[] args) {
for (Month month : Month.values()) {
System.out.print(month + "=");
}
System.out.println();
Month month = Month.JAN;
System.out.println(month);
System.out.println(month.getClass());
}
}
輸出結果:
JAN=FEB=MAR=APR=MAY=JUN=JUL=AUG=SEP=OCT=NOV=DEC= JAN class EnumeratedTypes1$Month
由上例中的輸出結果可看出,Month
是一個類別,姑且可稱之為列舉類別。會有這種結果,其實是可以理解的。因為列舉本身就是一種型別,在 C++ 中對其有特殊的設計,並且在 ++
的處理上,有些編譯器就無法通過,因為這些編譯器把列舉看成類別,因為對 ++
的部份我們要自己重載運算子(但有趣的是 =
和 <=
這些運算子不用重載)。而 Java 語言中則完完全全地是個類別,從 getClass()
中即可看出來。
更複雜的列舉定義
底下示範了一個比較複雜的例子,由這個例子中可以看出,列舉其實只是一種比較特殊的類別,所以我們也才一直稱呼 enum
為列舉「型別」。它可以擁有自訂建構式、欄位以及方法,使得我們在應用上更方便,當然,C++ 中也可以做到類似的事。
public class EnumeratedTypes2 {
enum Suit { club(1), diamond(2), heart(3), spade(4);
private int value; // 自訂欄位
Suit(int value) { // 因為設定 club(1), ..,所以必須要撰寫此一建構式
this.value = value;
}
public int getValue() { // 自訂取值方法
return value;
}
public void setValue(int value) { // 自訂設值方法
this.value = value;
}
};
public static void main(String[] args) {
for (Suit suit : Suit.values()) {
System.out.println(suit + "=" + suit.getValue());
}
Suit suit = Suit.diamond;
System.out.println(suit.getValue());
suit.setValue(10);
System.out.println(suit.getValue());
}
}
輸出結果:
club=1 diamond=2 heart=3 spade=4 2 10
之前提到列舉型別可看成常數,但是在這裡我們自訂方法 setValue
後,會發現竟然可以重新指定數值。這不就和常數相衝突了?其實不然,在 C++ 中因為直接把列舉子(club
, JAN
等名稱叫做列舉子)對應到整數,所以我們也就不能再修改這些數值。但在 Java 中,列舉的字串值其實是它本身的名稱,也就是我們使用 System.out.println(Suit.club)
會在螢幕上顯示 club
字串,這才是列舉型別真正的常數值,而上例中的 value
只是我們自行增加的欄位,可以自由設定,這和 enum
本身數值的常數定義並無衝突。不過我建議為了省去不必要的麻煩,最好還是宣告為 private final int value;
會比較好些,以免不小心修改到它而使得程式中出現莫名的 bug。
命名空間的問題
在 C++ 中如果列舉中有相同的名稱,那麼可能會造成一些命名重複的問題:
enum fruit {
orange=0,
apple=1
};
enum color {
orange=1, // multiply defined
green=2
};
enum color = orange; // 到底是那個 orange?
// enum color = color::orange; // 無法使用此種方法指定
不過在 Java 中,因為完完全全以類別來處理,所以我們在設定時必須要加上類別名稱,就像是在存取類別中靜態常數值一樣,從而避免了上述命名衝突的情況:
public class EnumeratedTypes3 {
enum Color { RED, GREEN, BLUE, ORANGE };
enum Fruit { BANANA, ORANGE, PEACH };
public static void main(String[] args) {
//Color color = ORANGE; // 無法通過編譯
Color color = Color.ORANGE; // 必須指定類別才能通過編譯
System.out.println(color);
Fruit fruit = Fruit.ORANGE;
System.out.println(fruit);
}
}