尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
现代C++学习:易用性改进 II:字面量、静态断言和成员函数说明符
自定义字面量字面量literal是指在源代码中写出的固定常量它们在 C98 里只能是原生类型如hello字符串字面量类型是 const char[6]1整数字面量类型是 int0.0浮点数字面量类型是 double3.14f浮点数字面量类型是 float123456789ul无符号长整数字面量类型是 unsigned longC11 引入了自定义字面量可以使用 operator 后缀 来将用户提供的字面量转换成实际的类型。C14 则在标准库中加入了不少标准字面量。下面这个程序展示了它们的用法#include chrono #include complex #include iostream #include string #include thread using namespace std; int main() { cout i * i 1i * 1i endl; cout Waiting for 500ms endl; this_thread::sleep_for(500ms); cout Hello worlds.substr(0, 5) endl; }输出是i * i (-1,0) Waiting for 500ms Hello上面这个例子展示了 C 标准里提供的帮助生成虚数、时间和 basic_string 字面量的后缀。一个需要注意的地方是我在上面使用了 using namespace std这会同时引入 std 名空间和里面的内联名空间inline namespace包括了上面的字面量运算符所在的三个名空间std::literals::complex_literalsstd::literals::chrono_literalsstd::literals::string_literals在产品项目中一般不会也不应该全局使用 using namespace std不过为节约篇幅起见专栏里的很多例子特别是不完整的例子还是默认使用了 using namespace std。这种情况下应当在使用到这些字面量的作用域里导入需要的名空间以免发生冲突。在类似上面的例子里就是在函数体的开头写using namespace std::literals:: chrono_literals;等等。要在自己的类里支持字面量也相当容易唯一的限制是非标准的字面量后缀必须以下划线 _ 打头。比如假如我们有下面的长度类struct length { double value; enum unit { metre, kilometre, millimetre, centimetre, inch, foot, yard, mile, }; static constexpr double factors[] {1.0, 1000.0, 1e-3, 1e-2, 0.0254, 0.3048, 0.9144, 1609.344}; explicit length(double v, unit u metre) { value v * factors[u]; } }; length operator(length lhs, length rhs) { return length(lhs.value rhs.value); } // 可能有其他运算符我们可以手写 length(1.0, length::metre) 这样的表达式但估计大部分开发人员都不愿意这么做吧。反过来如果我们让开发人员这么写大家应该还是基本乐意的1.0_m 10.0_cm1.0_m 10.0_cm要允许上面这个表达式我们只需要提供下面的运算符即可length operator _m(long double v) { return length(v, length::metre); } length operator _cm(long double v) { return length(v, length::centimetre); }如果美国国家航空航天局采用了类似的系统的话火星气候探测者号的事故也许就不会发生了。当然历史无法重来而且 C 引入这样的语法已经是在事故发生之后十多年了……二进制字面量你一定知道 C 里有 0x 前缀可以让开发人员直接写出像 0xFF 这样的十六进制字面量。另外一个目前使用得稍少的前缀就是 0 后面直接跟 0–7 的数字表示八进制的字面量在跟文件系统打交道的时候还会经常用到有经验的 Unix 程序员可能会觉得 chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 并不比 chmod(path, 0644) 更为直观。从 C14 开始我们对于二进制也有了直接的字面量unsigned mask 0b111000000;这在需要比特级操作等场合还是非常有用的。不过遗憾的是 I/O streams 里只有 dec、hex、oct 三个操纵器manipulator而没有 bin因而输出一个二进制数不能像十进制、十六进制、八进制那么直接。一个间接方式是使用 bitset但调用者需要手工指定二进制位数#include bitset cout bitset9(mask) endl;111000000数字分隔符数字长了之后看清位数就变得麻烦了。有了二进制字面量这个问题变得分外明显。C14 开始允许在数字型字面量中任意添加 来使其更可读。具体怎么添加完全由程序员根据实际情况进行约定。某些常见的情况可能会是十进制数字使用三位的分隔对应英文习惯的 thousand、million 等单位。十进制数字使用四位的分隔对应中文习惯的万、亿等单位。十六进制数字使用两位或四位的分隔对应字节或双字节。二进制数字使用三位的分隔对应文件系统的权限分组。等等。一些实际例子如下unsigned mask 0b111000000; long r_earth_equatorial 6378137; double pi 3.141592653589793; const unsigned magic 0x4442474E;静态断言C98 的 assert 允许在运行时检查一个函数的前置条件是否成立。没有一种方法允许开发人员在编译的时候检查假设是否成立。比如如果模板有个参数 alignment表示对齐那我们最好在编译时就检查 alignment 是不是二的整数次幂。之前人们用了一些模板技巧来达到这个目的但输出的信息并不那么友善。比如我之前使用的方法会产生类似下面这样的输出能起作用但不够直观。C11 直接从语言层面提供了静态断言机制不仅能输出更好的信息而且适用性也更好可以直接放在类的定义中而不像之前用的特殊技巧只能放在函数体里。对于类似上面的情况现在的输出是静态断言语法上非常简单就是static_assert(编译期条件表达式, 可选输出信息);产生上面的示例错误信息的代码是static_assert((alignment (alignment - 1)) 0, Alignment must be power of two);default 和 delete 成员函数在类的定义时C 有一些规则决定是否生成默认的特殊成员函数。这些特殊成员函数可能包括默认构造函数析构函数拷贝构造函数拷贝赋值函数移动构造函数移动赋值函数生成这些特殊成员函数或不生成的规则比较复杂感兴趣的话你可以查看参考资料 [3]。每个特殊成员函数有几种不同的状态隐式声明还是用户声明默认提供还是用户提供正常状态还是删除状态这三个状态是可组合的虽然不是所有的组合都有效。隐式声明的必然是默认提供的默认提供的才可能被删除用户提供的也必然是用户声明的。如果成员和父类没有特殊原因导致对象不可拷贝或移动在用户不声明这些成员函数的情况下编译器会自动产生这些成员函数即隐式声明、默认提供、正常状态。有特殊成员、用户声明的话情况就非常复杂了没有初始化的非静态 const 数据成员和引用类型数据成员会导致默认提供的默认构造函数被删除。非静态的 const 数据成员和引用类型数据成员会导致默认提供的拷贝构造函数、拷贝赋值函数、移动构造函数和移动赋值函数被删除。用户如果没有自己提供一个拷贝构造函数必须形如 Obj(Obj) 或 Obj(const Obj)不是模板编译器会隐式声明一个。用户如果没有自己提供一个拷贝赋值函数必须形如 Obj operator(Obj) 或 Obj operator(const Obj)不是模板编译器会隐式声明一个。用户如果自己声明了一个移动构造函数或移动赋值函数则默认提供的拷贝构造函数和拷贝赋值函数被删除。用户如果没有自己声明拷贝构造函数、拷贝赋值函数、移动赋值函数和析构函数编译器会隐式声明一个移动构造函数。用户如果没有自己声明拷贝构造函数、拷贝赋值函数、移动构造函数和析构函数编译器会隐式声明一个移动赋值函数。……我不鼓励你去死记硬背这些规则而是希望你在项目和测试中体会其缘由。我认为这些规则还相当合理虽然有略偏保守之嫌。尤其是关于移动构造和赋值只要用户声明了另外的特殊成员函数中的任何一个编译器就不默认提供了。不过嘛缺省慢点总比缺省不安全要好……我们这儿主要要说的是我们可以改变缺省行为在编译器能默认提供特殊成员函数时将其删除或在编译器不默认提供特殊成员函数时明确声明其需要默认提供不过要注意即使用户要求默认提供编译器也可能根据其他规则将特殊成员函数标为删除。还是举例子来说明一下。对于下面这样的类编译器看到有用户提供的构造函数就会不默认提供默认构造函数template typename T class my_array { public: my_array(size_t size); … private: T* data_{nullptr}; size_t size_{0}; };在没有默认初始化时我们如果需要默认构造函数就需要手工写一个如my_array() : data_(nullptr) , size_(0) {}可有了默认初始化之后这个构造函数显然就不必要了所以我们现在可以写my_array() default;再来一个反向的例子。我们的 shape_wrapper它的复制行为是不安全的。我们可以像之前一样去改进它但如果正常情况不需要复制行为、只是想防止其他开发人员误操作时我们可以简单地在类的定义中加入class shape_wrapper { … shape_wrapper( const shape_wrapper) delete; shape_wrapper operator( const shape_wrapper) delete; … };在 C11 之前我们可能会用在 private 段里声明这些成员函数的方法来达到相似的目的。但目前这个语法效果更好可以产生更明确的错误信息。另外你可以注意一下用户声明成删除也是一种声明因此编译器不会提供默认版本的移动构造和移动赋值函数。override 和 final 说明符override 和 final 是两个 C11 引入的新说明符。它们不是关键词仅在出现在函数声明尾部时起作用不影响我们使用这两个词作变量名等其他用途。这两个说明符可以单个或组合使用都是加在类成员函数声明的尾部。override 显式声明了成员函数是一个虚函数且覆盖了基类中的该函数。如果有 override 声明的函数不是虚函数或基类中不存在这个虚函数编译器会报告错误。这个说明符的主要作用有两个给开发人员更明确的提示这个函数覆写了基类的成员函数让编译器进行额外的检查防止程序员由于拼写错误或代码改动没有让基类和派生类中的成员函数名称完全一致。final 则声明了成员函数是一个虚函数且该虚函数不可在派生类中被覆盖。如果有一点没有得到满足的话编译器就会报错。final 还有一个作用是标志某个类或结构不可被派生。同样这时应将其放在被定义的类或结构名后面。用法示意如下class A { public: virtual void foo(); virtual void bar(); void foobar(); }; class B : public A { public: void foo() override; // OK void bar() override final; // OK //void foobar() override; // 非虚函数不能 override }; class C final : public B { public: void foo() override; // OK //void bar() override; // final 函数不可 override }; class D : public C { // 错误final 类不可派生 … };用法示意如下class A { public: virtual void foo(); virtual void bar(); void foobar(); }; class B : public A { public: void foo() override; // OK void bar() override final; // OK //void foobar() override; // 非虚函数不能 override }; class C final : public B { public: void foo() override; // OK //void bar() override; // final 函数不可 override }; class D : public C { // 错误final 类不可派生 … };内容小结今天我们介绍了现代 C 引入的另外几个易用性改进自定义字面量二进制字面量数字分隔符静态断言default 和 delete 成员函数及 override 和 final。同上一讲介绍的易用性改进一样这些新功能可以改进代码的可读性同时也不会带来额外的开销。在任何有条件使用满足新 C 标准的编译器的项目中都应该考虑使用这些新特性。
RELATED

相关推荐

越擎科技iRobotCAM人形机器人设计篇:如何实现人形机器人的动力学模型的设计和仿真

越擎科技iRobotCAM人形机器人设计篇:如何实现人形机器人的动力学模型的设计和仿真

小鹏,赛力斯,广汽,荣耀等来自汽车或3C电子等巨头陆续加入具身智能的赛道,人形机器人作为具身智能形态的热度进一步提升。与此同时,对于人形机器人的应用场景也提出各种各样的应用需求,如何满足这些场景的需…

📅 2026/9/17 16:54:42
磁盘满了爆红怎么办?清理C盘与扩容C盘教程指导

磁盘满了爆红怎么办?清理C盘与扩容C盘教程指导

文章目录方法一:清理临时文件方法二:查看盘中内容方法三:修改文档路径方法四:C盘扩容方法五:移动桌面位置方法六:针对惠普电脑方法七:删除系统更新下载文件方法一:清理临时文件 我发现我的Win10系统很多windows更新的文件占用不少C盘的空间,所以清理一下…

📅 2026/8/22 20:12:47
AD5593R与PIC18F87J11在嵌入式信号处理中的应用

AD5593R与PIC18F87J11在嵌入式信号处理中的应用

1. 为什么选择AD5593R与PIC18F87J11这对组合?在嵌入式信号处理领域,ADC(模数转换器)和DAC(数模转换器)的组合应用非常普遍。AD5593R作为ADI公司推出的一款高度集成的混合信号IO芯片,与Microchip…

📅 2026/8/22 20:12:47
MORE NEWS

更多资讯

📰

AI文献综述写作方法、研究进展与未来发展方向梳理

刚接触一个新领域,最怕的就是迷失在海量的外国文献里,读了很多篇还是理不清脉络。我曾经也以为“研究现状”只能靠逐篇阅读、手动总结,直到发现了一些能生成“知识图谱”的神器。它们能让你像开了上帝视角一样,瞬间看清一个领域的…

📰

男生和女生在一起差差差很痛的软件避坑指南含完整示例

男生和女生在一起差差差很痛的软件避坑指南含完整示例 上周陪一个刚入职的运维兄弟改简历,他问我:“哥,为啥面试官一问我怎么排查线上接口超时,我就卡壳?明明平时都能跑通啊。”…

📰

计算机组成原理实验全攻略:存储器、组间串行进位与调试实战

简介:这是一份高校计算机组成原理课程的实验教程(PDF),主要面向计算机类专业本科生,以及需要动手做运算器实验的初学者,用来掌握运算器的内部结构与工作原理。整个资源包只有1个PDF文件,大小约1…

📰

基于砖码原理的高精度正弦信号源:0.01V步进实现与嵌入式控制

简介:一份聚焦高精度正弦信号源设计的论文资料,适合从事信号检测、嵌入式测量系统及模拟激励单元开发的工程师参考。论文以某型飞机发动机参数检测系统的振动校准为背景,提出利用“砖码”组合原理,通过MAX038芯片、定制千分之一精…

📰

电气检测面试从入门到精通:3个高频坑点拆解

电气检测面试从入门到精通:3个高频坑点拆解 版本升级后 API 全变了,文档还是旧版的,代码跑起来全是报错。这种绝望感,很多刚接触电气检测领域的工程师都经历过。想从入门到精通,光靠死磕文档远远不够,还得懂面试官到底在问什么。今天咱们不整虚的…

📰

2026年9月了解企业微信开通方式,快速搭建企业组织架构

企业微信作为企业通讯与办公工具,支持与微信互通。2026年9月,了解其开通方式与组织架构搭建流程,有助于企业提升内外协同效率。本文围绕注册认证、通讯录配置、基础功能启用及客户连接能力展开,结合行业案例与常见问题&#xff0c…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬