前面我们已经知道了指针是用来存储变量地址的变量。我们也可以定义另外的一个指针,用来存储某一个指针的地址,也就是指针的指针。定义指针的指针同定义单纯指针的方法很类似,只要在原有的星号前再多加上一个星号就可以,比如int **pointer.在这里首先我们要搞清楚一个关系:变量中存储的是某一个数据,指针当中存储的是变量的地址,而指针的指针存储的是指针的地址,当有如下定义时:
                       int var=2;
                       int *pointer;
                       int **ptr_to_ptr;
那么:
pointer=&var,  *pointer=var=2,  *ptr_to_ptr=&pointer,  **ptr_to_ptr=pointer=&var;

每学习一样新内容的时候,我们都要问问这项新技能可以帮助我们做些什么,指针的指针也不例外。这便引出了另外一个概念,也就是指针数组。从字面意思看,指针数组就是存储了一组地址连续的指针的数组。那么指针数组又是做什么的?有些时候我们需要处理比较大量的字符串,当对字符串进行处理的时候,计算机是很难识别每个字符串之间的相互关系的,假如程序存在大量字符串的移动,那将会更大程度的造成混乱。所以我们把每一句字符串的第一个字母的地址都存放在一个指针里,当系统对字符串进行调用或者排序的时候,只要移动相应的指针就可以,不需要执行大规模的字符串,这很大程度上提高了程序的效率。而每个存储字符串首地址的指针又都存储在一段相互连续的内存地址中,也就组成了一个指针数组,指向这个数组首地址的指针,便是指针的指针。

在程序中对于数组的调用是一件棘手的问题,指针的使用则解决了这个难题,我们可以提前定义一个指向数组首地址的指针,因为数组元素是连续存放的,所以找到首地址有助于进一步找到其他元素。当数组被调用时,只需调用指针而不需要调用整个数组,事实上,对于函数来说,也不具备调用整个数组的功能,这就使得指针在数组同程序和函数之间起到了一个桥梁作用,而且即使在一些可以直接调用或更改数组出现顺序的环境下,单纯的调用指针也要比调用或移动整个数组容易得多,这在相当程度上提高了程序的效率。

对于一个数组a[],我们知道a可以表示指向该数组的指针,我们同样也可以用a[]来表示指向二维数组a[][]的指针,于是我们得出以下结论:
——数组名后带n对方括号时,为数组的数据;
——数组名后带的方括号少于n对时,为一个指向数组元素的指针。
这并不难理解,不过还是给出一个简单的例子来说明一下:
#include <stdio.h>
int multi[2][4]
int main(void)
{
    printf(”\nmulti = %u”,multi);
    printf(”\nmulti = %u”,multi[0]);
    printf(”\nmulti = %u”,&multi[0][0]);
  }
这个例子很好的说明了多维数组间的关系,当然因为硬件环境的不同,每个人的结果都会不尽相同,但输出的三个值都是一样的。
n维数组的元素是n-1维数组,当n为1时,则元素为数组声明的开头指定的类型的变量。但有一点,用数组名虽然可以表示指向数组的指针,但由于数组名是指针常量,无法修改,所以我们要声明一个真正的指针来指向数组的首地址,假如有二维数组  
                              int multi[2][4];
要声明一个名为ptr,且能够指向multi数组的元素(即能够指向包含4个int变量的数组)的指针,可以这样做:
                              int(*ptr)[4];
之所以给*ptr加上圆括号,因为方括号优先级高于星号,如果这样编写代码
                              int*ptr[4];
则声明的是一个数组,该数组包含4个int指针。
 
最后我们看看指向函数的指针。