首页 > 编程笔记 > C语言笔记 阅读:33

深度剖析C语言指针和数组的关系(新手必看)

学习C语言数组时,我们学习了使用下标来访问数组元素。看一个使用下标访问数组元素的示例。
#include <stdio.h>
int main()
{
    int arr[5] = {111, 222, 333, 444, 555}; // 定义一个包含5个整数的数组
    printf("%d\n", arr[0]);    // 第1个元素
    printf("%d\n", arr[1]);    // 第2个元素
    printf("%d\n", arr[2]);    // 第3个元素
    printf("%d\n", arr[3]);    // 第4个元素
    printf("%d\n", arr[4]);    // 第5个元素
    return 0;
}
由于数组元素在内存中的存储是连续的,因此第一个元素的首地址就是整个数组的首地址。

我们可以使用取地址运算符 & 来获取第一个元素的首地址以及空间大小,从而获得一个 int* 类型的指针。例如:
int *p = &arr[0];    // 从第 1 个元素获取数组首地址
p;                   // 指向第 1 个元素
p + 1;               // 指向第 2 个元素
p + 2;               // 指向第 3 个元素
p + 3;               // 指向第 4 个元素
p + 4;               // 指向第 5 个元素
通过取值运算符 *,我们可以使用指针中的首地址和空间大小来访问或修改数组元素。具体代码如下:
#include <stdio.h>
int main()
{
    int arr[5] = {111, 222, 333, 444, 555};
    int *p = &arr[0];
    printf("%d\n", *p);        // 第1个元素
    printf("%d\n", *(p + 1));  // 第2个元素
    printf("%d\n", *(p + 2));  // 第3个元素
    printf("%d\n", *(p + 3));  // 第4个元素
    printf("%d\n", *(p + 4));  // 第5个元素
    return 0;
}
这段代码的主要目的是通过指针操作来访问和输出数组中的元素。它首先定义了一个整数数组 arr 并初始化了 5 个整数值,然后创建了一个整型指针 p 并将其指向数组的首地址(即第一个元素的地址),最后使用了指针运算访问并输出了数组中的每个元素。

在 printf() 函数中,通过对指针 p 进行加法操作,指针会根据数组元素的步长进行移动。例如,*(p + 1) 将访问数组中的第二个元素,*(p + 2) 将访问第三个元素,以此类推。最后,该程序输出数组中的所有元素。运行结果为:

111
222
333
444
555

注意,表达式 p + 1 必须先被括号包括,再使用取值运算符 *。这是因为取值运算符 * 的优先级高于算术运算符。我们需要先让首地址移动,再进行取值操作。若不使用括号,*p 会先被取值,之后值再被加 1。

不使用括号,*p 的值为 111,*p + 1 的结果为 112。使用括号,(p + 1) 使得首地址移动到第二个元素,*(p + 1) 得到结果为 222。

C语言有一种更加简便的方法来获取数组的首地址,那就是直接使用数组的名字表示数组的首地址。所以当你使用数组名时,实际上就是获取了数组的首地址。

下面是一个使用数组名获取数组首地址的示例。
#include <stdio.h>
int main()
{
    int arr[5] = {111, 222, 333, 444, 555};
    printf("arr = %llu\n", arr);
    printf("&arr[0] = %llu", &arr[0]);
}
运行结果为:

arr = 6487552
&arr[0] = 6487552

&arr[0] 的结果为一个指向数组第一个元素的指针,其值 6487552 为第一个元素的首地址,而数组名 arr 的值同样也是首地址。

由于值是相同的,因此我们可以尝试将数组名赋值给一个指针类型,具体代码如下:
#include <stdio.h>
int main()
{
    int arr[5] = {111, 222, 333, 444, 555};
    // 使用数组名初始化 int*指针
    int *p = arr;
    printf("%d\n", *p);
    printf("%d\n", *(p + 1));
    printf("%d\n", *(p + 2));
    printf("%d\n", *(p + 3));
    printf("%d\n", *(p + 4));
    return 0;
}
程序在 Visual Studio 中可以成功编译,并且能够访问数组中各个元素的值。

那么,有的同学可能会认为数组名的类型就是一个指向元素的指针。为了验证这个猜想,我们使用 sizeof 测量数组名的大小。如果数组名是一个指针,那么它的大小在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。

程序用 64 位系统进行编译。运行结果为:
#include <stdio.h>
int main()
{
    int arr[5] = {111, 222, 333, 444, 555};
    int *p = arr;
    printf("sizeof arr = %d\n", sizeof(arr));
    printf("sizeof p = %d\n", sizeof(p));
    printf("sizeof arr + 1 = %d\n", sizeof(arr + 1));
    return 0;
}
运行结果为:

sizeof arr = 20
sizeof p = 8
sizeof arr + 1 = 8

可以看出 arr 的大小为 20,p 的大小为 8,arr + 1 的大小为 8。

这段程序演示了 C语言中指针和数组的一些概念,接下来我们对其进行详细分析:
首先,p 是一个指针,通过前面的学习我们知道了 64 位系统的指针大小是 8 字节,这个结果是毫无疑问的;

其次,arr 的大小为 20,因为它是一个包含 5 个整数的数组。整数(int 类型)占用 4 字节。因此,arr 的大小是 5 个整数,每个整数占用 4 字节,总共是 5 × 4 = 20 字节。

最后,arr + 1 的大小是 8,因为这里涉及一个指针运算。arr 是一个数组,在表达式中被解释为指向数组第一个元素的指针。当你对数组名执行加法操作时,结果是一个指针。在 64 位系统上,指针占用 8 字节。因此,在这种情况下,sizeof(arr + 1) 计算的是一个指针的大小,而不是数组本身的大小。这就是为什么 sizeof(arr + 1) 的值为 8。

这里有一个重要的知识点要强调一下,当数组名 arr 出现在一个表达式当中,数组名 arr 将会被转换为指向数组第一个元素的指针。但是,这个规则有两个例外:
也就是说,数组名 arr 的类型其实是 int [5],因此 sizeof(arr) 的结果是 20。数组名 arr 出现在表达式 int *p = arr 中,会被转换为指向数组第一个元素的指针,即 int [5] 被转为 int * 类型,之后将转化后的指针赋值给指针变量 p。arr + 1 也是一个表达式,数组名 arr 被转换为 int * 类型,并进行加法运算后,仍然为 int * 类型。

C语言指针和数组的关系

通过前面的学习,我们知道了指针和数组之间存在密切的关系,接下来我们对其进行总结。

1、数组名会被转换为指针

当你声明数组时,数组名在表达式中会被转换为一个指向数组第一个元素的指针,这意味着数组名的值是数组第一个元素的地址。

2、指针运算

我们可以对指针进行算术运算,如加法和减法。这可以用来遍历数组。例如,pArr + 1 将指向数组中的下一个元素,而 pArr - 1 将指向上一个元素。指针运算会考虑所指向的数据类型的大小,以确保正确地进行遍历。

3、数组和指针的互换

在 C 语言中,数组和指针可以在很多情况下互换使用。下面用实际的例子来说明它们之间的关系和相互调用的方式。

假设我们有如下整数数组:
int arr[] = {1, 2, 3, 4, 5};
数组名 arr 在表达式中可以被转换为一个指向数组第一个元素的指针。因此,我们可以声明一个指针 p,让它指向数组的第一个元素。
int *p = arr;
现在,我们可以使用指针 p 来访问和操作数组元素。以下是一些使用指针和数组名访问数组元素的例子。

1) 使用数组名和下标访问元素。
printf("arr[2] = %d\n", arr[2]);  // 输出:arr[2] = 3

2) 使用指针和偏移访问元素。
printf("*(p + 2) = %d\n", *(p + 2));  // 输出:*(p + 2) = 3

3) 使用数组名和偏移访问元素。
printf("*(arr + 2) = %d\n", *(arr + 2));  // 输出:*(arr + 2) = 3

4) 使用数组表示法访问指针所指向的连续内存空间。
printf("p[2] = %d\n", p[2]);  // 输出:p[2] = 3

在上面的例子中,我们使用了数组名 arr 和指针 p 以不同的方式访问数组元素。需要注意的是,数组名和指针虽然在很多情况下可以互换使用,但在本质上是不同的。

现在我们学会了访问数组元素的两种办法:数组名[下标]*(数组名 + 偏移量)。其中,偏移量就是指针指向的地址与数组首地址之间相差几个元素。

例如,要访问第 2 个元素,可以使用 数组名[1]*(数组名+ 1);要访问指针指向的数据,可以使用 *(指针名 + 偏移量)指针名[偏移量];要访问指针移动后 2 字节的数据,可以使用*(指针名 + 2)指针名[2]

通过这些例子,我们可以看到指针和数组在很多情况下可以相互替换。但是,我们应该明确它们之间的区别,以避免在编程时产生错误。总结如下:
理解这些概念后,你将能够灵活地运用指针和数组来编写高效且易于理解的代码。在实际编程中,合理地运用指针和数组有助于简化代码结构,提高程序的执行效率。

相关文章