1. 程式人生 > >列舉enum學習小記

列舉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);

}