第6课 类型别名和强枚举类型

时间:2023-01-10 16:59:09

一. typedef和using关键字

(一)两者的差异

  ①C++11引入using关键字,覆盖了typedef的全部功能。它既可以用来定义类型的别名,也可以定义模板的别名。而typedef可以定义类型的别名,但不能用来重定义模板的别名。

  ②使用using不用写“::type”的后缀。在模板内,对于内嵌typedef的引用经常要加上typename前缀。

  ③using采用类似于赋值的方式,从语法比typedef更加清晰。

(二)using在模板中的优势

  ①using可以直接为模板取别名(alias template),但typedef需外加一个包装类才能达到类似的目的。

  ②在模板类部使用依赖型类型时(如MyAlloc<T>::type),需要在类型前面用typename修饰,以表明它是一个类型,而不是数据成员。但使用using定义的模板别名则不会出现这个问题。

  ③许多C++11中的“类型萃取”是在包装类模板里用typedef来实现的(eg.std::xxx<T>::type)。而C++14是都加上了对应的别名模板(如std::xxx_t<T>)。

【编程实验】typedef和using的比较

#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;

//自定义内存分配器
template<typename T>
class MyAlloc {};  //用于演示,没有实际内容

//演示类
class Widget{};
template<typename T, typename Alloc = MyAlloc<T>>
class List
{
public:
    List(){}
    List(std::initializer_list<T> list){}
};

//2.1: 为模板取别名
template<typename T>
using MyList1 = List<T, MyAlloc<T>>;   //使用using方式,合法!

//template<typename T>
//typedef std::list<T, MyAlloc<T>> type;   //无法通过这种方式为模板取别名,必须加上一个包装类(如下)

template<typename T>
struct MyList2    //外包装类
{
    typedef List<T, MyAlloc<T>> type; //使用typedef方式
};

//2.2 依赖型类型(如list1)
template<typename T>
class Foo
{
public:

    //嵌套依赖类型名问题
    typename MyList2<T>::type list1;   //注意:MyList1<T>::type的类型,依赖模板形参T。在模板内定义时,必须加typename
                                       //因为,当编译器看到MyList1<T>::type时,它不能确定这是一个类型,还是一个数据成员变量?如果是成员变量,
                                       //则用其去声明一个list1变量,显然是错的。

    MyList2<Widget>::type list2;       //由于MyList1<Widget>::type是个确定的类型,不依赖模板形参T。因此其前面可以不加typename,但加了也不影响

    MyList1<T> list3;                  //MyList2<T>是用using声明的,因它是一个别名模板,必然也就是一个类型,所以不会出现二义/性。其前面无须加typename
};

//3. 类型萃取(C++11和C++14中的等价物)
template<class T>
using remove_const_t = typename remove_const<T>::type;  //const T --> T

template<class T>
using remove_reference_t = typename remove_reference<T>::type; //T&/T&& --> T

template<class T>
using add_lvalue_reference_t = typename add_lvalue_reference<T>::type; //T --> T&

int main()
{
    //1. typedef 和 using的等效定义
    typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPtrMapSS1;  //typedef 
    using UPtrMapSS2 = std::unique_ptr<std::unordered_map<std::string, std::string>>;   //赋值方式,更直观

    typedef std::vector<std::string> strVec1;
    using strVec2 = std::vector<std::string>;

    typedef void(*FP1)(int, const std::string&);  
    using FP2 = void(*)(int, const std::string&); //使用using声明的函数指针

    //2. using在模板中的优势
    //2.1为模板取别名
    MyList1<Widget> lw1;        //使用using方式,直接使用模板别名!
    MyList2<Widget>::type lw2;  //使用typedef方式,需加"::type"后缀

    //2.2 嵌套依赖类型名问题(出现在模板类内部)
    Foo<Widget> foo;
    foo.list1 = { Widget(), Widget(), Widget() };
    foo.list2 = { Widget(), Widget(), Widget() };
    foo.list3 = { Widget(), Widget(), Widget() };

    return 0;
}

二、强枚举类型

(一)enum 和 enum class的差异

  1. enum:被称为不限范围的枚举类型枚举量的名字会泄漏到枚举类型所在的作用域,这意味着在此作用域内不能有其他实体取相同的名字。enum class属于限定作用域的枚举类型,也被称为“枚举类”枚举量仅在枚举类型内可见,它不会带来名称空间的污染。

  2. 不限范围的枚举类型可以隐式转换到整数,“枚举类”不能隐式转化为其他任何类型。

 (二)枚举量的底层存储类型

1. enum和enum class 都支持指定底层类型。不限定范围的枚举类型没有默认的底层类型,而限制作用域的枚举类型的默认底层类型为int

2. 限定作用域的枚举类型总是可以进行前置声明,而不限范围的枚举却只有在指定了默认底层类型的前提下才可以进行前置声明。

【编程实验】enum和enum class的区别

#include <iostream>
#include <vector>
#include <tuple>
using namespace std;

//测试函数:求x的质因数(用于演示,没真正的实现)
std::vector<std::size_t> primeFactors(std::size_t x) { return{}; }

//辅助函数,用于将enum class中的枚举量转为其底层类型输出
template<typename E>
constexpr auto toUType(E enumerator) noexcept  //注意这里使用编译期常量,同时不抛出异常
{
    return static_cast<std::underlying_type_t<E>>(enumerator);    //underlying_type用于萃取底层类型
}

int main()
{
    //1. enum 和 enum class的区别
    //1.1 名称空间的污染
    enum Color1 {black, white, red};      //三个枚举量的作用域与Color1相同
    //auto white = false;   //错误! white己在Color1中声明了。

    enum class Color2 {black, white, red}; //black、white、read的作用域仅限定在Color2内。

    Color1 c1 = white;          //Color1中的white,名称污染到整个main作用域
    Color2 c2 = Color2::white;  //ok
    auto   c3 = Color2::white;  //ok

    //1.2 强类型阻止隐式类型转换
    //不限范围的枚举类型
    if (c1 < 14.5) {   //ok,不限范围的枚举类型可以将c1隐式转换为double
        auto factors = primeFactors(c1); //计算c1的质因数
    }

    //限定范围的枚举类型
    //if (c2 < 14.5) {   //error,限定范围的枚举类型无法隐式转为double,但可用static_cast<double>(c2)进行强制转换
    //    auto factors = primeFactors(c2); //计算c2的质因数,需static_cast<double>(c2)强制转换
    //}

    //2. 枚举类型的底层类型
    //2.1 enum类型(为了节约内存,编译器通常会为枚举类型选用足够表示枚举量取值的最小底层类型。但某些情况下,编译器
    //会以空间换时间,这种情况下可能不会选择最小底层类型)
    enum Color3 {blue, yellow, green}; //底层类型为char
    enum Status{good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0xFFFFFFFF}; //int

    //2.2 enum class类型(底层默认为int类型)
    enum class State{ good = 0, failed = 1, incomplete = 100, corrupt = 200, audited = 500, indeterminate = 0x00FFFFFF};

    //2.3 为枚举类型指定底层类型
    enum Color4 : std::uint8_t; //不限范围的枚举类型,注意由于指定了底层类型(uint8_t),所以可以进行前置声明
    enum class Color5 : std::uint32_t {good = 0, failed = 1, indeterminate = 0xFFFFFFFF};

    //3. 不限范围枚举类型在tuple中使用的优势
    //3.1 对比enum和enum class在tuple的使用的优劣
    using UserInfo = std::tuple<std::string, std::string, std::size_t>; //客户信息:3个成员分别为姓名、电子邮件和声望值
    UserInfo uInfo = std::make_tuple("SantaClaus", "abc@126.com", 80);

    enum UserInfoFields1{uiName, uiEmail, uiReputation};       //定义enum类型的枚举类型
    enum class UserInfoFields2{uiName, uiEmail, uiReputation}; //定义enum class类型的枚举类型

    auto name = std::get<0>(uInfo);        //取出姓名,但用索引值0表示,不直观!
    auto email = std::get<uiEmail>(uInfo); //取出uEmail,用不限范围的枚举类型,含义非常直观 
    auto rep = std::get<static_cast<std::size_t>(UserInfoFields2::uiReputation)>(uInfo); //取出声望值,用enum class则需强转,不好用!
    cout << "name = "<< name << ",email = "<< email <<",Reputation = " << rep << endl;

    //3.2 改进enum class的使用,但与enum相比仍较繁琐(注意,因为std::get<Index>中的Index必须为编译期常量,因此toUType函数必须
    //是constexpr函数)
    auto name2 = std::get<toUType(UserInfoFields2::uiName)>(uInfo);      //姓名
    auto email2 = std::get<toUType(UserInfoFields2::uiEmail)>(uInfo);    //email
    auto rep2 = std::get<toUType(UserInfoFields2::uiReputation)>(uInfo); //期望值
    cout << "name = " << name2 << ",email = " << email2 << ",Reputation = " << rep2 << endl;

    return 0;
}
/*输出结果
name = SantaClaus,email = abc@126.com,Reputation = 80
name = SantaClaus,email = abc@126.com,Reputation = 80
*/