前言

c++中使用到const的地方有很多, 而且const 本身也针对不同的类型可能有不同的含义, 比如对指针就有顶层和底层. 本节就是探讨关于C++中const的在不同的地方不同表现或含义.

const

关于const :

  • const修饰的对象一旦创建一般就不能改变, 所以对于const对象必须进行初始化.

    int i = 0;
    const int j; // error. 必须进行初始化
    const int j = 0;
  • 初始化时并不关心初始化对象的是const还是非const

    int i = 0;
    const int j = i; // i 是非const也可以
  • const不能改变

    const int i = 0;
    i = 1; // error const的对象一般不能进行修改
  • 引用对象的类型必须与其所引用对象的类型一致

    int i = 0;
    int &j = i;
    double &size = j; // error. size与j的类型不一致
    • 因为以上引用的规则, 所以const类型的引用只能被const的对象引用
    int i = 0;
    const int &size = i;
    int &j = size; // error. size的类型为const int, j的类型为 int. 两者并不匹配
    • 引用类型对应的例外

      int size = 0;
      const double &i = size; // size与i的类型虽然不一致, 但是因为const的原因使得等式成立

      原因 : 虽然i与size两者的类型并不一致, 但是初始化i时, 编译器会为size生成一个临时量(double j = size;), 然后i最终绑定在这个临时量上(const double &i = j ). i 之所以能绑定在一个临时量上, 还是因为const的对象不能被修改, 则i 无法被修改, 保障了临时量不会被改变.

      注意 i实际绑定在临时量上, 并没有绑定在size上

      int size = 0;
      const double &i = size;
      size = 1; // i 实际值并没有改变, 它绑定的是临时量不是size
  • 修改const对象的值

    int i = 0;
    const int size = i;
    const int &j = i;
    const_cast<int&>(size) = 1; // 将size的值修改为1
    i = 2; // 因为j绑定i, i被修改则j也被修改

    因为const只是对修饰的对象限制其不能修改, 不能保证对象一定是常量, 所以能保证是常量的对象最好都定义成constexpr . 对constexpr不清楚的可以看一下constexpr浅析

顶层与底层const概念

顶层const : 指针本身是一个常量(即地址不允许改变).

其实我们一直都有在用顶层const, 比如int i = 0;, 这就是一个顶层const, 因为 i 的地址不会改变, 只有值会被改变.

int size = 0, i = 0;	// 其实是顶层const
int *const p = &size; // const直接修饰指针本身, 顶层const
p = &i; // error. p是顶层const
*p = 1; // 顶层const可以直接修改值

底层const : 指针所指的对象是一个常量(指针本身是可以修改的, 只是指向的值不能进行修改).

int size = 0, i = 0;
const int * p = &size; // const直接修饰指针指向的对象, 底层const
ptr = &i; // ptr可以重新指向其他地址, 因为是底层const
*ptr = 1; // error. 底层const不能直接修改指向的值

当然我们可以将一个对象修饰为既是顶层又是底层

int size = 0;
const int * const p = &size; // 既是顶层又是底层
const int i = 0; // 既是顶层又是底层

有一点一定要注意 : 顶层const被拷贝时会忽略掉顶层const

const int i = 0;
int size = i; // 这里的顶层const被忽略掉了
auto j = i; // 此时 auto 推断出 j的类型为 int , i 的顶层const被忽略了

const与重载函数

在重载函数时, const类型的参数可能会在处理顶层const与底层const的时候出现问题. 具体什么问题分析之后再来总结.

void Ccount(int ccount) {}			// ccount为顶层const
void Ccount(const int ccount) {} // error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了 void Ccount_pointer(int *ccount) {} // ccount为顶层const
void Ccount_pointer(int *const ccount) {} // error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了

上面可以看出来, 因为顶层const会被忽略, 所以顶层const与另外顶层const不能被区分出来.

// error. 在函数调用的时候有二义性, 并不能区分调用哪一个函数, 在编译期间报错.
void const_reference(int i) {} // i 是顶层const. 参数类型为 int
void const_reference(int &i) {} // i 是顶层const. 参数类型为 int // 下面都没有问题
void const_reference(int &i) {} // i 是顶层const. 参数类型为 int
void const_reference(const int &i) {} // i 是底层const. 参数类型为const int void const_pointer(int *i) {} // 顶层const
void const_pointer(const int *i) {} // 底层const

因为引用对象的类型必须相同, 所以int &iconst int &i有区别, 前者类型为int , 后者类型为const int, 所以后者是底层const.

上面可以看出来, 因为底层const不会被忽略, 底层与底层有区分, 所以可以底层const可以用来重载.

const与类的常量成员函数

如果const放在函数名的前面其意义只是告诉编译器返回类型是const类型的常量而已, 但是如果把const放在函数名后那就又是另一种情况了, 我们这里主要分析的就种情况.

const int const_func(int i) {return i;}	// 这里函数返回的是const类型的, 即常量
int const_func(int i) const {return i;} // error. const不能直接放在普通函数名的后面, 只能放在成员函数(类函数)名的后面,原因之后分析.

定义一个简单的类

class A {
private: int nun;
public: int const_func() const {return 0;} // success
};
// 如果将函数改为
int const_func() const { ++num; return 0;} // error

int const_func() const函数中const是告诉编译器, 类中定义的非静态变量都不能进行修改. 原因在于类的所有成员函数都会隐式的传入this 指针, 即上面的成员函数被修改为

int const_func(const A * const this)  { ++num; return 0;}

this指针本身就是顶层const, 而放在函数名后面的const是为了修饰this指针的, 但是因为this指针不能显示的被传入, 所以const只能放在函数名后.

知道了这里const修饰的是this 指针, 所以this->i就不能被修改了, 而静态成员不是属于实例化类本身, 也就没有this指向静态变量, 所以可以在以上类型的函数中修改静态变量.

const放在成员函数名后面的函数我们称为常量成员函数

但是有的时候非要在以上函数中改变某个变量的值怎么办? c++中有mutable关键字, 就是允许这样的特例发生. mutable就是告诉编译器, num可以在任何函数中进行修改.

class A {
private: mutable int nun;
public: void const_func() const {++num;} // success
};

const与类

我们在定义类的实例化时, 可能会将类实例化定义为const, 即

class A {
private: int nun;
public: int const_func() {return 0;}
};
A a;
const A ca;
a.const_func(); // success
ca.const_func(); // error

上面出错的原因在于ca的类型为const, 所以与之对应的函数应该是常量成员函数, 所以最好在定义类函数实现时, 重载一个常量成员函数.

const与类静态成员

同样上面的类为例子

class A {
private: static int nun = 0; // error
public: int const_func() const {++num; return 0;} // success. 原因上面分析了
};

在类中定义的静态变量不能在类中初始化, 必须在类外进行初始化, 不然报错. 所以上面应该在类外改为int A::num = 0; .

但是有一个例外 :

class A {
private: const static int nun = 0; // success
};

因为const要求必须在创建的时候就需要对其初始化, 所以上式的例子才成立.

const与typedef

当我们不愿意每次都定义指针的时候, 就想到用typedef来定义指针类型. 即:

typedef char * Str;
char *str1 = "hello";
const char *str2 = "hello";
const Str str3 = "hello";

对其进行相同的操作

str1[0] = 'a';
str2[0] = 'a'; // error
str3[0] = 'a'; // success str1++;
str2++; // success
str3++; // error

以上面的执行的操作可以看出来typedef不仅仅只是一个替换, 它将const Str str3转换为了char *const str3而不是跟str2一样.

原因是 : char *重写声明之后, 真实的数据类型变成了char 而不是char *, 反而*成了声明符的一部分了, 导致const Str的数据类型为const char, 而*修饰const char, 也就成了常量指针.

总结

本节汇总了部分关于const用法的注意点, 可能看起来会很晕, 也不是一次性就容易记住, 希望在看的时候最好也进行验证是最好的. 最主要记住底层const和顶层const, 怎样重载, 基本很多的问题都是衍生.

最新文章

  1. 鱼眼模式(Fisheye projection)的软件实现
  2. Install and Configure SharePoint 2013 Workflow
  3. 千呼万唤始出来:Apache Spark2.0正式发布
  4. linux-14基础命令之-复制(cp)移动(mv),删除(rm),拷贝文件(dd)
  5. 中南民航如何利用K2BPM构建业务流程?
  6. python urllib模块的urlopen()的使用方法及实例
  7. Web表格
  8. css3创建动画
  9. java比较器Comparable接口和Comaprator接口
  10. SVG-1
  11. python并发编程之线程(一):线程&amp;守护线程&amp;全局解释器锁
  12. web 直播&amp;礼物赠送------腾讯云(四)
  13. css/html/Javascript/getUrlCode/各种前端小点汇总集合
  14. vue使用animate.css库
  15. sql数据库之多库查询
  16. IE中div被视频遮住的解决方法
  17. SQL 2008 R2 收缩日志,不用修改简单模式
  18. JS 控制页面刷新
  19. python3连接Mongodb
  20. 完美兼容IE,chrome,ff的设为首页、加入收藏及保存到桌面js代码

热门文章

  1. 【C语言】编写函数实现库函数atof
  2. VC 获取任务栏窗体的句柄
  3. 3736 【HR】万花丛中2
  4. Batch基本知识
  5. [POJ 1386] Play on Words
  6. JSP-Runoob:JSP JavaBean
  7. VPS主机
  8. nodejs操作mysql
  9. 用Python一键搭建Http服务器的方法
  10. 使用UDEV SCSI规则在Oracle Linux上配置ASM