当前位置: 首页 > news >正文

C++11:系统类型增强

C++11:系统类型增强

    • 强枚举类型
      • 作用域限定
      • 隐式类型转换
      • 指定类型
      • 前置声明
    • 类型别名 using
      • 模板别名
      • 复杂指针别名
    • auto
      • 限制性 auto
      • 注意事项
    • nullptr
    • decltype


强枚举类型

在C++98的枚举设计中,存在很多缺陷,为此C++11推出了强枚举来代替旧版的枚举,提供更加安全可靠的枚举类型。

强枚举的语法如下:

enum class e : type
{val1 = 1,val2 = 2
}

此处定义了一个名为e的强枚举,只需要在enum后面加一个calss关键字即可,此处的: type用于指定底层类型,后面讲解,可以省略。


作用域限定

旧版本的 enum 会直接把枚举内部的值放到外层作用域,如果多个枚举有一样的值,或者在相同作用域定义了与枚举同名的变量,那么就会导致命名冲突:

enum e1
{a = 1
};enum e2
{a = 2
};

以上代码中,e1::ae2::a发生了冲突,全局作用域存在两个叫做a的变量。

在强枚举中,每个枚举自成一个作用域,不会污染外部变量,相互之间也不会冲突

enum class e1
{a = 1
};enum class e2
{a = 2
};

以上代码就不会报错了,因为e1e2都是强类型枚举,各自作用域独立,枚举也不会放到全局作用域。

由于枚举值不在全局作用域了,那么也就不能直接访问a了,必须通过域限定符来访问:e1::ae2::a


隐式类型转换

旧版本的 enum 可以随意的隐式转换成其他类型,这是从C语言继承下来的极其不安全的特性,它可以转化为任意整形家族,甚至charboolfloat

enum type_old
{a = 128
};int main()
{int x = a;signed char c = a;long long ll = a;float f = a;return 0;
}

以上代码是合法的(在 vs2022g++13.3编译均通过),这非常不安全,例如此处的 a = 128,它其实是超出了 signed char 的范围的,但是他不仅没有报错,而且还隐式的发生了截断,此时如果再std::cout << (int)c,你会得到-128这个值。

强枚举有非常严格的类型限定,他不能隐式转化为强枚举之外的类型。

enum class type_new
{a = 1
};int main()
{// err: 不允许隐式转化int x = type_new::a;type_new e1 = 1;// success: 显示类型转换int y = static_cast<int>(type_new::a);type_new e2 = (type_new)1;// success: C风格显示类型转换int z = (int)type_new::a;type_new e3 = static_cast<type_new>(1);return 0;
}

使用强枚举后,既不允许从其他类型隐式转为强枚举(哪怕这个值在枚举中存在),也不允许强枚举隐式转为其他类型。如果需要转换,那么必须用static_cast或者C语言风格的显式类型转换


指定类型

在旧版enum中,其类型往往是不确定的,这可能随着编译器不同而变化,一般为int。就算你传入一个long long类型,最后也会被转回int

这是因为 在C++标准中规定:编译器指定的枚举底层类型,只要可以存储所有的枚举值即可

例如你的枚举值是1 2 3,那么就有可能用short这样的来存储,如果再大一点就可能是int。不过就算C++标准这么规定,其实大部分主流编译器都固定使用int,不论你数据范围是多少,例如MSVCgcc

enum type_old
{a = INT_MAX + 1ll // 此处 1ll 表示 long long 字面量,后缀ll不可省略
};int main()
{std::cout << a << std::endl;return 0;
}

以上代码中,type_old::a这个枚举接受了一个大于int最大值的long long字面量,程序正常运行,最后输出结果为:-2147483648也就是int的最小值,这是因为发生了从long long -> int的隐式截断,你无法在 C++98 的枚举中存储大于int范围的值。相应的,当你存储的数据范围比较小,比如枚举值都是0 ~ 127,你也不能用一个字节来存,必须用int,(或者你可以祈祷某种编译器检测到了你的数据范围比较小,给你改用小范围的类型来存储)。

在强枚举中,可以指定枚举值的底层类型:

enum class type_new : long long
{a = INT_MAX + 1ll
};int main()
{std::cout << static_cast<long long>(type_new::a) << std::endl;return 0;
}

代码中: long long指定了枚举的底层使用long long存储,最后程序输出:2147483648,也就是INT_MAX + 1

要注意的是,如果你在C++11环境运行以下代码,也是合法的:

enum type_new : long long // 此处把 class 删掉了,是普通枚举
{a = INT_MAX + 1ll
};

因为C++11在更新强枚举的同时,对旧版枚举也做了优化,允许普通枚举也指定底层类型!但是普通枚举作用域,隐式转化等特性依然保留。

前置声明

旧版枚举是不允许前置声明的,例如以下代码会报错:

enum type_old;void func(type_old e)
{
}enum type_old
{a = 128
};int main()
{func(a);return 0;
}

以上代码在C++98环境运行会报错,刚才说过,枚举底层使用什么类型是不确定的,在不同编译器可能不同,这就导致func函数的第一个参数type_old声明后,无法确定其内存大小,从而编译失败

在C++11中,其实强枚举直接这么写也会报错,例如把上面的声明改成:enum class type_new这样的强枚举,还是会报错。

根本原因是无法确定枚举的底层变量,从而无法得知大小。因为C++标准明确说了编译器可以自己来指定底层变量,在枚举值还没有定义之前,编译器根本就无法推断用什么类型来存,也就不知道这个枚举的底层类型。

此时上一个特性就派上用场了,用户可以自己显式指定枚举底层类型,那编译器不就明确了枚举的大小了么

而刚才又说过,C++11对普通枚举和强枚举都支持指定底层类型,那么代码就可以这样改写:

enum type_old : int;
enum type_new : int;void func(type_old e1, type_new e2)
{
}enum type_old : int
{a = 128
};enum type_new : int
{a = 128
};

现在不论强枚举还是普通枚举,都可以提前声明了,因为通过: int明确指定了底层使用int存储,那么func的两个参数就知道自己要给参数预留多少空间,此时前置声明就有用了。


类型别名 using

在 C++11 之前,typedef 是定义类型别名的唯一方式,但它存在语法局限,尤其是在模板编程中不够灵活。

在C++11之前,using主要用于展开命名空间,或者声明其它命名空间内部的变量。

C++11 给 using 添加了类型别名的功能,不仅替代了 typedef,还提供了更强大的功能,特别是在模板别名和复杂类型表达式简化方面。

语法如下:

using new_name = old_name;

模板别名

如果想给一系列模板类取别名,例如希望简化list的迭代器类型std::list<T>::iterator变成list_it<T>,使用typedef是无法做到的,而using就可以配合模板使用:

template<typename T>
using list_it = std::list<T>::iterator;int main()
{list_it<int> p; // successreturn 0;
}

这是typedef无法做到的,也是using最大的优势。

复杂指针别名

在使用typedef给函数指针或者数组指针取别名的时候,语法会很复杂,而且可读性很差,例如:

// 把 void(*)(int, int) 类型的函数指针取别名为 func_ptr
typedef void(*func_ptr)(int, int);// 把 int(*)[] 类型的数组指针取别名为 arr_ptr
typedef int(*arr_ptr)[];

这是因为在typedef中,要求新名称必须写在*后面,这样编译器才知道这个新的名称是一个指针,这就导致可读性很差,例如一个带有回调函数的函数指针取别名:

typedef void(*func_ptr)(void(*)(void), int);

此处func_ptr的类型是void(*)(void(*)(void), int),如果C语言基础差一些,这段代码要琢磨一点时间。

using中,无需把新名称写到*后面,就是固定写在=左边,右边就是原始类型,例如:

// 把 void(*)(int, int) 类型的函数指针取别名为 func_ptr
using func_ptr = void(*)(int, int);// 把 int(*)[] 类型的数组指针取别名为 arr_ptr
typedef arr_ptr = int(*)[];

这样语义就明确很多了,程序员一下就看出来新名称是什么,原始类型是什么。


auto

在C++中,auto关键字可以用来自动推断变量的类型,它在编译时会根据初始化表达式的类型来确定变量的类型。

使用auto的主要好处是可以简化代码并提高可读性。它可以减少手动指定变量类型的工作,并且可以防止类型错误。相比于显式指定变量类型,使用auto可以让代码更加灵活和易于维护。

  1. 自动推断基本类型变量的类型
auto age = 25; // 推断age为int类型
auto salary = 5000.50; // 推断salary为double类型

auto也可以自动推断指针的类型,比如这样:

int x = 10;
auto y = &x;

此时y的类型自动判别为int*

实际上不建议这么做,C++中最好还是明确每个变量的类型,对于这种简单的类型还是不要用auto的好。

  1. 自动推断非常长的类型
std::vector<int> numbers = {1, 2, 3, 4, 5};for (auto it = numbers.begin(); it != numbers.end(); ++it) 
{std::cout << *it << " ";
}

有的时候获得变量的类型会需要很长的代码,使用auto可以缩短变量类型的长度,这是C++推荐的做法,当然用户心里还是要清楚auto最后接收到了什么类型,只是懒得写出来而已。

  1. 接受不确定的类型
auto add = [](int a, int b){ return a + b; };

lambda表达式中,返回值类型是不确定的,必须用auto接收。这是因为lambda设计出来,就是只用一次就不再用的匿名函数,那么用户就无需知道这个表达式的类型,因为拿到类型就可以再去定义相同的函数了,那就不要用lambda,直接写一个函数/仿函数就行了。因此C++中lambda的类型是随机生成的,必须用auto才能接收。


限制性 auto

除去基本的类型推断,auto可以限制接收到的类型必须是指针或引用。

看到一段代码:

int x = 10;auto* a1 = x;
auto* a2 = &x;
auto a3 = &x;

auto* a1 = x;中,x的类型是int,那么auto本应将其值判别为int,但是由于auto**限制了,此时auto必须得到一个指针,所以编译器会报错;而auto* a2 = &x;得到的就是指针,此时代码不会报错,可以正常识别为int*

在本质上auto* a2 = &x;auto a3 = &x;的结果是没有区别的,只是auto*要求得到的必须是一个指针类型,而auto不限制其类型。

同理auto&也可以限定类型必须是一个引用,否则会报错。


注意事项

  1. auto不能作为函数的参数
  2. auto不能用于声明数组

比如以下代码:

int arr1[] = {1, 3, 5, 7, 9};
auto arr2[] = {1, 3, 5, 7, 9};

此时第二条代码就会报错,因为其用auto类型定义了一个数组。

  1. 在同一行定义多个变量时,如果将auto作为其类型,必须一整行都是同一个类型的变量。

比如以下代码:

int x = 1, y = 2;
auto a = 3, b = 4;
auto c = 5, d = 6.0;

以上代码中,auto a = 3, b = 4;是合法的,因为一行内都是int类型。

但是auto c = 5, d = 6.0;是非法的,因为同一行内有不同类型,会报错。


nullptr

在C++11后,推出了新的空指针nullptr,明明已经有NULL了,为啥还需要nullptr?

NULL在C语言中,表示的是((void*)0),也就是被强制转为void*类型的0。但是在C++中,NULL就是整数0

比如可以用刚才学的typeid验证一下:

cout << typeid(NULL).name() << endl;

输出结果为:int,这下就石锤了NULL在C++中就是int

这会导致不少问题,比如这样:

void func(int x)
{cout << "参数为整型" << endl;
}void func(void* x)
{cout << "参数为指针" << endl;
}int main()
{func(NULL);return 0;
}

以上代码中,func函数有两个重载,一个是参数为指针,一个是参数为整型。我现在就是想传一个空指针去调用指针版本的func。但是最后还是会调用int类型的。

nullptr不一样,nullptr不仅不是整型,而且其也不是void*。C++给了nullptr一个专属类型nullptr_t。这个类型有一个非常非常大的优势,该类型只能转化为其它指针类型,不能转化为指针以外的类型

比如以下代码:

int x1 = NULL;//正确
int x2 = nullptr;//错误

因为NULL本质是0,其可以转化为很多非指针类型,比如intdoublechar。但是nullptrnullptr_t,它只能转化为其他指针。上述代码中,我们把nullptr转化为一个int,此时编译器会直接报错,绝对禁止这个行为。

但是这样是可以的:

void* p1 = nullptr;
int* p2 = nullptr;
char* p3 = nullptr;
double* p4 = nullptr;

可以看到,nullptr保证了指针类型的稳定,空指针不会被传递到指针以外的类型。因此nullptr在各方面都有足够的优势,以更加安全的形式给用户提供空指针。


decltype

在C++11以前,有一个关键字typeid,其可以识别一个类型,并且可以通过name成员函数来输出类型名。

比如这样:

int i = 0;
int* pi = &i;cout << typeid(i).name() << endl;
cout << typeid(pi).name() << endl;

输出结果为:

int
int * __ptr64

也就是说,我们可以通过typeid来检测甚至输出变量类型。

decltype也是用于识别类型的,但是decltypetypeid应用方向不同。

decltype可以检测一个变量的类型,并且拿这个类型去声明新的类型

比如这样:

int i = 0;
decltype(i) x = 5;

decltype(i)检测出i的类型为int,于是decltype(i)整体就变成int,从而定义出一个新的变量x

autodecltype 的区别在于,decltype声明变量可以无需初始化。

int a;
decltype(a) b; // success
auto c = a;    // success
auto d;        // error

decltype还可以作为模板参数

例如把lambda传给std::priority_queue作为比较条件:

auto comp = [](const std::string& a, const std::string& b) {return a.size() >= b.size();};std::priority_queue<std::string, std::deque<std::string>, decltype(comp)> q(comp);

相关文章:

  • Redis keydb dragonfly skytable
  • uni-app开发特殊社交APP
  • 人工智能在智慧物流中的创新应用与未来趋势
  • Flask集成pyotp生成动态口令
  • 时序数据库IoTDB如何快速高效地存储时序数据
  • 深兰科技陈海波率队考察南京,加速AI医诊大模型区域落地应用
  • Android 缓存应用冻结器(Cached Apps Freezer)
  • 深兰科技董事长陈海波率队考察南京,加速AI大模型区域落地应用
  • 宁夏农业科技:创新引领,赋能现代农业新篇章
  • C++23:std::print和std::println格式化输出新体验
  • AWS 创建VPC 并且添加权限控制
  • PR2020+MS1824+MS7210+MS2130 1080P@60Hz USB3.0采集
  • 数据结构之堆(topk问题、堆排序)
  • Gartner《2025 年软件工程规划指南》报告学习心得
  • 【C++指南】C++ list容器完全解读(二):list模拟实现,底层架构揭秘
  • rsync使用守护进程启动服务
  • 主流 AI IDE 之一的 Windsurf 介绍
  • 尚硅谷redis7 63-69 redis哨兵监控之理论简介
  • 查看webpack版本的三种方式
  • LiveGBS国标视频平台收流模式:UDP、TCP被动与TCP主动传输模式之差异剖析
  • 打开部分网站很慢/google永久免费的服务器
  • 域名价格查询/长沙seo霜天
  • 东莞网站建设营销的企业/青岛关键词排名提升
  • 推广链接跳转/百度seo优化分析
  • wordpress企业网站源码/武汉seo网站优化技巧
  • 做php网站用什么软件/网络营销推广方法有哪些