Linux基金会免费官方培训及考试申请即将截止,戳这里申请!>>>

原文:C++ 惯用法: const 常量和字面量
作者:Breaker <breaker.zy_AT_gmail>


C++ 中 const 关键字、常量和字面量的惯用法

关键字:const, enum, const_cast, const_iterator, mutable, 左值, 字面量

本质和非本质上的常量

字面量 (literal),是指在 C++ 中直接书写的数字、字符和字符串,如 0x3f, '+', , "hello", L"world",具体语法参考 [CPP_LANG] 字符 整数 浮点数 字符串

左值 (l-value),是指想尽办法可以让其放到 = 左面的对象,这里的 = 是赋值而不是初始化,这里的对象可以是类类型和基本类型。想尽办法主要指强制转换和指针地址操作

本质上的常量,指非左值,字面量、enum 枚举值是非左值

字面量的 const 变量

是否能通过强制而修改,由编译优化和运行平台决定,这是 Win7 VC 2010 的情况:

通过 const_cast 修改字面量的 const 变量
字面量的 const 变量 是否能修改
static 数字字面量 const 不可修改
non-static local 数字字面量 const 可修改
non-static local const char[] 可修改
static const char[] 不可修改
const char* 字符串字面量 不可修改

这些通过强制而修改字面量 const 变量的代码很难移植,并且不是正常的逻辑,所以认为字面量 const 变量是本质上的常量是合理的

const T& 引用

参考 [CPP_LANG] 引用

和字面量的 const 变量不同,字面量的 const T& 会引用一个临时变量

const int& g_C = 42;// 解释为:
// int temp = 42;
// const int& g_C = temp;// 所以强制是合理的
const_cast<int&>(g_C) = 43;

类类型也是如此,并且 const T& 的初始值可以不是类型 T 的,会自动隐式转换,而这一点对于 non-const T& 不成立的,如:

// std::basic_string 中有 const char_type* 的转换 ctor
void func_1(std::string& str);
void func_2(const std::string& str);void test() {func_1("hello");    // Error, string& 不能接受 const char[]func_2("hello");    // OK, const string& 自动隐式转换
}

非本质的常量

非本质的常量比本质的常量更容易理解和确定,通常是函数的 const 参数接受的 non-const 初始化,如:

char* strcpy(char* dest, const char* src);
class Widget {
public:void show() const;
};void func() {char str[] = "hello world";char str2[64];strcpy(str2, str);  // strcpy 中 src 是非本质的 constWidget w;();           // Widget::show 中 this 是非本质的 const*
}

非本质的常量的目的,是在一定执行区间内约束代码(不保证区间外是否 const),产生试图修改 const 的编译错误

常量求值

本质上的常量,可以在编译期求值,它们或化为指令立即数,或保存在目标文件 (.obj) 和可执行文件 (.exe/.dll) 的特殊位置,如 .rdata 区段

也因在编译期求值,本质上的常量可以作为非类型模板参数,只需注意指针类型模板参数只接受外部链接对象的地址,而字符串字面量是内部链接的,这时用外部链接的字符数组代替即可

非本质的常量,在运行时求值

有一些在运行时求值的非平凡情况,评估它们的常量本质性:

// strdup 早于 main 调用
const char* g_Str = strdup("hello");// Widget ctor 早于 main 调用
const Widget g_Widget;void func() {const_cast<char*>(g_Str)[0] = 'A';free(const_cast<char*>(g_Str));const_cast<Widget&>(g_Widget).move(); // 设 Widget::move 是 non-const 方法
}

常量存储

左值一定分配存储,因此可以取其地址

本质的常量(非左值)不一定分配存储,是否分配由具体情况、编译优化和运行平台决定,参考 [CPP_LANG] 常量

  • enum 不分配存储

  • 字面量的 const 变量,如果不引用其地址,如创建指向它的指针或引用,则也不需要分配存储,但具体视编译优化而定

  • 类的 static 整数 const,如果不引用其地址,则可以只用声明式而不用定义式,此时不需要分配存储;如果引用其地址,则需要定义式,并且分配存储

    class Widget {static const int Num = 5;
    };
    const int Widget::Num;  // 引用 Num 地址时,需要定义式
    

const 对比 volatile

const 关键字的横向对比物是 volatile,对比两者可以更明白本质

  • 和编译优化相关

    volatile 是防止变量访问被激进优化的修饰字,volatile 变量的每次读写都会产生实际的内存访问指令,以防止因编译期不可知的因素(如并发线程的共享变量访问)而优化去除 volatile 变量的内存访问(如访问寄存器中的旧值)

    本质 const 可能被优化存储,如存储到 ROM 中

    优化一定和具体编译器与平台有关,这里 C++ 是定义一种适当的目的描述,而非实现细节,如 VC 2005+ 的 volatile 变量读写带有 Acquire/Release 语义以防止编译期指令 reorder,但仅在 IA64 上保证相同的运行时模型,x86 x64 上仍允许运行时 CPU reorder

    本质 const 的存储也是平台相关的,所以上面表格中的 const_cast 很难移植

  • 非本质的 const 和 volatile

    非本质的 volatile 此称谓并不适当,权作和 const 对比,通常是函数的 volatile 参数接受的 non-volatile 初始化,如 InterlockedExchange(volatile LONG*, LONG) 中,它的目的和 const 参数相似,是在一定执行区间内约束代码(不保证区间外是否 volatile),防止那里的激进优化

  • 用 const_cast 强制去除 const, volatile 修饰字

    const_cast 只能去除非本质 const 的 const 修饰

  • 指向 const/volatile 的指针和 const/volatile 指针,参考 MSDN: const and volatile Pointers

    const T*        pc; // 指针引用物是 const
    T* const        cp; // 指针值是 const
    volatile T*     vp; // 指针引用物是 volatile
    T* volatile     pv; // 指针值是 volatile
    

const_iterator

一些 STL 容器如 vector 有 iterator 和 const_iterator 两种迭代器

vector<T>::const_iterator 是 const T*
const vector<T>::iterator 是 T* const

const 和 non-const 方法

class Widget {
public:void move(int x, int y);    // non-const 方法// 解释为:// void move(Widget* this, int x, int y);void show() const;          // const 方法// 解释为:// void show(const Widget* this);
};void func() {const Widget cw;Widget w;(42, 84);     // OK, non-const 对象调用 non-const 方法();           // OK, non-const 对象调用 const 方法c(42, 84);    // Error, const 对象调用 non-const 方法c();          // OK, const 对象调用 const 方法
};

逻辑上的 const 方法

参考 [CPP_LANG] 10.2.7.1, 10.2.7.2 mutable

有时我们需要在 const 方法里修改对象的状态(成员变量),这时需要用 mutable 修饰那个被修改的成员变量

典型的惯用法:返回对象状态时,先检查其 cache 值,如果需要更新,则修改其值,如:

class Widget {
public:// 这里的计算太简单,cache 效率可能比无 cache 差,仅作示例// 合理的场景是有复杂计算过程的方法int area() const {if (m_size_changed) {m_area = m_height * m_width;m_size_changed = false;}return m_area;}void move(int x, int y, int dx, int dy);    // 设置 m_size_changed = trueprivate:int         m_height;int         m_width;bool        m_size_changed;mutable int m_area;
};

从 Widget 用户的角度看,Widget::area() 是具有 const 逻辑的

方法的 const 和 non-const 版本

一些 STL 容器如 vector 有同一个 operator 的 const 和 non-const 两个版本,如 vector<T> 实例化后有两个 operator[]:

class vector<T> {T& operator[](size_type p);const T& operator[](size_type p) const;
};

第一个 operator[] 可用来充当左值(准确的说是可变左值 modifiable l-value),因为所有的左值都是右值,所有它也可充当右值(注意 右值和非左值的区别)

那第二个版本 const 版本的 operator[] 岂不是多余

其实它和非本质 const 配合,用来约束代码,逻辑很合理:如果 vector<T> 是 const,那么每个元素都应该是 const,反之,假如元素能修改,就称不上是 const vector<T>,如:

void print(const vector<T>& vec) {for (vector<T>::size_type i = 0; i < (); i++)cout << vec[i] << '\n';   // 作用 1: 调用第二个版本的 operator[], 因为 vec 是 const* thisvec[0] = T(42);               // 作用 2: 不小心写错, 但 vec[0] 是 const T&, 编译出错从而保护
}

分清两个位置 const 的作用:

作用 1, 返回值 const T&: 产生约束保护
作用 2, const 方法 (const* this): 产生重载规则,C++ 不能仅通过返回不同类型重载函数

也可以理解为,同一方法概念的两个版本:

non-const 版: set 式方法
const 版: get 式方法

转接 non-const 方法到 const 方法

虽然区分同一方法概念的两个版本,但大多数时候,const 和 non-const 版本的内部操作相同,仅在传入参数和返回值类型上不同

为了简化重复代码,可利用 static_cast/const_cast 将 non-const 方法转接到 const 方法,如 vector<T> 的 non-const operator 可如下转接:

T& vector<T>::operator[](size_type p) {return const_cast<T&>(                           // 转换 const T& 到 T&,因为事先知道 this 是非本质的 const*,所以强制是安全的static_cast<const vector<T>&>(*this)[p]);    // 转换 non-const* this 到 const* this,从而调用 const operator
}

反向的,将 const 转接到 non-const 上会违背 const 承诺,不要那么做

参考

[CPP_LANG] 《C++ 程序设计语言》特别版, Bjarne Stroustrup

[EFF_CPP] "Effective C++", 3Ed, Scott Meyers, 条款 02, 03

原文链接:

springboot 初始化一个常量map_C++ 惯用法: const 常量和字面量相关推荐

  1. c语言 const常量作用,C语言 const常量讲解

    //const的本质 //const本质上是伪常量,无法用于数组初始化以及全局变量初始化 //原因在于const仅仅限定变量无法直接赋值,但是却可以通过指针间接赋值 //例如局部常量在栈区,而不在静态 ...

  2. const常量函数详解

    在类中,如果你不希望某些数据被修改,可以使用const关键字加以限定.const 可以用来修饰成员变量和成员函数. const常量与指针 ` const int *p1与const int *p2的顺 ...

  3. const常量和readonly常量区别

    1.const常量为静态常量:readonly常量为动态常量: 2.const常量在编译时值被确定,在运行时值为编译时值:readonly常量,在编译时为类型的默认值(非指定的默认值),在运行时值被确 ...

  4. c语言常量定义规则,c语言常量(c语言常量定义规则)

    帮帮忙吧 ! 还有 知不知道在C语言中形式参数和实际参数之间的联系是什么. C语言定义常量常用的方法有以下2种:第一种:宏定义#define N 3 // 定义了一个常量为3的宏N,在程序中N就代表3 ...

  5. 02_星仔带你学Java之变量、常量、字面量、数据类型

    资料和代码存放地址:<华星详谈-学习中心>.开源项目持续更新中.     大家好,我是星仔.本博客收录于华星详谈-学习中心.本学习中心收集了Java整个技术体系的所有技术要点.每篇博客后面 ...

  6. [JVM]了断局:常量池 VS 运行时常量池 VS 字符串常量池

    一.前言 最近在看JVM, 常量池, 运行时常量池,字符串常量池 这个看的有点懵. 整理一下. class常量池 是在编译的时候每个class都有的. 在编译阶段,存放的是常量的 符号引用 .    ...

  7. 简单理解常量、常量池、运行时常量池和字符串常量池

    1.常量 常量在java中就值的是一般的字面量,比如字符串,整数,浮点数等等数据.简单理解java中什么叫常量 2.常量池,也叫静态常量池或者class文件常量池,说常量池一定要指明是编译器产生的.它 ...

  8. ABAP 标准培训教程 BC400 学习笔记之五:ABAP 编程语言的变量,常量和字面量,以及文本符号

    在 Jerry 的前一篇文章ABAP 标准培训教程 BC400 学习教程之四:ABAP 编程语言的数据类型里,我们实际上已经涉及到了 ABAP 字面量的一种:如下图高亮的 '01' 所示,该文本字面量 ...

  9. 方法区元空间实现之jdk7和8字符串常量池、运行时常量池、静态变量到底在哪?

    方法区(落地实现jdk7永久代,jdk8元空间),元空间并不在虚拟机中,而是使用本地内存,它和堆在逻辑上是连续的,但在物理上是不连续的,所以也叫非堆. 1.此区域是线程共享的.储存已加载的类型信息.常 ...

  10. 网络资源的初始化与释放(C++ RAII惯用法)

    1. 网络资源的初始化与释放(C++ RAII惯用法) C++ RAII 惯用法 RAII (Resource Acquisition Is Initialization)资源获取即初始化 我们拿到资 ...

最新文章

  1. Java控制层怎么调用适配器_java – 从适配器调用片段方法
  2. VMware vCenter Converter Standlone迁移手册
  3. SpringBoot - 优雅的实现【自定义参数校验】高级进阶
  4. PE 头文件 IMAGE_NT_HEADER
  5. ubuntu16.04下wifi上网速度很慢的解决方案
  6. Android的图片压缩并上传
  7. 【优化求解】基于matlab粒子群与遗传算法混合算法求解切削参数优化问题(以成本和碳排放量为目标函数)【含Matlab源码 1619期】
  8. Think PHP(TP)框架基础知识
  9. 读《自己动手写操作系统》(于渊著)第一节
  10. ubuntu装指定分区_ubuntu安装时候硬盘如何分区
  11. 实现lightbox效果
  12. 关于音频录制raw格式转换为mp3文件
  13. 上交计算机考研专业课,2020上海交通大学计算机考研经验,过来人谈考研
  14. matinee和matin区别_法语小灶 | an和année, jour和journée有什么区别?
  15. 手把手教你Axure-默认元件库(上)
  16. 机械硬盘提示格式化的常见原因|3种数据恢复方法
  17. 计算机网络之应用层Tips
  18. 生存分析第一课: censoring 、truncation、survival function、hazard function
  19. Harbor开源项目有奖征文活动开启
  20. 前端MUI+H5+HBuilderX开发APP(IOS,android),后台Springboot,java学习与实践文章,更新中(二)

热门文章

  1. selenium+python在Windows的环境搭建
  2. jQuery对象和DOM对象的区别
  3. 如何学习Vim(转)
  4. 查询工商单位注册信息 平台
  5. js undefined null
  6. 【自然框架】之通用权限(三):组织结构表组
  7. Spring Cloud Zuul网关集成JWT身份验证学习总结
  8. Ajax的工具类AjaxUtils,使用struts返回Json类型
  9. 一种简便的安装使用 qemu 的方法
  10. EvilAP_Defender:可以警示和攻击 WIFI 热点陷阱的工具