在C++中,枚举类型是一些标识符的集合,枚举类型的变量值只能取自括号内的标识符,其声明方法如下例:
enum weekday {Sunday,Monday,Tuesday,Wedmesday,Thursday,Friday,Saturday};
事实上,这里的weekday就是一种枚举类型的数据,而它的取值只有列表中的七种。使用这样的声明方式,C++会自动给列表内容赋值,从0开始,以1自增。
枚举类型的声明方式通常有两种,上述为第一种声明方式,第二种声明方式我们可以给列表内容赋值:
enum weekday {Sunday=7,Monday=1,Tuesday=2,Wedmesday=3,Thursday=4,Friday=5,Saturday=6};
当然,如果我们使用第二种方式赋值,也可以只给一部分列表内容赋初值,没有赋值的内容会接着上一个值做自增1运算:
enum weekday {Sunday=7,Monday=1,Tuesday,Wedmesday,Thursday,Friday,Saturday};
赋值的结果是相同的。枚举类型的列表元素只能赋整型,这一点需要注意。
枚举类型的使用一样需要先定义,如:
int main()
{
weekday day1; // 申请weekday类型数据day1
day1=Tuesday; // 为day1赋值
cout<<day1;
}
// 输出为:2
但需要注意的是,如果我们想要修改枚举类型数据的内容,就不能直接用int类型数据进行赋值,也不可以使用列表内没有的值进行赋值。具体赋值方法如下:
int main()
{
weekday day1;
day1=(weekday)2;
cout<<day1;
}
// 输出为:2
当然,如果我们想要把这里的day1当做int类型的值赋值给其他int类型的变量也是可以的,但是并不建议这样使用,容易造成混乱。
枚举类型的数据也是int类型的一种,可以参与比较、四则运算。举个例子:
enum weekday {Sunday=7,Monday=1,Tuesday,Wedmesday,Thursday,Friday,Saturday};
int main()
{
weekday day1=Monday,day2;
day2=(weekday)7;
if (day2>day1) // 比较
{
cout<<day2-day1<<endl;
cout<<day2+day1<<endl; // 超过枚举类型的最大值也可以
}
}
// 输出为:6
// 8
可以看到,比较操作和四则运算都是可行的法操作都是可行的,而且超过枚举类型的列表元素最大值也是可以的。事实上,我们使用day2=(weekday) 整型数为day2赋值,这个整形数可以不在枚举类型列表应有值的范围内。所以,我们尽量用day2=Monday这样的方式进行赋值。
同python一样,C++也设计有异常处理机制。当我们需要对程序运行过程中可能遇到的错误进行修订或者当代码运行过程中遇到设计时没考虑到的问题时,就可以利用异常处理机制捕获或抛出一个问题。异常处理机制由关键字try和catch组成,其中,try后面的内容会先顺序执行,如果遇到了throw,则会立即进入catch内,一种简单的异常处理机制可以写成这样:
int main()
{
int a=-1;
try
{
if (a<0)
{
throw -1;
}
}
catch(int error)
{
if (error == -1)
{
cout<<"value error"<<endl;
}
}
}
// 输出为:value error
catch关键字要添加一个参数,用于接收throw抛出的值。我们也可以直接抛出字符串形式的错误提示:
int main()
{
int a=-1;
try
{
if (a<0)
{
throw "value error";
}
}
catch(string error)
{
cout<<error<<endl;
}
}
理论上这样可以直接打印抛出的内容,但是实际上会遇到报错:
这是因为我们这样抛出的是不可修改的固定值字符串,而string并不是C++内置的类型,这种类型定义的字符串内容可以修改,所以我们应该用chat const*(或写作const char*)类型的error来接接收,或者先将try中的"value error"转换成string类型再抛出:
int main()
{
int a=-1;
try
{
if (a<0)
{
throw "value error";
}
}
catch(const char* error)
{
cout<<error<<endl;
}
}
这样的方式虽然更适合展示错误内容,但当我们想要判断的异常不止一种时,判断展示哪个内容会比较麻烦。因此更常用的方法是用抛出不同的数字并搭配不同的展示内容:
int main()
{
int a[]={-1,0,1};
for(int i=0;i<3;i++)
{
try
{
if (a[i]<0)
{throw -1;}
else if (a[i]==0)
{throw 0;}
cout<<a[i];
}
catch(int error)
{
if (error == -1)
{cout<<"Minus Error"<<endl;}
else if (error == 0)
{cout<<"Zero Error"<<endl;}
}
}
}
// 输出为:Minus Error
// Zero Error
// 1
其实,在代码运行过程中遇到的问题也是通过异常捕获语句获取和展示的,比如前面例子中的“terminate called after throwing an instance of ‘char const*’”就是如此。当然,我们也可以通过自己的代码覆写这个错误的展示:
void test(int a)
{
try
{
if (a<0)
{throw "value error";}
}
catch(string error)
{cout<<error<<endl;}
}
int main()
{
int a=-1;
try
{test(a);}
catch(...) // 可以捕获无指定的任意异常
{cout<<"未知类型错误!"<<endl;}
}
// 输出为:未知类型错误!
这段代码首先会在主函数的try中运行test函数,而后test函数会抛出一个异常,理论上应该被C++内部的catch捕获,进而显示“terminate called after throwing an instance of ‘char const*’”,然而这个异常却被我们主函数中的catch“截胡”了,转而显示了“未知类型错误!”。这个设定还是很实用的,这意味着我们可以在主函数中的catch关键字后面使用代码修正这个错误,而不是终止程序的运行。
此外,我们还可以搭配一个类来完成错误的捕获和展示:
class self_Error_Type // 用于给出自定义错误的ID和错误的内容
{
public:
int m_ErrorID;
char m_Error[50];
self_Error_Type():m_ErrorID(-1)
{
strcpy(m_Error,"Value Error");
}
self_Error_Type(int a,char const* error):m_ErrorID(a)
{
strcpy(m_Error,error);
}
int get_error_id(){return m_ErrorID;}
char* get_error(){return m_Error;}
};
然后我们就可以使用这个类来对程序运行中可能出现的异常进行处理:
int main()
{
int a;
cin>>a;
try
{
if (a<0)
{
throw (new self_Error_Type());
}
else if (a==0)
{
throw(new self_Error_Type(0,"Zero Error"));
}
else
{
cout<<"true value"<<endl;
try // 给出一个运行时才会报错的例子
{throw "value";}
catch(string T)
{cout<<T;}
}
}
catch(self_Error_Type* error)
{
cout<<error->m_ErrorID<<":"<<error->m_Error;
}
catch(...)
{
cout<<"未知类型错误!";
throw; // 将刚捕获的内容重新抛出
}
}
当输入值不同时,可以看到不同的结果:
遇到未知类型错误时,输出未知类型错误的提示后代码会重新抛出错误,程序会被中断。另外,当我们使用类指针实现异常捕获时一定要注意函数重载,catch对异常排列时应把派生类排在基类前面。
适当使用异常捕获语句可以增强代码的健壮性,但由于C++遇到的问题大多是编译过程中就会报错,跑起来的代码遇到抛出问题的情况较少,而设法捕获我们自己的代码中没考虑到的情况又不如直接避免没考虑到的情况的出现来得实在,这就在很大程度上限制了异常捕获的使用。也正是因为这个原因,很多程序设计人员在实践中会忽略这个机制。相比之下python的异常捕获就显得实用很多了。
在C++中,宏定义是一种预处理指令,用于在编译之前替换代码中的文本。宏定义的内容会在编译时就完成替换,在预处理阶段,编译器会将源代码中的宏名称替换为宏定义的内容,也因此使用宏定义花费的时间比定义一个变量少。宏定义也不会限制变量类型,因为它只是简单的文本替换。另外,宏一旦定义,在整个程序中都可以使用,而且宏定义处理简单的运算,类似于简单的函数,我们看个例子:
# define Pi 3.14
# define square(x) (x*x)
int main()
{
cout<<square(2*Pi)<<endl;
}
// 输出为:39.4384
但是需要注意一下,这样定义square是有风险的,我们再看一个例子:
# define square(x) (x*x)
int main()
{
cout<<square(2+3)<<endl;
}
// 输出为:11
但是如果我们定义的是一个函数:
int square(int x)
{
return x*x;
}
int main()
{
cout<<square(2+3)<<endl;
}
// 输出为:25
就没有这样的问题,这是因为我们定义宏的时候,“调用”过程计算机会记录成:
# define square(2+3) (2+3*2+3)
也就是说输出的结果自然是(2+6+3)。因此我们需要更多的括号来规范运算顺序:
# define square(x) ((x)*(x))
因为宏定义不是C++语句,所以宏定义也不需要加分号。
本节我们总结了一些C++的实用的枚举类型、宏定义和异常处理,在编程中这些内容可以帮助我们提升代码的简便性和健壮性。使用C++语言开发程序一般都大量使用结构体。将一些关系紧密的数据制作成一个结构体,便于使用和开发。下节开始,我会和大家一起学习使用C++语言操作文件。