目录
一、enum关键词
枚举(enumeration)是独立的类型,是c/c++中一个基本的内置类型,它的值被限制在一个取值范围内,它可以包含数个明确命名的常量(“枚举项(enumerator)”)。各常量的值是某个整型类型(称为该枚举的底层类型(underlying type))的值。枚举的本意就是定义一个类别,并穷举同一类别下的个体约束给代码使用。由于一开始设计枚举的底层类型是整型,因此编译器会默认给枚举值赋予一个整型来区别。
enum order{zero,one,two}; //本质上是zero=int(0),one=int(1),two=int(2)
枚举语法定义:
/*枚举说明符,出现于声明语法的声明说明符序列 :定义枚举类型与其枚举项。*/
枚举关键词 属性(可选) 枚举名(可选) 枚举基(可选) { 枚举项列表(可选) }
/*不可见(opaque)枚举声明:定义枚举类型,但不定义其枚举项:在此声明后,该类型是完整类型,
*且其大小已知。注意:仅有的在 枚举名 之前出现 嵌套名说明符 的情况,是类模板的有作用域枚举成员
*的显式特化声明}}。*/
枚举关键词 属性(可选) 枚举名 枚举基(可选) ; //(C++11 起)
/*参数注释
*枚举关键字 - enum (C++11 前),enum、 enum class 或 enum struct 之一 (C++11 起)
*属性--(C++11 起) 可选的任意数量的属性序列.
*枚举名--所声明的枚举的名字。只能在非不可见的无作用域枚举声明中省略名字。如果名字存在且此声明是重声明,那么它前面可以带有嵌套名说明符,即名字和作用域解析运算符 :: 的序列并以作用域解析运算符结尾。 (C++11 起)
*枚举基--(C++11 起) 冒号 (:),后随指名某个整型类型的类型说明符序列(忽略其限定性),该类型将作为此枚举类型的固定底层类型。
*枚举项列表--枚举项定义的逗号分隔列表,每项要么是简单的标识符,它成为枚举项的名字,要么是带初始化器的标识符:标识符=常量表达式。在任一情况下,标识符可直接后随一个可选的属性说明符序列。(C++17 起)
*/
有两种截然不同的枚举:无作用域枚举(以枚举关键词enum声明)和有作用域枚举(以枚举关键词 enum class 或 enum struct 声明)。
enum Type1{a,b,c}; //error conflicts with Type2
enum Type2{a,b,c}; //error conflicts with Type1
enum class Type3{a,b,c}; //OK
enum struct Type4{a,b,c}; //Ok
无作用域枚举和有作用域枚举的主要区别就是对作用域作出了约束。
二、无作用域枚举
2.1 语法定义
无作用域枚举的语法形式:
/*
*声明一个无作用域枚举类型,其底层类型不固定(此时底层类型是由实现定义的某个能表示所有枚举项值的整型类型;
*此类型不大于int,除非枚举项的值不能放入int或unsigned int。如果枚举项列表为空,则底层类型为如同枚举拥有单个值为0的枚举项)。
*/
enum 名字(可选) { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }
enum Type1{a=1,b,c}; //b=2,c=3
enum Type2{a=5,b,c=10}; //b=6
enum {a,b,c}; //a=0,b=1,c=2
enum {a=10,b,c}; //a=10,b=11,c=12
/* 声明一个底层类型固定的无作用域枚举类型。*/
enum 名字(可选) : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... } //(C++11 起)
enum Type1:char{s1='a',s2}; //s2='b'
enum :short{s1,s2}; //s1=0,s2=1
/*无作用域枚举的不可见枚举声明必须指定名字与底层类型。*/
enum 名字 : 类型 ; //(C++11 起)
enum Type1:char; //
2.2 枚举项外围作用域可见
无作用域枚举定义时,每个枚举项都成为该枚举类型(即 名字)的一个具名常量,在其外围作用域可见,且可用于要求常量的任何位置。
enum Type1{a1=1,b1,c1}; //b1=2,c1=3
enum Type2{a2=5,b2,c2=10}; //b2=6
//无作用域枚举的名字可以忽略:这种声明仅将各枚举项引入到其外围作用域中
enum {a3,b3,c3}; //a3=0,b3=1,c3=2
enum {a4=10,b4,c4}; //a4=10,b4=11,c4=12
enum Type3:char{a5='a',b5}; //s2='b'
enum :short{a6,b6}; //a6=0,b6=1
enum Type4:char;
//
int i1 = a1; //Type1::a1
int i2 = a2; //Type2::a2
int i3 = a3; //::a3
int i4 = a4; //::a4
char c5 = a5; // Type3::a5
short s6 = a6; //::a6
2.3 枚举项初始化
上述代码还展示了无作用域枚举的一些特性:
- 每个枚举项都与一个底层类型的值相关联。
- 当在枚举项列表中提供了初始化器时,各枚举项的值由那些初始化器所定义。
- 如果首个枚举项无初始化器,那么它的关联值为零。
- 对于其他任何定义中无初始化器的枚举项,其关联值为前一枚举项加一。
值得注意的是初始化器支持常量表达式,则算术运算符等表达式也是可行的,只要能计算出常量值来。
enum Type5{ a, b, c = 10, d=1, e, f=b*e, g = f+c };
//a = 0, b = 1, c = 10, d = 1, e = 2, f = 2, g = 12
2.4 枚举值类型转换
无作用域枚举类型的值可隐式转换为整型类型。
- 如果底层类型不固定,那么该值可转换到下列首个能保有其整个值范围的类型:int、unsigned int、long、unsigned long、long long 或 unsigned long long、拥有更高转换等级的扩展整数类型(按转换等级顺序,有符号优先于无符号) (C++11 起)。
- 如果底层类型固定,那么该值可转换到其提升后的底层类型(在重载决议中优先),之后它能被提升。
enum Type3:char{a5='a',b5}; //s2='b'
long l8 = a5;// Type3::a5,char to long
整数、浮点和枚举类型的值可用 static_cast 或显式转型转换到任何枚举类型。
如果底层类型不固定且源值在范围外(源值,如果是浮点类型则首先转换到枚举的底层类型,能以足以保有目标枚举的所有枚举项的最小位域表示的情况下在范围内),
则行为未定义。否则,结果与隐式转换到底层类型的结果相同。 注意,这种转换后的值不必等于任何为该枚举所定义的具名枚举项。
enum access_type { read = 1, write = 2, exec = 4 }; //枚举项:1、2、4 范围:0..7
enum Type7{ a7 = 0, b7 = UINT_MAX }; // 范围:[0, UINT_MAX]
//
access_type rwe = static_cast<access_type>(7);
std::cout << ((rwe & read) && (rwe & write) && (rwe & exec)) << "\n";//1
access_type x = static_cast<access_type>(8.0); // 没匹配枚举值,转换为枚举的底层类型
std::cout << "x = " << x <<"\n"; //x = 8
access_type y = static_cast<access_type>(8); // 没匹配枚举值,转换为枚举的底层类型
std::cout << "y = " << y <<"\n"; //y = 8
Type7 t7 = Type7(-1); // 没匹配枚举值,即使 Type7 的底层类型是 unsigned int,转换为枚举的底层类型
std::cout << "t7 = " << t7 <<"\n"; //t7 = 4294967295
2.5 枚举值作为类成员访问
当无作用域枚举是类成员时,其枚举项可以通过类成员访问运算符 . 和 -> 访问:
struct X_enum
{
enum direction:char{ left = 'l', right = 'r' };
};
//
X_enum xe;
X_enum* pe = &xe;
int a = X_enum::direction::left; // 仅从 C++11 开始允许
int b = X_enum::left;
int c = xe.left;
int d = pe->left;
三、有作用域枚举
有作用域枚举在C++11起来引入,又称强类型枚举。
3.1 有作用域枚举-强类型枚举特点
无作用域枚举类型则为非强类型作用域,允许隐式转换为整型,占用存储空间及符号性不确定,都是枚举类的缺点。针对这些缺点,新标准C++11引入了一种新的枚举类型,即"枚举类",又称"强类型枚举"(strong-typed enum)。
声明强类型枚举非常简单,只需要在 enum后加上关键字class或struct。比如:
enum class Color{ red,blue,green }; //Ok,无命名冲突
enum class Color1{ red,blue,green }; //OK
引入强类型枚举后,c++11前的一些语法将无法生效:
enum class Color{ red,blue,green }; //Ok
//
// int x = Color::red; //C++98/03中允许,C++11中错误:不存在Color->int的转换
// Color y= 7; //C++98/03中,C++11中错误:不存在int->Color conversion的转换
// Color z = red; //C++98/03中允许,C++11中错误:red不在作用域内
Color r= Color::red;//C++98/03中错误,C++11中允许
强类型枚举具有以下几点优势,声明一个强类型的枚举Type:
- 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间。
- 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换。
- 同样可以指定底层类型。强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上":type",其中type可以是除wchar t以外的任何整型。比如:
enum class Type:char{ General,Light,Medium,Heavy };
3.2 有作用域枚举的语法
有作用域枚举的语法形式:
/* 声明底层类型为int的有作用域枚举类型(关键词class与struct完全等价)*/
enum struct|class 名字 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }
/*声明底层类型为类型的有作用域枚举类型*/
enum struct|class 名字 : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }
/*底层类型为int的有作用域枚举类型的不可见枚举声明*/
enum struct|class 名字 ;
/*底层类型为类型的有作用域枚举类型的不可见枚举声明*/
enum struct|class 名字 : 类型 ;
每个枚举项都成为该枚举的类型(即 名字)的具名常量,它为该枚举的作用域所包含,且可用作用域解析运算符访问。没有从有作用域枚举项到整数类型的隐式转换,尽管 static_cast 可用于获得枚举项的数值。
enum class Color{ red,blue,green }; //Ok
//
Color r= Color::red;//C++98/03中错误,C++11中允许
switch(r)
{
case Color::red : std::cout << "red\n"; break;
case Color::green: std::cout << "green\n"; break;
case Color::blue : std::cout << "blue\n"; break;
}
// int n = r; // 错误:不存在从有作用域枚举到 int 的隐式转换
int n = static_cast<int>(r); // OK, n = 1
3.3 强类型枚举的类型安全
强类型枚举最大的特点就是作用域约束,主要是为了提高类型安全而作出的改进,c++11以前的一些代码习惯就需要调整了:
enum class Type:char{ General,Light,Medium,Heavy };
enum class Category:int{ General = 1,Pistol,MachineGun,Cannon };
//
Type t = Type::Light;
// t = General; // 编译失败,必须使用强类型名称
// if(t == Category::General)//编译失败,必须使用Type中的General
if (t > Type::General) // 通过编译
std::cout << "> General\n";
// if(t > 0) // 编译失败,无法转换为int类型
if ((int)t > 0) // 通过编译,强制转换
std::cout << "> 0\n";
//Type和Category都是POD类型,不会像class封装版本一样被编译器视为结构体
std::cout << std::is_pod<Type>::value << std::endl;//1
std::cout << std::is_pod<Category>::value << std::endl;// 1
在c++11中,枚举成员的名字除了会自动输出到父作用域外,也可以在枚举类型定义的作用域内有效。
3.4 枚举类型定义指定底层类型
指定底层类型为较小的基本类型可以有效节省内存空间。
enum class Type:char{ General,Light,Medium,Heavy };
enum class Category:int{ General = 1,Pistol,MachineGun,Cannon };
//
std::cout << "sizeof(Type::General) = "<< sizeof(Type::General) <<"\n";
std::cout << "sizeof(Category::General) = "<< sizeof(Category::General) <<"\n";
//out log
sizeof(Type::General) = 1
sizeof(Category::General) = 4
3.5 枚举引入新整型设计
在(C++17 起),枚举在满足下列条件时都能用列表初始化从一个整数初始化而无需转型:
◦初始化是直接列表初始化
◦初始化器列表仅有单个元素
◦枚举是底层类型固定的有作用域枚举或无作用域枚举
◦转换为非窄化转换
enum btyte : unsigned char {}; // byte 是新的整数类型
struct A { btyte b; };
enum class Handle : std::uint32_t { Invalid = 0 };
void func(btyte){};
//g++ main.cpp test*.cpp -o test.exe -std=c++17
#if __cplusplus > 201103L
btyte bt { 42 }; // C++17 起 OK(直接列表初始化)
// btyte bc = { 42 }; // 错误
btyte bd = btyte{ 42 }; // C++17 起 OK;与 b 的值相同
// btyte be { -1 }; // 错误
// A a1 = { { 42 } }; // 错误(构造函数形参的复制列表初始化)
A a2 = { btyte{ 42 } }; // C++17 起 OK
// func({ 42 }); // 错误(函数形参的复制列表初始化)
Handle h { 42 }; // C++17 起 OK
#endif
这使得我们能引入新的整数类型并享受与其底层整数类型相同的既存调用约定,即使 ABI 不利于以值传递/返回结构体。
四、using enum 声明
C++20 起,using 声明也能将枚举的枚举项引入命名空间、块和类作用域。using 声明也能用于无作用域枚举项。using 声明枚举类型名时,并不传送其枚举项。
/*嵌套名说明符(可选) 名字 不得指名待决类型且必须指名一个枚举类型。*/
using enum 嵌套名说明符(可选) 名字 ; //c++20
4.1 在类、函数体采用using 引入枚举类型
using enum 声明引入其所指名的枚举的枚举项名字,如同用对每个枚举项的 using 声明。在类作用域中时,using enum 声明将其指名的枚举的枚举项名字作为成员添加到作用域,使成员查找能访问它们。
enum class Color{ red,blue,green }; //Ok
//g++ main.cpp test*.cpp -o test.exe -std=c++20
#if __cplusplus > 201703L
struct S1 {
using enum Color; // OK :引入 red,blue,green 到 S1 中
};
struct S2 {
using Color::red; // OK :引入 red 到 S2 中
};
void func1()
{
S1 s;
s.red; // OK :指名 Color::red
S1::blue; // OK :指名 Color::blue
}
void func2()
{
S2 s;
s.red; // OK :指名 Color::red
// S2::blue; // error : S2没引入 Color::blue
Color::blue; //OK,显式直接调用
}
#endif
4.2 using 引入枚举类型带来的冲突影响
需要注意的是,引入两个同名的枚举项的两个 using enum 声明会冲突。
enum class Color{ red,blue,green }; //Ok
enum class Color1{ red,blue,green }; //OK
#if __cplusplus > 201703L
//c++20起
void func3()
{
using enum Color; // OK
// using enum Color1; // 错误:Color::red 与 Color1::red 等冲突
// using Color::red; // error :重复引入 Color::red
// using Color1::red; // error:Color::red 与 Color1::red 冲突
}
#endif
enum知识点阐述结束。
感谢读友能耐心看到最后,本人文章都很长,知识点很细,可能会有所遗漏和失误,如果有不妥之处,烦请指出。如果内容对您有所触动,请点赞关注一下防止不迷路。
五、源码补充
编译指令g++ main.cpp test*.cpp -o test.exe -std=c++11,涉及更高级版本宏条件的,采用(c++17,c++20)
main.cpp
#include "test1.h"
int main(int argc, char* argv[])
{
enum_test();
return 0;
}
test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
void enum_test(void);
#endif //_TEST_1_H_
test1.cpp
#include "test1.h"
#include <iostream>
// enum Cl1{ red,blue,green }; //error conflicts with Cl2
// enum Cl2{ red,blue,green }; //error conflicts with Cl1
enum Type1{a1=1,b1,c1}; //b1=2,c1=3
enum Type2{a2=5,b2,c2=10}; //b2=6
enum {a3,b3,c3}; //a3=0,b3=1,c3=2
enum {a4=10,b4,c4}; //a4=10,b4=11,c4=12
enum Type3:char{a5='a',b5}; //s2='b'
enum :short{a6,b6}; //a6=0,b6=1
enum Type4:char;
enum Type5{ a, b, c = 10, d=1, e, f=b*e, g = f+c };//a = 0, b = 1, c = 10, d = 1, e = 2, f = 2, g = 12
enum access_type { read = 1, write = 2, exec = 4 }; //枚举项:1、2、4 范围:0..7
enum Type7{ a7 = 0, b7 = UINT_MAX }; // 范围:[0, UINT_MAX]
struct X_enum
{
enum direction:char{ left = 'l', right = 'r' };
};
enum class Color{ red,blue,green }; //Ok
enum class Color1{ red,blue,green }; //OK
enum class Type:char{ General,Light,Medium,Heavy };
enum class Category:int{ General = 1,Pistol,MachineGun,Cannon };
enum struct AType{atype,btype};
AType at = AType::atype; //OK
// enum struct {atype,btype} BType; //无法使用该枚举
#include <type_traits>
enum btyte : unsigned char {}; // byte 是新的整数类型
struct A { btyte b; };
enum class Handle : std::uint32_t { Invalid = 0 };
void func(btyte){};
#if __cplusplus > 201703L
struct S1 {
using enum Color; // OK :引入 red,blue,green 到 S1 中
};
struct S2 {
using Color::red; // OK :引入 red 到 S2 中
};
void func1()
{
S1 s;
s.red; // OK :指名 Color::red
S1::blue; // OK :指名 Color::blue
}
void func2()
{
S2 s;
s.red; // OK :指名 Color::red
// S2::blue; // error : S2没引入 Color::blue
Color::blue; //OK,显式直接调用
}
void func3()
{
using enum Color; // OK
// using enum Color1; // 错误:Color::red 与 Color1::red 等冲突
// using Color::red; // error :重复引入 Color::red
// using Color1::red; // error:Color::red 与 Color1::red 冲突
}
#endif
void enum_test(void)
{
int i1 = a1;//Type1::a1
int i2 = a2;//Type2::a2
int i3 = a3;//::a3
int i4 = a4;//::a4
char c5 = a5;// Type3::a5
short s6 = a6;//::a6
long l8 = a5;// Type3::a5,char to long
access_type rwe = static_cast<access_type>(7);
std::cout << ((rwe & read) && (rwe & write) && (rwe & exec)) << "\n";//1
access_type x = static_cast<access_type>(8.0); // 没匹配枚举值,转换为枚举的底层类型
std::cout << "x = " << x <<"\n"; //x = 8
access_type y = static_cast<access_type>(8); // 没匹配枚举值,转换为枚举的底层类型
std::cout << "y = " << y <<"\n"; //y = 8
Type7 t7 = Type7(-1); // 没匹配枚举值,即使 Type7 的底层类型是 unsigned int,转换为枚举的底层类型
std::cout << "t7 = " << t7 <<"\n"; //t7 = 4294967295
//
X_enum xe;
X_enum* pe = &xe;
int a = X_enum::direction::left; // 仅从 C++11 开始允许
int b = X_enum::left;
int c = xe.left;
int d = pe->left;
//
// int x = Color::red; //C++98/03中允许,C++11中错误:不存在Color->int的转换
// Color y= 7; //C++98/03中,C++11中错误:不存在int->Color conversion的转换
// Color z = red; //C++98/03中允许,C++11中错误:red不在作用域内
Color r= Color::red;//C++98/03中错误,C++11中允许
switch(r)
{
case Color::red : std::cout << "red\n"; break;
case Color::green: std::cout << "green\n"; break;
case Color::blue : std::cout << "blue\n"; break;
}
// int n = r; // 错误:不存在从有作用域枚举到 int 的隐式转换
int n = static_cast<int>(r); // OK, n = 1
//
Type t = Type::Light;
// t = General; // 编译失败,必须使用强类型名称
// if(t == Category::General)//编译失败,必须使用Type中的General
if (t > Type::General) // 通过编译
std::cout << "> General\n";
// if(t > 0) // 编译失败,无法转换为int类型
if ((int)t > 0) // 通过编译,强制转换
std::cout << "> 0\n";
//Type和Category都是POD类型,不会像class封装版本一样被编译器视为结构体
std::cout << std::is_pod<Type>::value << std::endl;//1
std::cout << std::is_pod<Category>::value << std::endl;// 1
//
std::cout << "sizeof(Type::General) = "<< sizeof(Type::General) <<"\n";
std::cout << "sizeof(Category::General) = "<< sizeof(Category::General) <<"\n";
//
#if __cplusplus > 201103L
btyte bt { 42 }; // C++17 起 OK(直接列表初始化)
// btyte bc = { 42 }; // 错误
btyte bd = btyte{ 42 }; // C++17 起 OK;与 b 的值相同
// btyte be { -1 }; // 错误
// A a1 = { { 42 } }; // 错误(构造函数形参的复制列表初始化)
A a2 = { btyte{ 42 } }; // C++17 起 OK
// func({ 42 }); // 错误(函数形参的复制列表初始化)
Handle h { 42 }; // C++17 起 OK
#endif
};