C语言指针系列——并不可怕的声明

Posted TigerMee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言指针系列——并不可怕的声明相关的知识,希望对你有一定的参考价值。

指针就像双节棍,指针的指针就像三节棍,依此类对至n节棍。。。

指针的数组,呃,就像某种伤不起的棍。。。

 

C语言里面最灵活、有时候也是最让人莫名的就是指针了,以至于专门有一本书叫《C和指针》。尤其是指针的声明,指向整型的指针,指向数组的指针,指向函数的指针,再加上n多个括号的组合,有时候真让人摸不到头脑。其实,理解指针声明的关键首先在于记住*, [], ()操作符的优先级和结合性;然后由指针名称开始由内向外层层解剖,再复杂的声明也不难解释。本文使用一种自创的图示的方法,来清晰地展示指针声明的根本。

 

首先我们来看一下与指针声明相关的一些运算符的优先级:

(优先级高低由上至下,即上面的操作符的优先级高于下面的运算符的优先级。)

 

操作符描述结合性
() 函数调用 L-R
[] 下标引用 L-R 
*间接访问R-L

 

请务必记住以上运算符的优先级,下面我们来看几个例子。为了能更形象地说明指针声明,我们使用图示法。

 

 

1. 基本数据类型的指针。

让我们从最简单的开始,相信大家都能说出这个声明中var的意思。

a) int *var;

显然,var是一个指向整型的指针,图示如下:

 

 

2. “数组的指针”和“指针的数组”

经过热身,来看一下数组的指针。

a) int (*var)[6];

根据“由内向外”的原则,我们层层分析。

第一层:(*var),表示对var进行解引用,那么var肯定是一个指针。那么它指向什么呢?接着看。(下面用xxx表示(*var))。

第二层:int xxx[6],Ok,看来xxx是一个数组,就是说对var进行解引用后得到一个数组,并且数组的元素是int。

结论:到此为止,这个表达式就解释完了。var是一个指向数组的指针,指向的数组有6个元素,并且数组元素的类型是int。

 

 

有了上面的基础,相信下面的声明对你也不难了:

b) int *(*var)[6];

第一层:与上面的示例一样,var是一个指针。

第二层:int *xxx[6],xxx是什么呢?又有*操作符,又有[]操作符,应该先解释那个呢?上文提到的操作符的优先级这里要发挥作用了。[]操作符的优先级高于*操作符,所以xxx先与[]结合,那么xxx是一个数组,数组的元素类型是int *,即指向整型的指针。

结论:var是一个指向数组的指针,指向的数组有6个元素,并且数组元素的类型是int *,即指向整型的指针。

 

 

如果你感觉对数组和指针足够清晰了,来看一下下面的声明:

c) int *var[6];

这个貌似比上面的简单多了,不过不要上当哦,var这里可不一定是指针。

第一层:*和[]同时出现,还是先看操作符的优先级,[]高于*,所以var先与[]结合,因此var是个数组。

第二层:对var进行下标引用后,得到数组的一个元素xxx。int *xxx,xxx是个指向整型的指针。原来数组的元素是int *,即指向整型的指针。

结论:var是一个数组,数组有6个元素,数组元素的类型是int *。

 

 

3. 函数的指针

从某个意义上说,函数指针是指针中的精髓,提供了极大的便利性,同时也引起了足够的混乱,函数的指针要是再和数组结合起来,可真是乱上加乱。不过没关系,只要我们牢记运算符的优先级,使用由内至外的方法,一样能够清晰地解释。话不多说,来看示例。

(为了简化声明,更突出声明中的指针,如果函数没有参数,则省略了函数的参数类型,即将func(void)记为func()。在实际编程中,强烈建议你使用函数参数void,明确标示该函数没有参数)

a) int (*var)();

第一层:(*var), var是一个指针,指向什么呢?接着看。

第二层:int xxx(),xxx是一个函数,所以var指向的是一个函数。

结论:var是一个函数指针,指向一个函数,并且,函数的参数类型是void,返回值类型是int。

 

 

有了上面的例子做参考,相信这个也不难。

b) int *(*var)();

第一层:(*var), var是一个指针。

第二层:int * xxx(),xxx是一个函数,所以var指向的是一个函数。

结论:var是一个函数指针,指向一个函数,并且,函数的参数类型是void,返回值类型是int *。

 

 

不要被迷惑哦,来看这个。

c) int *var();

注意()和*操作符的优先级,()操作符高于*,优先计算。

到这里相信大家都能理解了,var只是一个函数,不是指针。

结论:var是一个函数,函数的参数为void,返回值为int *。

 

如果你认为已经对函数的指针足够了解了,那么来试试下面这个。

d) int *(*var())();

还是第一次在一个表达式中出现两个()操作符,不怕,还是两大法宝:操作符优先级,由内向外。

第一层:(*var()),()操作符优先级高于*,所以先计算var(),再对结果与*操作符做运算。所以var是一个函数!对函数的返回值可以做解引用操作,所以函数的返回值是一个指针,指向什么呢?往下看。

第二层:int *xxx(),看看上面的示例c,不陌生吧,xxx就是一个返回值为int *的函数。

结论:var是一个函数,函数的参数为void,返回值为一个函数指针,指向的函数参数为void,返回值为int *。

 

 

细心的读者会发现,函数指针和数组指针的声明非常类似,只是将[]操作符换位()操作符。的确,由于()和[]操作符的优先级都高于*,所以指向数组的指针和指向函数的指针声明都很类似。

 

不过,目前[]和()操作符还没有一起出现过,如果一起出现,会是什么情况?

e) int (*var[6])();

第一层:(*var[6]),[]优先级高于*,所以var先与[]操作符做计算,var是一个数组。接下来对数组的元素做解引用,那么数组的元素是指针。

第二层:int xxx(),很容易吧,数组的元素解引用后是一个函数,函数的返回值是int。

结论:var是一个数组,有6个元素,元素的类型是指向函数的指针,函数的参数是void,返回值是int。

 

 

其实看破指针的声明,分辨指针、数组、函数并不困难,只要你牢记本文反复强调的两点:

1) 牢记*, [], ()操作符的优先级

2) 从变量名开始,由内向外的顺序分层分析

再复杂的声明都能清晰地解释。

 

而且,变量是指针、数组还是函数,在一层中就能确定。

1) 如果变量第一层的形式是(*var),那么var一定是一个指针;

2) 如果变量第一层的形式是(...var[x]),那么var一定是一个数组;

3) 如果变量第一层的形式是(...var()),那么var一定是一个函数;

至于指针指向的是什么数据类型,数组的元素是什么数据类型,函数的参数和返回值是什么类型,则需要继续向外层看才能判断。

 

耐心读完本章的读者,恭喜你,相信你现在已经对指针的声明有了很多的了解,并且能够区别指针、数组、函数的声明。虽然指针的声明还能更复杂,甚至远远复杂,但是在实际情况中并不会出现。本章的内容相信能够涵盖实际中的95%的情况,理解本章已经足够让你在实际中使用指针了。更重要的是,如果你能理解本章提出的图示法,相信即使遇到再复杂的指针声明,你都能独立去解析。

 

当然,关于指针的讨论远远没有结束,指针的声明只是第一步,还有更多的复杂问题。比如,什么是指向数组的指针?“指向数组的指针”和“指向数组第一个元素的指针”有什么差别?请见下一章,“指针+1”。

 

以上是关于C语言指针系列——并不可怕的声明的主要内容,如果未能解决你的问题,请参考以下文章

知识分享:C语言知识系列——指针篇

C程序中可怕的野指针

C程序中可怕的野指针

嵌入式 Linux C语言——C语言的安全问题和指针陷阱

C语言04 - 结构体结构体指针tyepdefunionenum

《C专家编程》第四章——令人震惊的事实:数组和指针并不相同