指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针
数组指针:a pointer to an array,即指向数组的指针
还要注意的是他们用法的区别,下面举例说明。
int* a[4] 指针数组

表示:数组a中的元素都为int型指针

元素表示:*a[i]   *(a[i])是一样的,因为[]优先级高于*
int (*a)[4]   数组指针     
表示:指向数组a的指针
元素表示:(*a)[i]

我们先看维基百科中对于数组指针的解释:
数组名出现在表达式中时,绝大多数情况(除了数组名作为sizeof的操作数或者作为取地址&元素符的操作数)会被隐式转换为指向数组的首个元素的指针右值。
当数组名作为取地址&元素符的操作数,则表达式的值为指向整个数组的指针右值。
例子:

char s[]="hello";

int main() {
  char (*p1)[6]=&s; //OK!
  char (*p2)[6]=s; //compile error: cannot convert 'char*' to 'char (*)[6]'   
  char *p3=&s;//compile error: cannot convert 'char (*)[6]' to 'char*' 
}
根据上述C语言标准中的规定,表达式 &s 的值的类型是char (*)[6],即指向整个数组的指针;而表达式 s 则被隐式转换为指向数组首元素的指针值,即 char* 类型。同理,表达式 s[4] ,等效于表达式 *(s+4)。
对于上面的内容中提到的“当数组名作为取地址&元素符的操作数,则表达式的值为指向整个数组的指针右值。”我们参考如下程序进行理解:
#include <stdio.h>
int main ()
{
    int a[5] = {1,2,3,4,5};
    int *p = (int*)(&a + 1);//&a表示整个数组的地址
    printf("%d %d\n" , *(a + 1), *(p - 1));
    printf("%p\n",&a);
    printf("%p\n",&a+1);
    return 0;
}
//输出结果为:
2 5
0x7ffc113aad80
0x7ffc113aad94

这是一道非常常见的C语言面试题。
0x7ffc113aad94-0x7ffc113aad80=0x14=20
因此可以判断&a表示整个数组,对&a+1将会偏移整个数组的大小。
a[0] 0x7ffc113aad80
a[1] 0x7ffc113aad84
a[2] 0x7ffc113aad88
a[3] 0x7ffc113aad8C
a[4] 0x7ffc113aad90
因此&a+1指向的是a[5],使用*(p-1)即可访问a[4]的元素。

#include <stdio.h>

int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    int e = 50;
    /*指针数组*/
    int *p[5]={&a,&b,&c,&d,&e};
    printf("a = %d\n",*p[0]);

    /*数组指针*/
    int arr[5] = {10,20,30,40,50};
    int (*ptr)[5] = &arr;
    printf("%d\n",(*ptr)[0]);
    return 0;
}