天天看點

枚舉enum學習小記

1、枚舉enum的用途淺例

寫程式時,我們常常需要為某個對象關聯一組可選alternative屬性.例如,學生的成績分A,B,C,D等,天氣分sunny, cloudy, rainy等等。

更常見的,打開一個檔案可能有三種狀态:input, output和append. 典型做法是,對應定義3個常數,即:

const int input = 1;

const int output = 2;

const int append = 3;

然後,調用以下函數:

bool open_file(string file_name, int open_mode);

比如,

open_file("Phenix_and_the_Crane", append);

這種做法比較簡單,但存在許多缺點,主要的一點就是無法限制傳遞給open_file函數的第2個參數的取值範圍,隻要傳遞int類型的值都是合法的。(當然,這樣的情況下的應對措施就是在open_file函數内部判斷第二個參數的取值,隻有在1,2,3範圍内才處理。)

使用枚舉能在一定程度上減輕這種尴尬(注1),它不但能實作類似于之前定義三個常量的功能,還能夠将這三個值組合起來成為獨一無二的組。例如:

enum open_modes {input = 1, output, append};

以上定義了open_modes為枚舉類型enumeration type。每一個命名了的枚舉都是唯一的類型,是一個類型标示器type specifier。例如,我們可以重新寫一個open_file函數:

bool open_file(string file_name, open_modes om);

在open_modes枚舉中,input, output, append稱為枚舉子enumerator, 它們限定了open_modes定義的對象的取值範圍。這個時候,調用open_file函數和之前的方法還是一模一樣:

open_file("Phenix_and_the_Crane", append);

但是,如果傳遞給open_file的第二個參數不是open_modes枚舉類型值的話(注1),那麼編譯器就會識别出錯誤;就算該參數取值等價于input, output, append中的某個,

也一樣會出錯哦!例如:

open_file("Phenix_and_the_Crane", 1);

2、枚舉的定義

一個枚舉是一個類型,可以儲存一組由使用者刻畫的值。定義之類,枚舉的使用很像一個整數類型。

枚舉的定義具有以下形式,即以關鍵詞enum開頭,接着一個可選的枚舉名,下來是由大括号{}包含着一個由逗号分隔的枚舉子清單enumerators list:

enum [enumeration name] {enumerator1[=value1], enumerator2[=value2], ...};

3、枚舉子的類型和取值

枚舉子的類型就是它所在的那個枚舉,例如前面說到的open_modes枚舉中,input,output和append等枚舉子的類型都是open_modes。這種做法,其實是為了賦予使用者和編譯器一些有關該變量拟議中的用途的提示。

預設下,第一個枚舉子被指派0,接下來的枚舉子取值是前面一個枚舉子的取值+1,例如:

enum weather {sunny, cloudy, rainy, windy};

其中

sunny == 0,

cloudy == 1,

rainy == 2,

windy == 3;

以上是預設情況,有時候我們希望顯式地指定某個枚舉子的值,那麼會出現什麼情況呢?看看:

enum some_fruit {apple = 3, orange, banana = 4, bear};

好了,apple == 3, banana == 4; 那麼orange和bear呢?記得前面說過一句,預設下”接下來的枚舉子取值是前面一個枚舉子的取值+1“。既然這兩個枚舉子沒有顯式指派,那麼就按照預設規則辦事,是以 orange == 4, bear == 5.

從這個例子也可以看出,同一枚舉中枚舉子的取值不需要唯一。這樣有什麼用處呢?下面是個簡單的例子:

enum some_big_cities {

Guangzhou = 4,

Shenzhen = 4,

Hongkong = 4,

Shanghai = 2,

Beijing = 3,

Chongqi = 3

};

以上簡單地按區域,将五個城市按照華南(4),華東(2), 華北(3)的幾個城市分類了。

4、枚舉變量的定義、初始化和指派

既然每個枚舉都是一個類型,那麼由這個類型自然可以聲明變量,例如,由前面定義的some_big_cities:

some_big_cities where_I_am;

需要注意的是,在聲明where_I_am時沒有初始化,如果這時列印where_I_am的值:

enum some_big_cities {

Guangzhou = 4,

Shenzhen = 4,

Hongkong = 4,

Shanghai = 2,

Beijing = 3,

Chongqi = 5};

int main(void)

{

some_big_cities wh;

cout<<"the value is: "<<wh<<endl;

return 0;

}

輸出将是the value is: 1. 然而,如果聲明wh為全局變量,則另一種情況:

enum some_big_cities {Guangzhou = 1 Shenzhen = 1, Hongkong = 1,

Shanghai = 2, Beijing = 3, Chongqi = 5};

some_big_cities wh;

int main(void)

{

cout<<"the value is: "<<wh<<endl;

return 0;

}

輸出将是the value is: 0;

以上結果是在Visual C++ 2005 Express中得到,不知道其它編譯器情況如何,也不知為什麼得到這樣的結果。下來再找找資料。

定義一個枚舉變量時,可以給它初始化,例如:

some_big_cities wh = Guangzhou;

注意等号右邊隻能取枚舉子中的某一個;特别地,以Guangzhou為例,雖然Guangzhou==4, 但以下初始化是出錯的:

some_big_cities wh = 4;

Visual C++ 2005編譯器提示:

error C2440: 'initializing' : cannot convert from 'int' to 'some_big_cities'

可見,不能直接地把一個整型指派給一個枚舉變量,因為枚舉和整型是不同類型的,除非顯式轉換。關于枚舉與整型的關系,後面再講。

除了初始化,枚舉變量也有指派運算:

some_big_cities wh;

wh = Guangzhou;

wh = Shanghai;

或者

some_big_cities wh1 = Guangzhou;

some_big_cities wh2 = Shanghai;

wh2 = wh1;

5、枚舉的取值範圍

如果某個枚舉中所有枚舉子的值均非負,該枚舉的表示範圍就是[0:2^k-1],其中2^k是能使所有枚舉子都位于此範圍内的最小的2的幂;如果存在負的枚舉值,該枚舉的取值範圍就是[-2^k,2^k-1].例如:

enum e1 {dark, light}; //範圍0:1

enum e3 {min = -10, max = 1000}; //範圍-1024:1023

6、枚舉與整型的關系

整型值隻能顯式地轉換成一個枚舉值,但是,如果轉換的結果位于該枚舉取值範圍之外,則結果是無定義的。

enum e1 {dark = 1, light = 10};

e1 VAR1 = e1(50); //無定義

e1 VAR2 = e1(3); //編譯通過

在這裡也說明了不允許隐式地從整型轉換到枚舉的原因,因為大部分整型值在特定的枚舉裡沒有對應的表示。

至于枚舉可以當作特定的整型數來用的例子,從open_modes可以體會。

7、自定義運算符

枚舉是使用者自定義類型,是以在使用者可以為它定義自身的操作,例如++或者<<等。但是,在沒有定義之前,不能因為枚舉像整型就可以預設使用,例如:

enum SomeCities

{

zhanjiang,

Maoming,

Yangjiang,

Jiangmen,

Zhongshan

};

SomeCities oneCity;

for (oneCity = zhanjiang; oneCity != Zhongshan; ++oneCity)

{

cout<<oneCity<<endl;

}

以上的++OneCity是沒有定義的,在Visual C++ 6 編譯下得到如下錯誤:

error C2675: unary '++' : 'enum main::SomeCities' does not define this operator or a conversion to a type acceptable to the predefined operator

8、Sizeof

一個枚舉類型的sizeof就是某個能夠容納其範圍的整型的sizeof, 而且不會大于sizeof(int), 除非某個枚舉子的值不能用int或者unsigned int來表示。

在32位機器中,sizeof(int)一般等于4。前面介紹的所有枚舉,例如,

enum SomeCities

{

zhanjiang,

Maoming,

Yangjiang,

Jiangmen,

Zhongshan

};

計算其sizeof, 可能是1,也可能是是4。在我的intel E2160雙核、32位機器中,得到4。

-----------------------------------------------------------------------------------

[注1, Begin]

由于通過将整型數顯式轉換就可能得到對應枚舉類型的值,是以聲明一個枚舉來達到限制傳遞給函數的參數取值範圍還是力不從心的,以下是一個例子:

enum SomeCities

{

zhanjiang=1, //1

Maoming, //2

Yangjiang, //3

Jiangmen, //4

Zhongshan = 1000 //1000

};

void printEnum(SomeCities sc)

{

cout<<sc<<endl;

}

int main(void)

{

SomeCities oneCity = SomeCities(50); //将50通過顯式轉換,為oneCity指派

printEnum(oneCity); //在VC++ 6 編譯器下得到50輸出

return 0;

}

以上例子說明,雖然SomeCities的定義裡沒有指派為50的枚舉值,但是,由于50在該枚舉的取值範圍内,是以通過顯式聲明得到一個有定義的枚舉值,進而成功傳遞給printEnum函數

4.8 枚舉

枚舉是表示具有共同屬性的整型常量集合的使用者自定義類型。這其中包含這些含義:

1. 枚舉的取值隻能是整數,正負皆可;

2. 枚舉的取值是常量,枚舉初始化後,這些值不能被改變;

3. 枚舉也是一種使用者自定義類型,使用者定義好枚舉後,可以自定義該枚舉類型自身的操作,如“++”,“<<”等;

枚舉類型的取值隐含着這樣的“潛規則”:

l 如果枚舉中所有枚舉值均非負,那麼該枚舉表示的範圍,是包含這些枚舉值的所有[0, 2k-1]區間中最小的那個;

l 如果枚舉中包含負枚舉值,那麼該枚舉表示的範圍,是包含這些枚舉值的所有[-2k, 2k-1]區間中最小的那個;

l 枚舉的sizeof,就是某個能容納其範圍的整型的sizeof,但不會大于sizeof(int);

l 如果不顯式的複制,那麼預設枚舉值将從0開始遞增;

例如:

enum Flags { A=1, B=2, C=9,D=7}; //Flags的取值範圍是[0, 15];

Flags f1 = 5; //錯誤!沒有定義從int到Flags的隐式類型轉換;

Flags f2 = Flags(14); //可以,利用顯式的類型轉換,而且14在[0,15]中;

//雖然在Flags的定義當中沒有14這個值;

Flags f3 = Flags(21); //錯誤!21不在[0, 15]當中;

第5章 指針、數組和結構

5.1 指針

5.1.1 零

“由于各種标準轉換,0可以被用于作為任意整型、浮點類型、指針、還有指向成員的指針的常量。”[1]

“0的類型将由上下文确定”[2]

為更好的保證類型安全,建議在C++中用0代替所有的NULL。如果不得不使用NULL,那麼用下面的妥協方案:

#ifndef _DEF_NULL_

#define _DEF_NULL_

const int NULL = 0;

#endif

第五章 C++/C程式設計入門

C++标準對main函數有幾個不同于一般函數的限制:

(1)不能重載;(2)不能内聯;(3)不能定義為靜态的;(4)不能取其位址;(5)不能由使用者直接調用;

int a ; //在C中為聲明,在C++為定義

在C++/C中,全局變量(extern 或 static)存放在程式的靜态資料區中,在程式進入main之前建立,在main結束之後銷毀,是以我們的代碼沒有機會初始化它們。

全局的聲明和定義應放在源檔案的開頭位置。

C++的通路控制政策是為了防止意外事件而不是為了防止對編譯器的故意欺騙。

class Base{

public:

virtual Say() {std::cout<<”Base::Say() was invoked”<<std::endl;}

};

Class Derived:public Base{

private:

virtual Say() {std::cout<<”Derived::Say() was invoked”<<std::endl;}

}

//測試

void test(void){

Base *p = new Derived;

p->Say(); //出乎意料的綁定到了一個private函數上!

類型轉換并不是改變原來的類型和值,而是生成了新的臨時變元,其類型為目标類型。

标準C允許非void 類型指針和void 類型指針互相轉化,而标準C++隻允許非void 指針轉化為void 類型指針,反過來是需要強制轉換。

強制轉換中的記憶體截斷和記憶體擴張:

double d = 1000.25;

int *pInt = (int*)&d;

int i = 100;

double *pDbl = (double*)&i;

不要用前導“_”定義自己的标志符。

基本資料類型的字面常量、枚舉常量、sizeof()、常量表達式,不需要配置設定存儲空間,編譯時放到程式的符号表(不是ROM,不能取位址)中;而字元串常量、const常量(尤其是ADT/UDT的const對象)就要配置設定運作時存儲空間。

布爾變量與零值比較:if(flag) 或if(!flag)

整型變量與零值比較:if(0==value ) 或 if(0!=value)

浮點變量與零值比較:if(abs(x)<=EPSILON) 或 if(abs(x)>=EPSILON)

指針變量與零值比較:if(NULL==p) 或 if(NULL!=p)

不要忘記switch的default ,即使不需要。

如果循環體中出現了continue語句,要防止它跳過循環變量修改語句,是以最好把循環變量的修改放在前面。

如果你的循環是确定的,用for;不确定,用while。

for中計數器一律用“前閉後開法”。

在多層嵌套的循環中,應盡可能把最長的循環放在最内層。

如果循環體記憶體在邏輯判斷,并且循環次數很大,宜将邏輯判斷移到循環體的外面。

第六章 C++/C常量

事實上隻存在基本資料類型的字面常量。

在C中,const定義的常量是不能修改的變量,是以會給它配置設定空間(外連接配接的);但C++中,對于基本類型常量放到符号表中,對于ADT/UDT的const對象則需要配置設定記憶體。還有一些,如強制聲明的extern的符号常量或取符号常量的位址,會配置設定存儲空間。

const long lng = 10;

long *pl = (long*)&lng; //去取常量位址

*pl = 1000; //“迂回修改”

cout << *pl << endl; //1000,修改拷貝

cout << lng << endl; //10,原始常量并沒有修改

對于構造類型的const 對象,它成了編譯時不允許修改的變量,但可以繞過。

Class Integer

{

Public:

Long m_lng;

};

const Integer int_1;

int_1.m_lng = 0;

Integer *pInt = (Integer*)&int_1; //去除常熟性

pInt->m_lng = 1000;

cout << pInt->m_lng << endl; //1000,修改const對象

cout << int_1.m_lng << endl; 、//1000,“迂回修改” 成功!

理論上,隻要能得到對象的位址,你就可以設法繞過編譯器随意修改它,除非有作業系統的保護。

在C中,const符号常量預設是外連接配接的,也就是說不能在兩個以上的源檔案中定義一個同名的const符号常量,或這把一個const符号常量放在頭檔案中而在多個源檔案愛您中包含該頭檔案。而C++中const預設是内連接配接的,每個編譯單元編譯時會分别為它們配置設定記憶體,在連接配接時進行常量折疊。

(1) const常量有資料類型,而宏常量沒有,是以沒有類型檢查。

(2) 有些調試工具可以對const常量進行調試。

是以C++中應盡量用const 而不用#define。

非靜态const資料成員是屬于每一個對象的成員,隻在某個對象的生存期内常量,而對于整個類是可變的,除非是static const。是以不能在類聲明中初始化非靜态const資料成員,隻能在構造函數的初始化清單中初始化。

如何建立在整個類中都恒定的類常量?

(1)用枚舉常量;(2)static const。

class A{

enume

{

SIZE1 = 100,SIZE2 = 200

};

int array1[SIZE1];

int array2[SIZE2];

};

第七章 函數設計基礎

連接配接的本質就是把一個名字的實作綁定到對它的每一個引用上。

如果函數沒有參數,那麼使用void 而不要空着。因為标準C把空參數清單解釋為可以接受任何類型和任意個數的參數,而C++解釋為不接受任何實參。

一般,輸出參數放在前面,輸入參數放在後面。

不要忽略傳回類型。C認為會傳回int。

為了避免誤解,應當将正常值和錯誤标志分開,即正常值用輸出參數獲得,而錯誤标志用return 語句傳回。如将标準C中的int getchar()改為bool GetChar(char*)就好多了。

盡管文法允許,但請不要在内層程式塊中覆寫外層程式塊的名字,否則會損害程式的可了解性。

非靜态全局函數和全局變量,名字空間的成員是外連接配接的。

在函數的入口處,建議用斷言來檢測參數的有效性。

Use const whenever you need.

第八章 函數和指針

int* a,b,c; 會了解為int *a,b,c,是以不要用這種聲明方式。

向函數傳遞多元數組,不需要說明第一維大小而必須說明其它各維大小。

任何成員函數都是獨立于類的對象而存在的,是以能夠取到一個成員函數的位址。

為了與靜态成員函數差別,取virtual 函數和普通成員函數的位址必須要使用&運算符,但取靜态成員函數位址不必要。

構造類型雖然可以嵌套定義,但其嵌套遞定義的類型對象不一定就存在包含關系,存在包含關系的對象類型也不一定是嵌套定義的。當一個類型A隻會在另一個類型B中被使用時,就可以把A定義在B 的定義體内,這樣可以減少暴露在外面的使用者自定義類型的個數。

第九章 進階資料類型

預設的拷貝指派函數就是對象的位拷貝語義,但不能直接比較大小。

位域

不要定義超過類型最大維數的位域成員。、

可以定義匿名的位域成員,其語義是占位符。

可以定義長度為0 的位域成員,其作用是迫使下一個成員從下一個完整的機器字開始配置設定空間。

不要讓一個位域成員跨位元組,這樣會增加計算開銷。

union

使用一個成員存入而用另一個成員取出是可以的,但可能不是你想要的。

在定義聯合變量時可以指定初值,但是隻能指定一個初始值,且該初始值必須與聯合的第一個成員的類型比對。你可以取聯合的位址,也可以取其成員的位址,它們都是聯合的位址。你可以在同類型聯合之間指派,但你不能比較兩個聯合變量的大小,不光是因為肯能的填補位元組,而且這兩個變量可能存儲着不同類型的成員,此時它們代表這兩個不同類型的變量。

C++對聯合進行了擴充,除了資料成員還可以定義成員的通路說明符,可以定義成員函數,甚至可以定義構造函數和析構函數;但聯合不能包含虛函數和靜态資料成員,不能作為基類。C++還支援匿名聯合。

匿名的枚舉類型就相當于直接定義的const符号常量。

第十章 C++/C 編譯預處理

(1)帶參數的宏體和各個參數都應用括号括起來。

(2)不要在引用宏定義的參數清單用++和--。

(3)帶參數的宏定義不是函數,是以沒有函數調用的開銷,但是每一處宏擴充會生成重複代碼,會使代碼體積增大。

給宏添加注釋用,不要用//,因為有些編譯器會把宏後面的行注釋了解為宏的以部分。

宏名用大寫字母并在各個單詞中間用“_”隔開。

如果要公布某個宏,那麼宏定義應放在頭檔案中,否則放在源檔案頂部。

不要使用宏來定義新類型名,應使用typedef。

# 構串操作符,##合并操作符。

第十一章 C++/C檔案結構和程式版式

ADT/UDT版式:

先public 後private。

第十二章 C++/C應用程式命名規則

類名、函數名首字母應大寫,變量名、參數名首字母應小寫。

靜态變量以s_開頭,全局變量以g_開頭,成員變量以m_開頭。

第十三章 C++/C面向對象程式設計方法概述

編寫拷貝指派函數的原則:

(1) 一定要檢查自指派,位址相等時才認為是同一個對象;

(2) 傳回本對象引用,用return *this。

如:

String& String::operator=(const String& other){

//檢查自指派

if(this != other){

//釋放原有的記憶體資源

delete[] m_data;

//配置設定新的記憶體資源,并複制内容

int len = strlen(other.m_data);

m_data = new char[len + 1];

strcpy(m_data,other.m_data);

m_size = len;

}

//傳回本對象的引用

return *this;

}

如果不想編寫,也不想讓别人調用,可将拷貝構造函數和拷貝指派函數聲明為private。

如何實作派生類的基本函數:

(1) 派生類的構造函數應在其初始化清單裡顯式地調用基類的構造函數;

(2) 如果基類是多态類,那麼必須把基類的析構函數定義為虛函數。

如:

#include <iostream>

class Base{

public:

virtual ~Base() {std::cout<<”Base::~Base()”<<std::endl;}

};

class Derived{

public:

virtual ~Derived() { delete p_test;std::cout<<”Derived::~Derived()”<<std::endl;}

private:

char *p_test;

};

int main(void){

Base *pB = new Derived;

Delete pB;

return(0);

}