参考:https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777416.html
一、const介绍
在C语言中我们想定义常量一般是用#define 宏来实现的,但是C++里面用const修饰常变量,从而便于管理代码。
常变量: const 类型说明符 变量名
常引用: const 类型说明符 &引用名
常对象: 类名 const 对象名
常成员函数: 类名::fun(形参) const
常数组: 类型说明符 const 数组名[大小]
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:
const int a=5; 与 int const a=5; 等同 类名 const 对象名 与 const 类名 对象名 等同
二、const用法
viod foo(const int *a ,const int &b );
这里a是指针变量指向int类型的常变量,a是变量,可以更改指向,a所指向的对象是常量。b是int 类型的引用,与常引用一个道理,在函数体内无法进行修改三、指针介绍
指针(pointer)实现了对其他对象的间接访问。指针本身也是一个对象,可以进行赋值和拷贝。
int x=10; int *p = &x; //这里p就是指向变量x的一个指针,存放着变量x实际存放的地址
四、常量指针与指针常量
首先需要区分常量指针与指针常量,方法是:从右往左,遇到数据类型如"int"读作“整型”,“const”读作“常量”,“ * ”读作指向,例如下面:
const int p; //整型常量 const int *p; //指向整型常量的指针(从右往左读) int const *p; //指向常量整型的指针(同上) int *const p; //常量指针,指向整型 const int *const p; //常量指针,指向整型常量 int const *const p; //常量指针,指向常量整型(同上)
因此从读法上区分指针常量与常量指针就变得十分简单了。对于他们的理解可以是:
对于指针常量,被指向的对象是常量;对于常量指针,是指针本身是常量。
const double pi = 3.14; //pi是常量,不能改变 double *ptr = π // 错误,ptr仅仅是普通指针 const double *cptr = π //cptr指向了一个“对象”,这个对象是常量 *cptr = 42; //不能对cptr赋值,因为它已经是一个常量了。 double dval = 3.14; //dval可以改变 cptr = &dval; //可以通过改变dval来改变cptr
所以和常量引用一样,指向常量的指针没有规定所指向的对象必须是常量,它仅仅要求不能借助这个指针改变对象的值,而没有规定那个对象的值不能通过其他方法改变。
#include <iostream> using namespace std; int main() { /* int t = 3; int &r = t; int newt = 4; r = newt; cout << newt << '\n' << r << endl; */ //上面可以正常执行为4和4 /* int t =3; const int &r = t; int newt = 4; r = newt; cout << newt << '\n' << r << endl;//错误 */ int i = 3; const int *p = &i; int newi = 4; p = &newi; cout << newi << endl; cout << *p << endl; //可以正常执行为4和4 return 0; }
对于常量指针(const pointer),必须进行初始化,且一旦初始化完成就无法修改。*在const之前说明指针是一个常量,例如:
#include <iostream> using namespace std; int main() { int a = 2; int c = 3; const int * pi = &a; cout << pi << '\t' << *pi <<endl; pi = &c; cout << pi << '\t' << *pi <<endl; cout << &c << '\t' << c <<endl; int * const ptr = &a; cout << ptr << '\t' << *ptr <<endl; *ptr = c; cout << ptr << '\t' << *ptr << endl; cout << &a << '\t' << a << endl; /*执行结果如: 0x6dfee4 2 0x6dfee0 3 0x6dfee0 3 0x6dfee4 2 0x6dfee4 3 0x6dfee4 3 */ return 0; }
可见作为常量指针,ptr被牢牢扣死在了0x6dfee4这个位置上,因为这个位置就是一个“常量”。
最后总结:看const与 * 的相对位置(从右往左念)
常量指针:type * const ptr
指针常量:const *type ptr
五、顶层与底层const
const修饰符用于修饰常量,在程序运行过程中一旦初始化就不会再改变
如果我们要描述一个指针是常量或者指针指向的对象是常量时,会出现一些歧义。
因为指针通常来说也是一个变量,但是我们也可以用const修饰符来获得常量指针。
所以为了区分这两种情况,引入顶层const和底层const两个名词。
顶层const :表示指针本身是常量
底层const: 表示指针指向的对象是常量
const int i=1; const int *p = &i; //底层const,p的值可以改变,但i的值不能改变 int const *p = &i; //底层const,同上,无差别 const int j = 0; p = &j; //正确,j是常量
int i =10; int *const p = &i; //顶层const,p是常量指针,初始化之后不能修改。 int j=0; p = &j; //error: expression must be a modifiable lvalue
const int i = 0; const int *const p =&i; //常量指针p指向常量对象i
int i = 0; int *const p1 = &i; //不能改变p1,顶层 const int ci = 42; //不能改变ci,顶层 const int *p2 = &ci;//可改变p2,底层(改变方法参见上面指针常量的例子) const int *const p3 = p2 //右const为顶层,左const为底层 const int &r = ci //用于声明的const都是底层 i = ci //顶层ci,拷贝没毛病 p2 = p3 // p2,p3指向对象类型相同,p3顶层部分不受影响
当执行拷贝操作的时候,底层与顶层的区分就出来了。
int num_c = 3; const int *p_c = &num_c; //p_c为底层const的指针 //int *p_d = p_c; //错误,不能将底层const指针赋值给非底层const指针 const int *p_d = p_c; //正确,可以将底层const指针复制给底层const指针
当执行对象拷贝时,两者需要有相同的底层const资格,或者两个对象数据类型能转换。一般来讲,非常量可转换为常量,反之不可。