指针是什么就不详细说明了,用一句话来总结就是:“指针是一种保存变量地址的变量”。
1.声明简单的指针变量
先看看代码:
int i = 1;
int *p; //声明一个指向int类型数据的指针变量 p
p = &i; //&为取地址符,把变量i的地址赋值给指针 p
*p = 2; //此时 i 的值变成2了
这段代码声明了一个指针变量p,并把它指向变量i,通过p可以访问到变量i,并对i的值进行修改。现在对星号 的作用进行详细的讲解。* 是一元运算符,它用在不同的地方将具有不同的作用。
1.1 星号 * 用于声明语句时的作用
上面的代码片段中的第2行代码“int p;”就是星号 用于声明语句时的情况。对于指针的声明,我们首先要从 p 这里开始看起,这是C语言中“声明”的语法,下面会介绍到。
第一步:先看p的右边有没有其它符号(分号不算),可以看到p的右边并没有符号。
第二步:看p的左边,在p的左边有一个星号 * ,关键的时候到了!在这里可以看到星号 * 作用于p上面,其产生的效果是:“声明变量 p 是一个指针 ”。目前为止,我们已经知道 p 是一个指针了,但还不知道该指针是指向什么类型数据的。
第三步:看星号 * 右边的类型说明符 int,在这里int的作用就是声明 p 指向的数据类型是int。至此,“int *p;”这句代码就完全解释完了。
注意!上面的 int 、* 这两个符号发挥作用是有先后的,并不是组合在一起来发挥作用的。先是 * 发挥作用:声明p是一个指针变量。然后是int发挥作用:声明p指向的数据类型是整型数据。
1.2 星号 * 用于声明语句之外时作用
以上代码片段中,最后一句“ p = 2; ”就是星号 用于声明语句之外时的作用。这句语句等效于“ i = 2;”。
用专业点的说法就是:如果指针p指向整形变量 i,那么在 i 出现的任何上下文中都可以使用 p ,当 作用于指针变量p时,就是访问指针变量p所指向的变量 i。
至此,已经说了如何声明简单的指针变量,是不是觉得很简单呢?接下来将会按照复杂程度递增的顺序来介绍和指针有关的复杂声明
2.复杂声明和声明语法
2.1声明指向指针的指针变量
先看代码:
int i = 1;
int *p = &i; //声明指针变量p,p指向变量i
int **pp; //声明指针变量pp
pp = &p; //pp指向变量p,p是指针变量
**pp = 2; //此时 i 的值变成 2 了
//*(*pp) = 2;这个语句等效于上面的语句
代码段中的1、2行在上面已经介绍过了,现在主要介绍第2行之后的代码。
注意,这下面这两句是等效的,这是因为类似于 * 和++这样的一元运算符遵循从右至左的结合顺序。
int **pp; //声明方式1
int *(*pp); //声明方式2
现在开始解释声明方式2(和声明方式1是一样),同上面的简单声明一样。我们再次从变量名pp开始。
第一步:先看看pp右边有没有符号,可以看到pp的右边有一个右括号,而括号只是强调结合顺序,所以不用管它。
第二步:看pp的左边,可以看到,pp的左边有一个星号 * (从右数起第一个星号),该星号的作用是:“声明变量pp是一个指针。”此时,我们还不知道指针pp所指向的数据类型,并且把括号里面的 * 和 pp 两个符号都解释完了。现在看看括号外面的符号。
第三步:先看括号外面的右边,可以看到,括号外面的右边并没有符号。
第四步:看括号左边,这时,我们看到括号外面的左边有一个星号 * (从右数起,第二个星号 ),这个星号 的作用是:“声明指针变量pp所指向的数据类型是指针类型”。此时,我们已经知道了pp指向的数据类型是指针类型(即是代码片段中 p 的类型),但还不知道所指向的指针是指向什么类型数据的。例如,pp指向指针p,但却不知道p指向的是什么类型的数据。
第五步:看最左边的符号“int”,这个int的作用就是:声明pp所指向的指针所指向的数据类型是整型int。例如,pp指向指针p,现在我们知道了p指向的数据类型是int整型。
哈哈,是不是有点晕了,现在简单总结下:pp右边第一个星号 * 声明了pp是一个指针变量,第二个星号 * 声明了指针变量pp所指向的数据类型是指针类型,而类型说明符int则声明了pp所指向的指针指向的数据类型是int。
看到这里,你可能会疑惑:一定要按照上面的步骤来解释声明吗?这是套路吗?
答:是的。因为这是C语言中”声明“的语法,下一小节将会对该语法进行介绍。之所以这么迟才介绍这个语法,是因为在用过后,会更容易理解它。
2.2 C语言中”声明“的语法
在这里,将会对声明的语法进行讲解,为后面理解更加复杂的声明打基础。
在C语言中解释一个声明,要先从被声明的变量名开始解释。并不是从左到右,也不是从右到左解释,而是从中间开始,更准确来讲,是从被声明的变量名开始解释。而声明总是由很多符号和唯一的变量名相结合,这些符号和唯一的变量名结合就是声明符。声明的形式为
”T D“的形式,其中 T代表类型,D代表声明符。举个例子吧:
int *p;
int就是T,而*p就是D。
下面将以变量名p来对声明中用到的符号进行解释。
变量名p可以和很多符号来结合成声明符,如[ ],(),*。
1.当p与符号 [ ] 结合时(结合在p的右边),符号 [ ]的作用就是声明变量p是一个数组类型。[ ]里面的数字则决定了数组中元素的个数。如下面的声明代码:
int p[5];//声明变量p是一个整型数组,数组中有5个元素
需要注意的是,符号[ ]的优先级是比星号 * 的优先级高的。
2.当p与符号 ( )结合时(结合在p的右边),符号( )的作用就是:声明p是一个函数,通过p()可以调用该函数,()中可以有参数列表或者无参数列表,如下面代码:
int p();//声明一个函数p,返回值由前面的int决定
int p(int a,int b); //声明一个带参数的函数p
同样,符号( )的优先级比星号 * 高。
3.当p与星号 * 相结合时(结合在左边),符号 * 的作用就是声明p是一个指针类型 。
如下代码 :
int *p; //声明p是一个指针,该指针指向int类型数据
介绍完这三个符号后,可以继续介绍语法(套路)了。
在解释声明时,首先要决定声明的名字p是什么东西,而和p最近的符号则决定了p是什么东西。如下面声明:
int p(); //p是函数
int *p; //p是指针
int p[5]; //p是数组
int *p[5]; //p是数组,里面的5个元素为指向int类型的指针
这里的第4行代码要解释一下,由于[ ]的优先级比星号 * 高,所以[ ]先作用于p,故p是一个数组,现在我们知道p是一个数组了,但还不知道数组中的元素是什么类型,这时,就要看左边的星号 * ,星号 * 声明了数组中的元素是指针类型。现在,我们知道了数组中的元素是指针类型,但还不知道那些指针是指向什么类型的,这时int发挥作用了,它声明了里面的指针是指向int类型数据的,所以这个声明的结果就是:声明了一个数组 ,数组有5个元素,每个元素都是指向整型数据的指针。
到这里,我们对这个语法(套路)有点头绪了!
原来解释声明就是要先从名字p开始,然后从p的右边开始看符号(因为优先级高的符号 [ ]和 ( ) 是放在右边的),如果有符号,刚和p先结合。看完右边的符号(如果有的话)后,就决定了p是什么。这时,就到p左边的符号发挥作用了(左边要么是 * ,要么就什么都没有)。最后发挥作用是则是类型说明符(因为它在最外面)。
总的来说,解释声明的套路就是不断问什么,然后从里往外看符号来解答什么的过程,说过无谓,再来看一段代码:
int *p(); //声明1,声明一个函数p,返回类型为指针类型
int (*p)(); //声明2,声明一个函数指针p,p指向一个函数
我们来按照套路来解释这两个声明
声明1:首先,要确定p是什么?从p右边的符号 ( ) 可以解得,p是一个函数。新的问题又来了,那函数p的返回值是什么类型?从p左边的星号 * 可以解得,函数p的返回值类型是指针类型。新的问题又来了,返回的指针指向的数据类型是什么啊?这时,按照套路,应该继续看外一层的右边(因为看符号的顺序为p的右左右左…,而且内层优先于外层),然而符号 ( ) 的右边没有符号了,转而看向外一层的左边。这时发现外一层的左边是类型说明符int,因此解得,返回的指针指向的数据类型是int。整个声明就是:声明p为一个函数,函数的参数为空,函数的返回值为指针,指针指向的数据类型为int。
声明2:由于这个声明中,有个括号把星号 * 和 p包住了,所以 * 和 p属于内层,而括号中p的右边没有符号,故 * 和p先结合,则声明了p是一个指针,这回答了p是什么。新的问题来了,p指向的是什么?这时,内一层的符号看完了,继续看外一层的右边,外一层的右边是符号( ) ,则解得p指向的是一个函数。然后再看外一层的左边得,该函数的返回值是int类型。整个声明就是:声明p是一个指针,这个指针指向一个函数,这个函数的返回值为int。
总结一下,解释声明的套路就是:先从最内层开始看符号,先从名字p的右边开始看,再到左边。然后跳到外一层的右边开始,再到外一层的左边开始看。不断循环,直到没符号为止。
下面把这个套路应用到更加复杂的声明中去。
如下面这个声明:
char (*(*x())[])();
有没有头晕的感觉?不用怕,按照套路来很简单的。
首先从最里面开始,x的右边是符号 ( ) ,所以x是一个函数。再看x的左边,x的左边是一个星号 * ,所以 函数x的返回值类型为指针类型。继续跳到外一层的右边,发现外一层的右边是符号 [ ] ,所以指针指向的是一个数组。继续看左边,发现左边是一个星号 * ,所以数组中的元素的类型为指针类型。再看外一层的右边,发现符号 ( ),所以数组中的指针指向的是函数。最后看左边的类型说明符char,所以函数的返回值类型为char类型。整个声明就是:x是一个函数,函数的返回值类型是指针类型,这个指针指向的是一个数组,这个数组是指针数组(里面的元素是指针),数组中的指针是函数指针(指针指向函数),指针指向的函数的返回值类型是char类型。
再看这个声明:
char (*(*x[3])())[5];
该声明的结果为:x是一个数组,数组有3个元素,元素的类型为指针类型,这些指针都是函数指针,所指向的函数的返回值为指针 ,返回的指针指向数组,指向的数组有5个元素,元素的类型为char类型。
到此为止,我们已经学会了复杂声明的语法(套路)。这时我们也可以利用这个语法来声明我们想要的变量。
2.3 使用复杂声明
现在我们来声明一些复杂的声明。
比如,我们来声明一个函数x,函数x的返回值为指针类型,该指针指向一个数组,数组的元素类型为char。
只要按套路反过来就行了:
1. 函数x:
x()
2.函数x的返回值为指针类型: 。
*x()
3.该指针指向一个数组:
(*x())[]
4.数组的元素类型为char:
char (*x())[];
搞定!是不是很简单?