跟着b站鹏哥学c语言学到了数组指针,感觉有点不好理解,所以在这里写一篇笔记。
数组指针,其本质是指针变量。其实就是指向数组的指针变量,用来存放一个数组的地址。其定义方式如下:
#include
int main()
{
//创建一个二维数组
int arr[3][5] = {1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15};
//定义一个数组指针
int (*p)[5] = arr;
return 0;
}
在之前我们通常用指针接收数组首元素(此处的指针是整型指针变量),这时的数组是一维数组。如下
#include
int main()
{
//创建一个一维数组
int arr[5] = {1,2,3,4,5};
//定义一个指针
int *p = arr;
return 0;
}
注意两种定义方式的区别:
int *p = arr; //方式一
int (*p)[5] = arr; //方式二
注意,在这里为什么不用方式一定义呢?实际上,试一下就知道,如果在接收二维数组的数组名时使用方式一的话,编译是不会通过的。
这就很奇怪了,为什么数组的维数会影响指针的类型呢?
这就涉及到另外一个知识点:二维数组元素问题。
这里参考我的另一篇博客:http://t.csdn.cn/g1WHD
我们知道数组名(此处为arr)在大多数情况下(有两种例外情况)代表数组首元素的地址。那么一个二维数组的首元素是什么呢?
一般来说我们会认为是这个数组的第一个元素(此处为1)。但其实二维数组可以看做是由行数(此处为3)个数组组成的数组,即二维数组的元素为每一行数组,每一行的数组的元素即为每一列的元素,个数为列数(此处为5)。
我知道这么说有点抽象,所以拿上述代码中的数组举一个例子就是:
[ 1 , 2 , 3 , 4 , 5
6 , 7 , 8 , 9 , 10
11 , 12 , 13 , 14 , 15 ]
这个二维数组的第一个元素是[1 , 2 , 3 , 4 , 5]这个数组,第二个元素是[6 , 7 , 8 , 9 , 10]这个数组,第三个数组是[11 , 12 , 13 , 14 , 15]。即此二维数组是由3个(行数个)数组组成的一个数组。
如果是一维数组的话,比如整型数组,因为里面的每个元素就是一个整型,所以用整形指针来接收合情合理。
但如过直接用整型指针变量 int *p 来接收此二维数组的首元素是不行的,即使这个二维数组中的每个元素也是整型。在这里二维数组的首元素也是一个数组,而不是一个单一的整型。所以就出现了数组指针,用来存放数组的地址。
到这里,我们就明白了为什要用数组指针了。下面来讨论数组指针的一些注意事项。
我们来测试以下代码:
#include
int main()
{
//创建一个二维数组
int arr[3][5] = {1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15};
//定义一个数组指针
int (*p)[5] = arr;
printf("%p\n",p);
printf("%p\n",*p);
return 0;
}
运行之后发现 p 和 *p 打印出的结果是一样的。这在当时让我产生了一个错觉,好像这俩是一个东西。但我们接着测试以下代码:
#include
int main()
{
//创建一个二维数组
int arr[3][5] = {1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15};
//定义一个数组指针
int (*p)[5] = arr;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("--------------\n");
printf("%p\n", *p);
printf("%d\n", *p + 1);
return 0;
}
这时我们发现 p + 1 的结果和 *p + 1 的结果不一样。在我的电脑上运行的结果如下:
我们发现 p + 1 的地址和 p 的地址差了 E4-D0 个字节,换算为十进制为20。而在下面的 *p + 1 的地址和 p 的地址差了 D4-D0 个字节,换算为十进制为1个字节。
为什么会出现以上的差异呢?
首先我们要搞懂,为什么是20呢?这是因为一个整型占4个字节,而这个二维数组的一个数组元素中正好有5个整型,所以下一个数组元素与上一个数组元素正好差了 4*5 = 20 字节个地址位数。
同理,为什么是4呢?因为一个整型正好就占4个字节,所以下一个整型元素与上一个整型元素就正好差了 4*1 = 4 个地址位数。
理解到这里,我们就可以看出 p 和 *p 的区别了:
p 这个数组指针指向的是二维数组中一个一维数组元素(比如[1,2,3,4,5])的整个数组的地址。所以在对 p 进行 +1 操作的时候,会跳过这一整个一维数组,指向下一个一维数组元素。
而 *p 指向的就是这个一维数组的首元素的地址(比如[1,2,3,4,5]中的1),也就是指向了 1 这个整型的地址。所以在对 *p 进行 +1 操作的时候,仅会跳过一个整型元素,指向这个一维数组中的下一个整型的地址。
从数组指针的定义上其实也好理解:p存放的就是二维数组中一维数组的地址,那么对这个一维数组的地址解引用,就得到了这个一维数组的首元素(整型)的地址。此时的 *p 指向的是整型的地址。而 p 指向的是这个一维数组的地址。只不过此时这个一维数组的地址和这个一维数组首元素的地址都等于这个首元素(整型)的地址。虽然在数值上相等,但意义完全不同。看到这里,有没有想到什么?没错,这就很像单独的数组名和&数组名的区别。
我们加入以下代码测试:
printf("%p\n", *(p + 1));
发现结果如下:
这种写法和 p + 1 的结果相同,但注意此处进行了解引用,所以此时的 *(p + 1) 指向的是第二个一维数组([6,7,8,9,10])的首元素(6)的地址,也就是说指向的是一个整型的地址。虽然数值相等,但意义不同。也就是整个数组的地址和数组首元素地址的区别。
到这里我们应该就充分理解 p 和 *p 的区别了。这也有助于我们理解以下的代码:
现在我们想实现打印二维数组的每一个整型元素:
void print_arr(int (*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for(i = 0; i < r; i++)
{
for(j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5, 6,7,8,9,10, 11,12,13,14,15};
int (*p)[5] = arr;
print_arr(arr, 3, 5);
return 0;
}
这里定义了一个函数,用来打印一个二维数组。我们仔细观察一下这行代码:
printf("%d ", *(*(p + i) + j));
p + i 代表要打印的行,*(p + i) 就指向了这一行的首元素,也就是指向了这个整型的地址。那么我们对这个整型的地址进行 + j 操作,就指向了这个一维数组中下标为 j 的整型的地址,并再对这个地址解引用,我们就顺利得到了对应的元素。
看到这里,我们不禁想到了二级指针的知识,不就是存地址的地址吗,用一个指针来存放另一个指针的地址不就行了吗?
但我们要注意到此处的指针指的是指针变量,但数组名并不是指针变量,所以也就不能用二级指针。到这里我们就对数组指针的存在意义又有了新的理解。
大功告成!
p 代表二维数组某个一维数组(这个一维数组是一个整体)的地址,等于这一行数组的首元素的地址,但并不等价。
对 p 解引用(*p)得出的是二维数组这一行的数组首元素的地址,再对 *p 解引用 *(*p) 得出的即是这一行数组的元素。当于 数组名和 &数组名的区别,即 p 相当于&数组名(某一行数组的整个数组的地址),*p 相当于数组名(某一行数组的首元素的地址) 。
以上就是数组指针的理解了。