可以说,指针是写代码的人无可避免的一个坎,而指针遇上const则有各种难解难分的关系,下面将其一层一层拆开。
1 两种交互类型
指针和const限定符之间,不外乎两种交互类型:
- 指向const对象的指针
- const指针
2 可能的组合
在详细描述这两种类型前,先给出一些可能的组合方式:
char *p
const char *p
char const *p
char *const p
const char **p
char const **p
char *const *p
char **const p
看到这里,我已经晕了,下面逐一进行拆解。
3 指向const对象的指针
顾名思义,指针指向的对象是const类型,不允许用指针来修改所指const对象的值。
定义:const int *p
p是一个指向char类型const对象的指针,const限定了p所指的对象,而非p本身,p不是const,定义时不必初始化,且允许对p重新赋值,使其指向另一对象(const对象或非const对象):
int num = 100;
const val = 200;
const int *p = # //ok
p = &val; //ok
3.1 允许将非const对象的地址赋给指向const对象的指针,但任何试图使用指向const对象的指针去修改所指对象的值,都是不允许的,尽管其所指对象可能是非const的
int num = 100;
const int *p = # //ok
*p = 150; //error
3.2不允许使用void*指针保存const对象的地址,必须使用const void*类型指针来保存const对象地址:
const int val = 200;
const void *p = &val; //ok
void *pv = &val; //error
3.3 不允许将const对象的地址赋给普通的、非const对象的指针:
const int val = 200;
int *p = &val; //error
const int *pv = &val; //ok
4 const指针
定义:int *const p = &val //假设val已经定义
顾名思义,指针p本身是const的,定义时必须初始化,且定以后不能再修改:
int val = 200;
int num = 100;
int *const p = &val; //ok
p = # //error
p = p; // error, 即使赋回同样的值也不允许
指针本身是const,与其所指对象能否被修改并无关系,这完全取决于所指对象的类型:
int val = 200;
const int num = 100;
int *const p = &val; //ok
*p = 250; //ok, val is not const
const int *const pv = # //ok
*pv = 150; //error, num is const
5 指向const对象的const指针
定义:const int val = 100;const int *const p = &val
既不能修改指针p指向的对象,也不能修改p的指向
6 指针和typedef
typedef int* pint;
const pint p; //ok,尽管没有初始化,编译时却通过了
int val = 100;
p = &val;//error,p是const指针
p的真正类型是:int *const p
事实上,int const val与const int val是一样的,于是pint const p能更好理解。其实,const pint p应该这样理解,p是pint类型的const变量,而pint是个指针,所以p是个指针,p又是const变量,因此p是个const类型的指针:int *const p
7 现在开始处理让人眼花的各种组合
首先明确一点,const int val与int const val是等价的,有一个诀窍是,将类型关键字去掉,看const修饰的是什么,如去掉int后,剩下const val,表明val是const的
然后看const修饰指针的情况,有一个关键点是看const是在*前面还是后面。
const char *p,去掉类型关键字,const *p,const修饰*p,修饰的是p所指的内容,因此p是个指向const对象的非const指针
char const *p,去掉类型关键字,const *p,与const char *p是等价的,就跟const int val与int const val是等价的一样
char *const p,去掉类型关键字,*const p,const 修饰p,修饰的是指针p本身,因此p是个指向char类型的const指针
char const **p,涉及两个指针,p和*p,将*p看做一个整体,用A来代替,char const *A,这就很好理解了,*p是一个指向char类型const对象的普通指针,看一个实验:
int val = 100;
int num = 200;
int *p = &val;
int *pn = #
const int **pp;
pp = &p; //ok,pp可以重新赋值,表明pp不是const,这里编译器给出一个warning,如果p改成int const *p = &val则没有warning
*pp = pn; //ok,可以通过pp修改所指对象的值,表明pp所指对象不是const
**pp = 150; //error,不能通过*pp修改*pp所指对象的值,表明*pp是指向const对象的指针
const char **p与char const **p是一样的
char **const p,涉及两个指针,p和*p,const修饰p,因此p是const的,至于*p的特性,看下面一个例子会比较清楚
int val = 100;
int num = 200;
int *p = &val;
int *pn = #
int **const pp;
pp = &p; //error,pp是const,不允许重新赋值,但是上面一句定义时没初始化却没报错
*pp = pn; //ok,*pp可以重新赋值,说明指针*pp非const,也说明pp不是指向const对象
**pp = 150; //ok,可以通过指针*pp修改所指对象的值,说明*pp不是指向const对象的
char *const *p,虽然有两个星号,看起来似乎很玄奥,其实与char const *p是一样的道理,前者是指向char*类型const对象的指针,后者是指向char类型const对象的指针,看下面例子:
int val = 100;
int num = 200;
int *p = &val;
int *pn = #
int *const *pp;
pp = &p; //ok,pp指向int*类型const对象,可以重新赋值
*pp = pn; //error,不允许通过pp来修改其所指对象的值