写点什么

Android C++ 系列:C++ 最佳实现 5 之 const

作者:轻口味
  • 2022 年 4 月 19 日
  • 本文字数:2462 字

    阅读完需:约 8 分钟

Android C++系列:C++最佳实现5之const

1. 背景

在 Java 中我们定义常量通常用final static TYPE variableName = xxx来实现,在 C 语言中我们通常用预编译宏来实现:#define MAX 100,在 C++中虽然我们仍可以使用预编译宏,但是已经不推荐这么干了。在 Effective c++ 的条款 1 中:提到“尽量用编译器而不用预处理”,因为 #define 经常被认为好象不是语言本身的一部分。而且有时候用宏,会出现意想不到的输出结果。const 是 C++给我们提供的新的定义常量的方法:


const int MAX = 100;const std::string NAMESPACE = "android";
复制代码


他们主要有下面几点区别:


  1. 类型和安全检查不同:宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,容易出错;const 常量有类型区别,需要在编译阶段进行类型检查;

  2. 编译器处理方式不同:宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试;const 常量是一个"运行时"概念,在程序运行使用,类似于一个只读变量;

  3. 存储方式不同:宏定义是直接替换,不会分配内存,存储于程序的代码段中;const 常量需要进行内存分配,存储于程序的数据段中;

  4. 定义域不同:就算是定义在方法体内的宏变量也可以在其他地方任意访问,而 const 不行;

  5. 定义后能否取消:宏定义可以通过#undef来取消,const 常量定义后将在定义域内永久有效;

  6. 是否可以做函数参数:宏定义不能作为参数传递给函数,const 常量可以在函数的参数列表中出现。


其实 const 做定义常量很好理解,而且和宏定义的区别也很好理解,只要记住宏展开只是替换,而 const 常量其实是只读的变量,上述的区别就很好理解了。但是关于 const 还有很多容易踩到的坑,下面我们在做一些详细分析和记录。

2. const 修饰变量的注意事项

  1. const 对象一旦创建后它的值就不能改变,所以 const 对象必须初始化。

  2. 与非 const 类型所能参与的操作相比,const 类型的对象能完成其中的大部分,主要的区别点在于 const 类型的对象上执行不改变其内容的操作。利用一个对象初始化另外一个对象,它们是不是 const 都不重要:


int i = 100;const int MAX = i;//正确int j = MAX; //正确
复制代码


  1. 默认状态下,const 对象仅在文件内有效,如果需要在多个文件中使用,那么不管声明还是定义都要添加 extern 关键字。

3. const 和复合类型结合

复合类型是基于其他类型定义的类型,我们接触到最常用的有引用和指针。

3.1 const 的引用

把引用绑到 const 对象上,称为对常量的引用。对常量的引用被不能被用作修改它所绑定的对象:


const int a = 100;const int &ar = &a;//正确ar = 200; //错误,ar是对常量的引用int &ar2 = a;//错误,让一个非常量引用指向一个常量对象。
复制代码


还有一个关于引用的例外情况:


double pi = 3.14;int &r1 = pi; //错误:Non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'double'const int &r2 = pi;//正确
复制代码


为什么 double 类型的对象绑定到非 const 的 int 引用报错,而绑定到 const 的 int 引用正常呢?


这里面想要绑定涉及到一个强制类型转换的问题,double 转 int 会强转:


double pi = 3.14;int temp = pi;int &r = temp;
复制代码


这里面 r 引用绑定了一个临时量对象。临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。如果 r 不是常量引用,那么就允许改变 r 所引用对象的值,此时 r 引用绑定的是 temp 而不是 pi。而我们即使要改变也是想改变 pi 而不是 temp,既然我们基本上不会想着把引用绑定到临时量上,所以 C++把这种行为归为非法。

3.2 指向 const 的指针

不能用于改变指针所指对象的值的指针称为指向常量的指针


const int i = 100;const int * ip = &i;*ip  = 200;//错误,不能给const指针指向的对象赋值。
复制代码


注意:常量指针和常量引用不过是一种“自以为是”的规则,不能通过该指针或者该引用改变对象的值,但是他们指向的对象不一定是一个常量,可以通过其他句柄来修改他们指向对象的值:


int i = 100;const int *ip = &i;i = 300;//正确std::cout<< "*p = " << *ip << std::endl;
复制代码


和对常量的引用一样,指向常量的指针所指的对象不是一定要是常量。它们都可以共同其他句柄修改被它们引用或者指向的对象的值。

3.3 const 指针

指针是对象而引用不是,所以 C++允许把指针本身定为常量。常量指针必须初始化。C++中把*放在 const 关键字之前用以说明指针是一个常量,不变的是指针本身的值而非指向的那个值。


int i= 0;int *const pi = &i;int j = 1;pi = &j;//错误 Cannot assign to variable 'pi1' with const-qualified type 'int *const'
复制代码


**最佳实践:**面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真是含义。


离 pi 最近的是 const,那么 pi 本身是一个常量对象,对象的类型由声明符的其余部分确认,声明符的下一个符号是*,意味着 pi 是一个常量指针,最后,声明语句的基本数据类型部分确定了常量指针指向的是一个 int 对象。


int i = 10;const int *pi  = &i;
复制代码


这个 pi 解释为 pi 是一个指针,指向的是 int 对象,并且这个对象是常量。

3.4 指针的顶层 const 和底层 const

我们经常在代码中看到下面这种形式:


int i = 10;const int *const pi = &i;
复制代码


因为指针本身是个对象,它又可以指向一个对象,所以一个指针既可以是常量也可以同时指向一个常量,所以就有上面的形式的指针。


  • 顶层 const:表示指针本身是个常量,可以表示任意的对象是常量,对任何数据类型都适用;

  • 底层 const:表示指针所指的对象是一个常量,只和指针与引用等复合类型的基本类型部分有关系。


int i = 10;const int j =100; //顶层constint *const p1 = &i;//顶层const const int* p2 = &i;//底层constconst int *r = i; //底层const
复制代码


执行对象拷贝时,顶层 const 不受影响,但是拷入和拷出的对象必须有相同的底层 const,或者两个对象的数据类型必须能够转换(非常量可以转换为常量,反之不行)。


可以这样理解,一个可以被修改的对象我们可以暂时不允许它修改,但是一个不可以修改的对象允许修改却是不允许的。

4. 总结

本文介绍了 const 与宏定义常量的区别,以及 const 修饰变量的注意事项,绑定常量的引用、指向常量的指针和常量指针。并介绍了顶层 const 与底层 const 的区别。

发布于: 刚刚阅读数: 3
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 欢迎加我微信wodekouwei拉您进InfoQ音视频沟通群 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:C++最佳实现5之const_c++_轻口味_InfoQ写作社区