指针是C语言的一个特色,它是一把双刃剑。
指针提供了便捷的内存操作,同时也提升了程序的复杂度。
良好的编码习惯,有助于提升程序的健壮性。
const和指针
疑问
在阅读本文之前,请先看以下几个声明语句:
int a = 10;
// #### 定义部分 ####
int const *d = &a; // 0
const int* b = &a; // 1
int* const c = &a; // 2
const int* const p = &a; // 3
const int const *p2 = &a; // 4
上述声明的指针变量,哪些指针是常量,哪些指针指向的内容是常量,或者哪些指针和其指向的内容都是常量?
有很多人写过博客来讨论const用于指针声明的情况,看完了之后,我仍然“记不住”。
因为大部分文章只是用程序run了一遍,告知结果,对应的分析并没有切中要害。也可能是这些分析没有对应我自身的问题点。
所以,凡事还是得躬行。
指针声明的语法
我们先从声明指针变量的语法来讲,一般声明一个指针变量的语法为:
type * p = xxx;
一般来讲,有两种写法:
type* p = xxx;
<pre name="code" class="cpp">type *p = xxx;
C语言是一个自由的语言,这两种形式在书写上都是符合要求的,而且一般人都倾向于将'*'写在靠近类型的一侧,也就是第一种声明方式,我自己也是如此。
这种声明方式的好处是,让人一下子就注意到这个变量是指向某种类型的指针。
但是,如果你对指针声明的知识理解的不到位的话,很多时候,采用第1种声明方式是有风险的。
比如以下这句
int* a, b, c;
就很容易造成误解。
有的人可能会误以为这是三个int型指针,而编码者本身的意图可能也是如此,但是事实并非如此。
它的含义是,a为指向int变量的指针, b和c均为一个int型变量。这会给维护代码的人带来困扰,因为他不知道编码者当初的本意。
如果采用方式2来声明的话,就会明显一点。
int *a, b, c;
一个解决的办法就是,每个变量都单独声明。但是,仅仅这样我们还没有理解到位。
解析
接下来,我们分解以下声明语句,之后大家就会有一个清晰的认识了。
其实,理解指针声明的关键在于理解这个'*'。
在这里,*是间接访问操作符,它只能间接引用指针变量;*a本身就是一个表达式,表示间接访问a指向的内存单元。
接下来,我们依据这个理念来理解const 指针的声明方式。
语句一
const int* b = &a;
它等价于
const int (*b) = &a;
- *b表明b是一个指针变量
- int (*b)表明通过b能够间接访问到一个整型变量,也就是说b为一个整型的指针
- const int (*b)则表明该整型变量是一个const常量。
所以,b是一个int类型的指针,它本身不是常量;它指向的整型变量为常量。
语句二
int* const c = &a;
它等价于
int *(const c) = &a;
- const c说明c是一个常量
- *(const c) 说明这个常量是一个指针
- 最后,int 关键字表明这个指针指向一个整型变量。
所以,c是一个int型指针,这个指针存储的地址值是常量,不可更改;c指向的整型内存单元不是常量,可更改。
语句三、四
它们是等价的,上述举例只是为了引出两种声明的写法。我们选其中一句来说明。
const int* const p1 = &a;
它等价于
const int (*(const p1)) = &a;
- const p1表明p1是一个常量;
- *(const p1)表明p1是一个指针;
- int *(const p1)表明p1是一个整型指针;
- const int *(const p1)则跟语句一是类似的,表明p1指向的内存单元也是一个常量。
所以,这里p1和p1指向的内存单元都是常量。
结束语
至此,用const来声明指针的几种情况均已经阐述完毕。其中,对于声明语句0我没有进行分解,日后留给自己复习检查用~