您现在的位置是:网站首页> 内容页

const浅析

  • 555000公海登录
  • 2019-02-11
  • 225人已阅读
简介前言c++中使用到const的地方有很多而且const本身也针对不同的类型可能有不同的含义比如对指针就有顶层和底层.本节就是探讨关于C++中const的在不同的地方不同表现或含

前言

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

const

关于const :

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

int i = 0const int j // error. 必须进行初始化const int j = 0

初始化时并不关心初始化对象的是const还是非const

int i = 0const int j = i // i 是非const也可以

const不能改变

const int i = 0i = 1 // error const的对象一般不能进行修改

引用对象的类型必须与其所引用对象的类型一致

int i = 0int &j = idouble &size = j // error. size与j的类型不一致因为以上引用的规则 所以const类型的引用只能被const的对象引用

int i = 0const int &size = iint &j = size // error. size的类型为const int j的类型为 int. 两者并不匹配

引用类型对应的例外

int size = 0const 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 = 0const double &i = sizesize = 1 // i 实际值并没有改变 它绑定的是临时量不是size

修改const对象的值

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

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

顶层与底层const概念

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

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

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

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

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

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

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

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

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

const与重载函数

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

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

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

// error. 在函数调用的时候有二义性 并不能区分调用哪一个函数 在编译期间报错. void const_reference(int i) {} // i 是顶层const. 参数类型为 intvoid const_reference(int &i) {} // i 是顶层const. 参数类型为 int// 下面都没有问题void const_reference(int &i) {} // i 是顶层const. 参数类型为 intvoid const_reference(const int &i) {} // i 是底层const. 参数类型为const intvoid const_pointer(int *i) {} // 顶层constvoid 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 aconst A caa.const_func() // successca.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 * Strchar *str1 = "hello"const char *str2 = "hello"const Str str3 = "hello"

对其进行相同的操作

str1[0] = "a"str2[0] = "a" // errorstr3[0] = "a" // successstr1++str2++ // successstr3++ // error

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

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

总结

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

文章评论

Top