枚举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);

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值