c++中使用到const
的地方有不少, 并且const
自己也针对不一样的类型可能有不一样的含义, 好比对指针就有顶层和底层. 本节就是探讨关于C++中const
的在不一样的地方不一样表现或含义.c++
const
修饰的对象一旦建立通常就不能改变, 因此对于const
对象必须进行初始化.函数
int i = 0; const int j; // error. 必须进行初始化 const int j = 0;
初始化时并不关心初始化对象的是const
仍是非const
this
int i = 0; const int j = i; // i 是非const也能够
const
不能改变.net
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. 二者并不匹配
引用类型对应的例外code
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上blog
int size = 0; const double &i = size; size = 1; // i 实际值并无改变, 它绑定的是临时量不是size
修改const
对象的值get
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, 好比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的时候出现问题. 具体什么问题分析以后再来总结.
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 &i
与const int &i
有区别, 前者类型为int
, 后者类型为const int
, 因此后者是底层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
, 即
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
, 因此与之对应的函数应该是常量成员函数, 因此最好在定义类函数实现时, 重载一个常量成员函数.
一样上面的类为例子
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
要求必须在建立的时候就须要对其初始化, 因此上式的例子才成立.
当咱们不肯意每次都定义指针的时候, 就想到用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
, 怎样重载, 基本不少的问题都是衍生.