指针和数组


2021年8月23日, Learn eTutorial
2254

在本教程中,您将学习如何使用指针来操作数组,即如何使用指针访问数组中的元素,以及如何遍历数组等,并通过一些简单的示例来帮助您理解。

要理解数组和指针之间的联系,最好提前了解 C 指针和 C 数组的基础知识。 

指针与一维数组

让我们首先检查一个简单的示例,该示例显示了如何打印一维数组中每个数组元素的地址。 


#include<stdio.h>

int main() {
    int arr[5] = {
        10,
        20,
        30,
        40,
        50
    };
    for (int i = 0; i < 5; i++) {
        printf("The address of element %d in position arr[%d]: %d\n", arr[i], i, & arr[i]);
    }
    return 0;
}
 

输出


The address of element 10 in position arr[0]: 6422016
The address of element 20 in position arr[1]: 6422020
The address of element 30 in position arr[2]: 6422024
The address of element 40 in position arr[3]: 6422028
The address of element 50 in position arr[4]: 6422032

在此示例中,我们声明了一个包含 5 个元素的数组:10、20、30、40 和 50。声明时,编译器会分配足够的内存来存储数组的元素。在这里,每个元素都连续存储在内存中,相邻元素之间有 4 个字节的差异,这表明了整数的字节大小。请参见下面的数组元素在内存中的可视化。

Array Pointer

仔细观察图像,您可能会找到“数组 arr 和 arr[0] 的地址是否相同?”这个问题的答案。答案是肯定的,因为数组 arr 始终指向第一个元素 arr[0],所以数组名是指向第一个元素地址的常量指针。更准确地说, 

Array Pointer

使用指针访问数组地址 

通常,为了获取数组元素的地址,我们会使用 **“&”** 符号。我们也可以使用 **指针“*”** 来获取数组元素的地址。让我们看看如何修改上述程序以使用指针获取数组元素的地址并产生相同的结果。


#include<stdio.h>

int main() {
    int arr[5] = {
        10,
        20,
        30,
        40,
        50
    };
    int * p;

    p = & arr[0];
    for (int i = 0; i < 5; i++) {
        printf("The address of element %d in position arr[%d]: %d\n", * p, i, p);
        p++;
    }
    return 0;
}
 

输出


The address of element 10 in position arr[0]: 6422016
The address of element 20 in position arr[1]: 6422020
The address of element 30 in position arr[2]: 6422024
The address of element 40 in position arr[3]: 6422028
The address of element 50 in position arr[4]: 6422032

我们知道指针的每个元素都有一个特定的地址。这里我们使用了指针并逐渐(使用 for 循环)增加其地址值来存储它们。在我们的示例中,我们有一个数组 arr[5] 和一个指针 p。我们可以这样使用它们:
p=&arr[0];
p+1=&arr[1];
p+2=&arr[2];
p+3=&arr[3];
p+4=&arr[4];
p+5=&arr[5];
同样,我们可以通过打印“*p”、“*(*p+1)”、“*(*p+2)”等的值来使用其中存储的值。
在使用数组指针时,我们需要牢记以下几点:

  1. 数组和指针的数据类型必须相同。 
  2. **数组名**可用于初始化指针,因为数组名指向数组第一个元素的地址。

指针算术(递增和递减)

指针支持算术运算,但仅限于加法(增量)和减法(减量)。对于整数,当我们通过单位增量一个数字时,存储变量的值会增加,例如 

4+1=5.

但在指针的情况下,增量意味着让指针指向下一个地址位置。也就是说,存储变量的值而不是地址值发生了变化,如下图所示。

Array Pointer

整数数据类型需要 4 个字节的内存,因此在这些情况下增量指针最终会将指针移动四个单位。同样,浮点指针会将地址值增加 4 个单位,因为它们每个都需要 4 个字节的空间来分配。 
在递减指针时,它会将地址值减少相应数据类型的字节数。指针会立即指向前一个地址位置。 

由于指针处理的是地址,因此乘法和除法的可能性完全不在考虑范围之内。 

指向数组的指针 

到目前为止,我们已经讨论了指向数组单个元素的指针。但是,当指针指向整个数组时,这是一个不同的概念。在 C 语言中,引用整个数组的指针被称为指向数组的指针。请参见下面的指向数组的指针的声明。


datatype (*p)[n]
 

其中 datatype 代表数组的类型,

  • p 是指向整个数组的指针。
  • n 是数组中的元素数量。

示例


int (*p)[5]
float (*p)[10]
 

需要考虑的关键点是“p”周围的圆括号是必需的,因为 int *p[5] 和 int (*p)[5] 是不同的。int *p[5] 表示一个包含 5 个整数指针的数组,而 int (*p)[5] 表示一个指向包含 5 个整数的数组的指针。


#include<stdio.h>

int main() {
    int arr[5];
    int * p;
    int( * pa)[5];

    p = arr;
    pa = arr;

    printf("Address of p = %d\n", p);
    printf("Address of pa = %d\n", pa);

    p++;
    pa++;

    printf("\n...After incrementing p and pa...\n\n");
    printf("Address of p = %d\n", p);
    printf("Address of pa = %d\n", pa);

    return 0;
}
 

输出


Address of p = 6422000
Address of pa = 6422000

...After incrementing p and pa...

Address of p = 6422004
Address of pa = 6422020

工作原理

当您检查示例时,您会看到 p 是一个指向数组 arr 第一个元素的指针,因此其基本类型是指向 int 的指针。另一方面,pa 是一个引用整个数组 arr 的指针,因此其基本类型是指向包含 5 个整数元素的数组的指针。当这两个指针递增时,它们相对于基本类型工作,因此 p 递增 4 个字节,而 pa 递增 20 个字节(5 x 4 = 20)。

指针与二维数组

到目前为止,我们已经看到了指针在一维数组上的工作原理。现在,让我们观察一下指针如何在二维数组上工作。要更好地理解二维数组,请参考我们之前的教程《数组》。

Array Pointer

我们知道二维数组可以简单地可视化为具有行和列的矩阵。但是,在内存方面,**二维数组可以被视为一维数组,其中每个元素本身也是一个一维数组**。这是因为在内存中,所有内容都是以线性方式存储的。上图展示了一个具有 2 行 3 列的二维数组的概念视图,而下图展示了一个具有 3 行 3 列的二维数组在内存中的实际视图。 

Array Pointer

从图中可以清楚地看出,在二维数组中,

arr[0] == arr : 是第 0 个一维数组 arr[1] == arr + 1 : 是第 1 个一维数组

arr[2] == arr +2 : 是第 2 个一维数组,依此类推。总的来说,

arr[i] == arr + i : 表示第 i 个一维数组,依此类推。 

总的来说,

arr[i] == arr + i : 表示第 i 个一维数组。 

如何使用指针访问二维数组的地址

以下示例显示了如何打印二维数组中每个元素的地址。
 


#include<stdio.h>

int main() {
    int arr[3][3]={10,20,30,40,50,60,70,80,90};
    int * p;

    p = arr;
    for (int i = 0; i < 3; i++) {
        printf("\n\nThe address of arr[%d]: %d\n", i, p);
        for (int j = 0; j < 3; j++) {
            printf("The address of element %d in position arr[%d][%d]: %d\n", * p, i, j, p);
            p++;
        }
    }
    return 0;
}
 

输出


The address of arr[0]: 6421984
The address of element 10 in position arr[0][0]: 6421984
The address of element 20 in position arr[0][1]: 6421988
The address of element 30 in position arr[0][2]: 6421992


The address of arr[1]: 6421996
The address of element 40 in position arr[1][0]: 6421996
The address of element 50 in position arr[1][1]: 6422000
The address of element 60 in position arr[1][2]: 6422004


The address of arr[2]: 6422008
The address of element 70 in position arr[2][0]: 6422008
The address of element 80 in position arr[2][1]: 6422012
The address of element 90 in position arr[2][2]: 6422016

在此示例中,为了获取二维数组中每个元素的地址,我们使用了指针变量 p。因此,在幕后,指针变量 p 实际上是这样工作的。

令我们的数组为 arr,(arr + i) 表示第 i 个一维数组。为了获取地址,我们可以直接在 (arr+i) 前面加上解引用运算符 (*)。即,

*(arr+i) 获取第 i 个一维数组的地址。 

更简洁地说,
*(arr + 0) ⇒ 第 0 个一维数组的地址
*(arr + 1) ⇒ 第 1 个一维数组的地址
*(arr + 2) ⇒ 第 2 个一维数组的地址

现在,为了访问每个数组中元素的地址,我们需要执行指针算术,如下所示:

*(arr + 0)+0 ⇒ 第 0 个一维数组的第 0 个元素的地址
*(arr + 0)+1 ⇒ 第 0 个一维数组的第 1 个元素的地址
*(arr + 0)+2 ⇒ 第 0 个一维数组的第 2 个元素的地址
*(arr + 1)+0 ⇒ 第 1 个一维数组的第 0 个元素的地址
*(arr + 1)+1⇒ 第 1 个一维数组的第 1 个元素的地址
*(arr + 1)+2 ⇒ 第 1 个一维数组的第 2 个元素的地址

这可以泛化为 

*(arr + i)+j⇒ 第 i 个一维数组的第 j 个元素的地址

也可以通过解引用 *(arr + i)+j 来访问数组中每个元素的值。即,

*(*(arr + i)+j)⇒ 第 i 个一维数组的第 j 个元素的值

上面的程序可以这样修改。 


#include<stdio.h>

int main() {
    int arr[3][3]={10,20,30,40,50,60,70,80,90};

    for (int i = 0; i < 3; i++) {
        printf("\n\nThe address of arr[%d]: %d\n", i, *(arr + i));
        for (int j = 0; j < 3; j++) {
            printf("The address of element %d in position arr[%d][%d]: %d\n", *( * (arr + i) + j), i, j, *(arr + i) + j);

        }
    }
    return 0;
}
 

这里,我们没有使用指针变量,而是直接在数组本身上使用指针来访问地址和值。