二级指针、一维数组与指针,二维数组与指针

2022-05-31 09:43:17 浏览数 (1)

二级指针

指针可以指向一个普通类型的数据,例如 int、double、char 等,也可以指向一个指针类型的数据,例如 int *、double *、char * 等。

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

假设有一个 int 类型的变量 age,page是指向 age 的指针变量,ppage 又是指向 page 的指针变量,它们的关系如下图所示:

将这种关系转换为C语言代码:

代码语言:javascript复制
int age =28;
int *page = &age;
int **ppage = &page;

指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*。page 是一级指针,指向普通类型的数据,定义时有一个*;ppage 是二级指针,指向一级指针 page,定义时有两个*

如果我们希望再定义一个三级指针 p3age,让它指向 ppage,那么可以这样写:

代码语言:javascript复制
int ***p3age = &ppage;

四级指针也是类似的道理:

代码语言:javascript复制
int ****p4age = &p3age;

实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。

想要获取指针指向的数据时,一级指针加一个*,二级指针加两个*,三级指针加三个*,以此类推,请看代码:

代码语言:javascript复制
#include <stdio.h>
int main()
{
   int a =100;
   int *p1 = &a;
   int **p2 = &p1;
   int ***p3 = &p2;
   printf("%d, %d, %d, %dn", a, *p1, **p2, ***p3);
   printf("&p2 = %#X, p3 = %#Xn", &p2, p3);
   printf("&p1 = %#X, p2 = %#X, *p3 = %#Xn", &p1, p2, *p3);
   printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#Xn", &a, p1, *p2, **p3);
   return 0;
}

一维数组指针

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:

定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:

数组下标为啥从0开始?

  • 数组下标实际上是每个元素的地址相对于第一个元素地址的偏移量

访问数组元素除了可以通过下标法之外,还可以通过指针法访问。

  • 下标法 for(int i = 0;i < 6;i ) { printf("%d ",arr[i]); //printf("%d ",i[arr]); }
  • 指针法 for(int i = 0;i < 6;i ) { printf("%d ",*(arr i)); } 这就是为什么在某些地方大家会看到 i[arr] 这种访问数组元素的方法的原因,实际上下标法就是通过指针法来实现的,只不过编译器帮助我们做了这个操作,简化了操作难度。

指向数组的指针

我们也可以定义一个指向数组的指针,例如:

代码语言:javascript复制
int arr[] = { 99, 15, 100, 888, 252 };
int *p = arr;

arr 本身可以看做是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

如果一个指针指向了数组,我们就称它为数组指针(Array Pointer)

数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是int *

反过来想,p 并不知道它指向的是一个数组,p 只知道它指向的是一个整数,究竟如何使用 p 取决于程序员的编码。

使用指针访问数组元素和使用函数名没有任何区别,值得注意的是我们不同通过指针获得数组的大小,但是通过数组名却可以。

代码语言:javascript复制
printf("%dn",sizeof(arr));     //数组所占字节数 20 Byte
printf("%dn",sizeof(p));       //指针所占字节数 4 Byte

也就是说,根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。

关于数组指针的谜题

假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p 、* p、(*p) 分别是什么意思呢?

*p 等价于 *(p ),表示先取得第 n 个元素的值,再将 p 指向下一个元素。

* p 等价于 *( p),会先进行 p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p 1),所以会获得第 n 1 个数组元素的值。

(*p) 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。

数组名和数组指针的区别

虽然说数组名可以当做指针使用,但实际上数组名并不等价于指针。

  • 数组名代表的是整个数组,具有确定数量的元素
  • 指针是一个标量,不能确定指向的是否是一个数组
  • 数组可以在某些情况下会自动转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址,类型就是数组元素的地址类型(通过sizeof也可以看出来)

二维数组指针

二维数组可以理解为每一个元素都是一个一维数组的数组,这样就可以很好的理解二维数组与指针了。

下面定义了一个2行3列的二维数组,并画出了对应的内存模型。

我们可以使用arr[0]获得第0个一维数组,然后再加上一个小标就可以获取到对应的元素,如arr[0][0]获取了第0行第0列的元素。

0 人点赞