c/c++ development, unavoidable custom class type (Part 6). Special class enum

Table of contents

1. enum keywords

2. Unscoped enumeration

         2.1 Grammar definition

        2.2 The outer scope of the enumeration item is visible

        2.3 Enumeration Item Initialization

        2.4 Enumeration value type conversion

        2.5 Enumeration values ​​are accessed as class members

Three, scoped enumeration

        3.1 Scoped enumeration - characteristics of strongly typed enumeration

        3.2 Syntax for scoped enums

        3.3 Type Safety of Strongly Typed Enums

        3.4 Enumerated type definition specifies the underlying type

        3.5 Enumeration introduces new integer design

Four, using enum statement

        4.1 Use using to introduce enumeration types in classes and function bodies

        4.2 The impact of conflicts caused by the introduction of enumeration types by using

5. Source code supplement


1. enum keywords

        Enumeration (enumeration) is an independent type, a basic built-in type in c/c++, its value is limited to a range of values, it can contain several clearly named constants ("enumeration item (enumerator) "). The value of each constant is the value of some integral type called the underlying type of the enumeration. The original intention of enumeration is to define a category and exhaustively enumerate individual constraints under the same category for code use. Since the underlying type of the enumeration is originally designed to be an integer, the compiler will assign an integer to the enumeration value by default to distinguish it.

enum order{zero,one,two};    //本质上是zero=int(0),one=int(1),two=int(2)

        Enum syntax definition:
 

/*枚举说明符,出现于声明语法的声明说明符序列 :定义枚举类型与其枚举项。*/
枚举关键词 属性(可选) 枚举名(可选) 枚举基(可选) { 枚举项列表(可选) } 

/*不可见(opaque)枚举声明:定义枚举类型,但不定义其枚举项:在此声明后,该类型是完整类型,
*且其大小已知。注意:仅有的在 枚举名 之前出现 嵌套名说明符 的情况,是类模板的有作用域枚举成员
*的显式特化声明}}。*/
枚举关键词 属性(可选) 枚举名 枚举基(可选) ; //(C++11 起)

/*参数注释
*枚举关键字 - enum  (C++11 前),enum、 enum class 或 enum struct 之一 (C++11 起)
*属性--(C++11 起) 可选的任意数量的属性序列.
*枚举名--所声明的枚举的名字。只能在非不可见的无作用域枚举声明中省略名字。如果名字存在且此声明是重声明,那么它前面可以带有嵌套名说明符,即名字和作用域解析运算符 :: 的序列并以作用域解析运算符结尾。 (C++11 起)
*枚举基--(C++11 起) 冒号 (:),后随指名某个整型类型的类型说明符序列(忽略其限定性),该类型将作为此枚举类型的固定底层类型。
*枚举项列表--枚举项定义的逗号分隔列表,每项要么是简单的标识符,它成为枚举项的名字,要么是带初始化器的标识符:标识符=常量表达式。在任一情况下,标识符可直接后随一个可选的属性说明符序列。(C++17 起)
*/

        There are two distinct kinds of enumerations: unscoped enumerations (declared with the enum keyword enum) and scoped enums (declared with the enum keywords enum class or 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

        The main difference between an unscoped enum and a scoped enum is the constraint on scope.

2. Unscoped enumeration

         2.1 Grammar definition

        The syntax for an unscoped enumeration is:

/*
*声明一个无作用域枚举类型,其底层类型不固定(此时底层类型是由实现定义的某个能表示所有枚举项值的整型类型;
*此类型不大于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 The outer scope of the enumeration item is visible

        When an unscoped enum is defined, each enum item becomes a named constant of the enum type (namely), visible in its enclosing scope, and available anywhere a constant is required.

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 Enumeration Item Initialization

        The above code also demonstrates some properties of unscoped enums:

  • Each enumeration item is associated with a value of the underlying type.
  • When initializers are provided in the enumerator-list, the values ​​of the individual enumerators are defined by those initializers.
  • If the first enumerator has no initializer, then its associated value is zero.
  • For any other enumerator defined without an initializer, the associated value is the previous enumerator plus one.

        It is worth noting that the initializer supports constant expressions, and expressions such as arithmetic operators are also feasible, as long as the constant value can be calculated.

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 Enumeration value type conversion

        Values ​​of unscoped enumeration types are implicitly convertible to integral types.

  •  If the underlying type is not fixed, the value is convertible to the first of the following types that retains its entire range of values: int, unsigned int, long, unsigned long, long long, or unsigned long long, an extended integral type with a higher conversion rank (Signed takes precedence over unsigned in conversion rank order) (since C++11).
  • If the underlying type is fixed, the value is convertible to its promoted underlying type (which takes precedence in overload resolution), after which it can be promoted.
enum Type3:char{a5='a',b5};   //s2='b'
long l8 = a5;// Type3::a5,char to long

        Values ​​of integer, floating point, and enumeration types can be converted to any enumeration type with static_cast or explicit casts.

        If the underlying type is not fixed and the source value is out of range (the source value, if it is a floating-point type, is first converted to the underlying type of the enumeration, and can be represented by the smallest bit field sufficient to hold all the enumeration items of the target enumeration within the scope),

        then the behavior is undefined. Otherwise, the result is the same as that of an implicit conversion to the underlying type. Note that this converted value need not be equal to any of the named enumerators defined for this enum.

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 Enumeration values ​​are accessed as class members

        When an unscoped enum is a class member, its enumerators can be accessed via the class member access operators . and ->:

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;

Three, scoped enumeration

        Scoped enumerations were introduced in C++11, also known as strongly typed enumerations.

        3.1 Scoped enumeration - characteristics of strongly typed enumeration

        Unscoped enumeration types are non-strongly typed scopes, allowing implicit conversion to integer types, occupying storage space, and uncertain symbolicity, all of which are disadvantages of enumeration classes. In response to these shortcomings, the new standard C++11 introduces a new enumeration type, namely "enumeration class", also known as "strong-typed enumeration" (strong-typed enum).

        Declaring a strongly typed enumeration is very simple, just add the keyword class or struct after enum. for example:

enum class Color{ red,blue,green };  //Ok,无命名冲突
enum class Color1{ red,blue,green }; //OK

        After the introduction of strongly typed enumeration, some grammars before c++11 will not take effect:

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中允许

        Strongly typed enumerations have the following advantages, declaring a strongly typed enumeration Type:

  • Strongly scoped, the names of strongly typed enumeration members are not exported to their parent scope space.
  • Conversion restrictions, values ​​of strongly typed enumeration members cannot be implicitly converted to and from integral types.
  • The underlying type can also be specified. The default underlying type of a strongly typed enumeration is int, but the underlying type can also be specified explicitly by adding ":type" after the enumeration name, where type can be any integer except wchar t. for example:
enum class Type:char{ General,Light,Medium,Heavy };

        3.2 Syntax for scoped enums

        The syntax for a scoped enumeration is:

/* 声明底层类型为int的有作用域枚举类型(关键词class与struct完全等价)*/
enum struct|class 名字 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... } 

/*声明底层类型为类型的有作用域枚举类型*/
enum struct|class 名字 : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... } 

/*底层类型为int的有作用域枚举类型的不可见枚举声明*/
enum struct|class 名字 ; 

/*底层类型为类型的有作用域枚举类型的不可见枚举声明*/
enum struct|class 名字 : 类型 ; 

        Each enumeration item becomes a named constant of the enumeration's type (i.e., name), which is contained by the enumeration's scope and accessible with the scope resolution operators. There is no implicit conversion from a scoped enumerator to an integral type, although static_cast can be used to obtain the numeric value of an enumerator.

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 Type Safety of Strongly Typed Enums

        The biggest feature of strongly typed enumeration is scope constraints, which are mainly improvements made to improve type safety. Some code habits before c++11 need to be adjusted:

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

        In C++11, the names of enumeration members are not only automatically output to the parent scope, but also valid within the scope defined by the enumeration type.

        3.4 Enumerated type definition specifies the underlying type

       Specifying the underlying type as a smaller primitive type can effectively save memory space.

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 Enumeration introduces new integer design

        In (since C++17), enums can be initialized with list initialization from an integer without casting if the following conditions are met: ◦ the
initialization is direct list initialization
◦ the initializer list has only a single element
◦ the enumeration is underlying type invariant A scoped or unscoped enumeration of
◦ conversions to non-narrowing conversions

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

        This allows us to introduce new integer types and enjoy the same existing calling conventions as their underlying integer types, even if the ABI does not favor passing/returning structs by value.

Four, using enum statement

        Since C++20, a using-declaration can also bring an enum's enumerators into namespace, block, and class scope. Using declarations can also be used on unscoped enums. When using declares an enumeration type name, its enumerators are not transmitted.

/*嵌套名说明符(可选) 名字 不得指名待决类型且必须指名一个枚举类型。*/
using enum 嵌套名说明符(可选) 名字 ;   //c++20

        4.1 Use using to introduce enumeration types in classes and function bodies

        A using enum declaration imports the enumeration item names of the enumeration it names, as if with a using-declaration for each enumeration item. When at class scope, a using enum declaration adds the enumeration item names of the enum it named to the scope as members, making them accessible to member lookup.

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 The impact of conflicts caused by the introduction of enumeration types by using

        Note that two using enum declarations that introduce two enumeration items with the same name will conflict.

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

        The elaboration of enum knowledge points is over.

        Thank you readers for patiently reading to the end. My articles are very long and the knowledge points are very detailed. There may be omissions and mistakes. If there are any inadequacies, please point them out. If the content touches you, please like and follow it to avoid getting lost.

5. Source code supplement

        Compilation instructions g++ main.cpp test*.cpp -o test.exe -std=c++11, for more advanced version macro conditions, use (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
};

Guess you like

Origin blog.csdn.net/py8105/article/details/129676168