数组和指针

1. 操作数组元素

在C语言中,数组名实际上就是一个指向数组首元素的指针。换句话说,可以把数组名视为指向了数组的第一个元素的内存地址。

例如,对于一个整型数组 int arry[5] = {1, 2, 3, 4, 5},我们可以通过数组名 arry 或者通过取指针操作符 &arry[0] 来获取指向数组第一个元素的指针。数组名字是数组的首元素地址,但它是一个常量

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main()
{
int arry[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("arry = %p\n", arry);
printf("&arry[0] = %p\n", &arry[0]);
arry = 10; //error, 数组名只是常量,不能修改
return 0;
}

使用指针操作数组可以通过以下几种方式进行:

  1. 指针访问数组元素:可以使用指针来直接访问数组的元素,使用指针解引用操作符 * 和数组下标来获取或修改对应的元素值。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <stdio.h>

    int main()
    {
    int arr[5] = { 1, 2, 3, 4, 5 };
    int* ptr = arr; // 将指针 ptr 指向数组的第一个元素

    // 使用指针访问数组元素
    // 输出第一个元素的值,即 1
    printf("value = %d\n", *ptr);
    // 输出第三个元素的值,即 3
    printf("value = %d\n", *(ptr + 2));

    // 修改数组元素的值, 将第二个元素的值修改为 10
    *(ptr + 1) = 10;
    // 输出修改后的值,即 10
    printf("value = %d\n", arr[1]);

    return 0;
    }
  2. 指针遍历数组:可以使用指针进行遍历数组,通过指针的自增操作来访问下一个元素。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>

    int main()
    {
    int arr[5] = { 1, 2, 3, 4, 5 };
    int* ptr = arr; // 将指针 ptr 指向数组的第一个元素

    // 使用指针遍历数组并输出所有元素
    for (int i = 0; i < 5; i++)
    {
    // 输出当前指针指向的元素
    printf("%d ", *ptr);
    // 将指针指向下一个元素
    ptr++;
    }
    printf("\n");

    return 0;
    }

需要注意的是,在使用指针操作数组时,需要确保指针和数组类型匹配,以避免类型错误或未定义行为。同时,还需要注意指针指向的位置在数组的有效内存范围内,避免访问越界或非法内存

2. 指针加减运算

指针加减法运算在编程中经常用于遍历数组、访问数组元素或定位指针在内存中的位置。指针加减法的结果取决于所操作指针的数据类型。

2.1 加法运算

指针加法允许我们将指针与一个整数相加。加法运算将根据指针所指向的数据类型来调整指针的位置。

  • 如果是一个int *,+1的结果是增加一个int的大小
  • 如果是一个char *,+1的结果是增加一个char大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int* p = a;
printf("%d\n", *p);
p += 2; //移动了2个int
printf("%d\n", *p);

char b[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
char* p1 = b;
printf("%c\n", *p1);
p1 += 2; //移动了2个char
printf("%c\n", *p1);

return 0;
}
  • 第8行 p += 2:从数组起始地址向右移动了2个int,也就是8个字节
  • 第14行 p1 += 2:从数组起始地址向右移动了2个char,也就是2个字节

由上可知,指针在参与算术运算的时候如果进行了加法操作,需要先确定指针的类型,然后通过sizeof(类型) * 右移的大小进行计算,得到的才是移动的总字节数,如果是减法就不是右移而是指针左移了。

对于数组而言,通过对指针进行算术运算也可以实现对它的正序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);

int* p = a;
for (i = 0; i < n; i++)
{
printf("%d, ", *p);
p++;
}
printf("\n");

return 0;
}

在上面的代码中使用指针p指向了数组的起始地址,在遍历过程中每次进行p++操作,相当于每次右移一个元素,又因为数组中的元素是整形,所以指针自增一次右移的字节数为 4。

2.2 减法运算

指针减法允许我们将指针和另一个指针或者一个整数相减。减法运算的结果将是两个指针之间的元素个数或字节数(根据数据类型而定)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int* ptr1 = &(arr[4]);
int* ptr2 = &(arr[0]);

int count = ptr1 - ptr2;
printf("count = %d\n", count);

int bytes = ptr1 - ptr2;
printf("bytes = %zd\n", bytes * sizeof(int));

return 0;
}
  • 第8行:计算两个指针之间的元素个数,即 4
  • 第12行:计算两个指针之间的字节数,根据数据类型求出最终的结果
    • 需要让得出的数量 * 类型所占的内存大小

对于数组而言,通过对指针进行算术运算也可以实现对它的逆序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);

int* p = a + n - 1;
for (i = 0; i < n; i++)
{
printf("%d, ", *p);
p--;
}
printf("\n");

return 0;
}
  • 第9行:指针指向数组的最后一个元素
  • 第13行:每遍历一次使指针左移一个元素

3. 指针数组和数组指针

指针数组和数组指针是两个概念上的不同,它们分别表示不同的数据结构。

3.1 指针数组

指针数组是一个数组,其元素为指针类型。换句话说,指针数组存储的是多个指针,每个指针可以指向不同类型的数据或对象。可以通过索引访问指针数组的元素,然后再通过解引用操作符 * 获取指针所指向的值。

下面是一个示例,展示了如何定义和使用指针数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main()
{
int num1 = 10;
int num2 = 20;
int num3 = 30;

// 声明指针数组,存储指向 int 类型数据的指针
int* arr[3];
arr[0] = &num1; // 将指针指向变量 num1
arr[1] = &num2; // 将指针指向变量 num2
arr[2] = &num3; // 将指针指向变量 num3

// 通过指针数组访问存储的指针并输出对应的值
for (int i = 0; i < 3; i++)
{
printf("%d ", *arr[i]);
}
printf("\n");

return 0;
}

在上述示例中,我们首先定义了一个指针数组 arr,它可以存储指向 int 类型数据的指针。然后,我们将数组的各个元素依次指向不同的变量 num1num2num3。最后,通过遍历指针数组,我们可以通过解引用操作符 * 获取指针所指向的值,并将其输出。

3.2 数组指针

数组指针是一个指针,它指向一个数组。换句话说,数组指针存储的是数组的地址,而不是数组元素的指针。通过对数组指针使用解引用操作符 *,可以获取指向整个数组的指针,通过索引操作符 [] 可以访问数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main()
{
int arr[3] = { 10, 20, 30 };
int(*ptr)[3];
ptr = &arr;

for (int i = 0; i < 3; i++)
{
printf("%d ", (*ptr)[i]);
}
printf("\n");

return 0;
}
  • 第6行:定义数组指针,该指针指向 int[3] 类型的数组

  • 第7行:给指针赋值,将数组指针指向数组 arr

    • 使用数组名字得到的指针是 int* 类型
    • 对数组名进行取地址操作,得到的指针类型是int(*)[3]
  • 第11行:通过数组指针访问数组中的元素的值

    • 使用 * 对 ptr 进行解引用得到 *ptr,它等价于数组名 arr
    • 通过 (*ptr)[i] 访问数组中对应位置的元素,它等价于 arr[i]

在上述示例中,指针数组通过存储指针来实现,每个指针可以指向不同类型的数据。而数组指针通过存储数组的地址来实现,通过解引用操作符和索引操作符,可以访问整个数组的元素

4. 二维数组和指针

要使指针指向二维数组,可以使用数组指针或者指针数组的方式。这取决于你想要处理的具体情况。

  1. 使用数组指针(Array Pointer)
    定义一个指向二维数组的指针,示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <stdio.h>

    int main()
    {
    int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    int(*ptr)[4];
    ptr = arr;

    // 通过数组指针访问二维数组的元素
    for (int i = 0; i < 3; i++)
    {
    for (int j = 0; j < 4; j++)
    {
    printf("%d ", ptr[i][j]);
    }
    printf("\n");
    }

    return 0;
    }
    • 代码的第 11 行,定义了一个数组指针 ptr,并指向 int[4] 类型的数组
    • 代码的第 12 行,将指针指向二维数组 arr
  2. 使用指针数组(Pointer Array)
    定义一个指针数组,每个指针都指向一维数组,示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <stdio.h>

    int main()
    {
    int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    int* ptr[3];
    for (int i = 0; i < 3; i++)
    {
    ptr[i] = arr[i];
    }

    // 通过指针数组访问二维数组的元素
    for (int i = 0; i < 3; i++)
    {
    for (int j = 0; j < 4; j++)
    {
    printf("%d ", ptr[i][j]);
    }
    printf("\n");
    }

    return 0;
    }
    • 代码第11行:定义一个指针数组,元素为指向 int 类型数据的指针
    • 代码第14行:每个指针指向二维数组 arr 的一维子数组

以上示例展示了如何定义并使用数组指针或指针数组来让指针指向二维数组。通过数组指针可以通过双重索引访问二维数组的元素,而指针数组需要先将每个指针指向二维数组的一维子数组,然后通过双重索引来访问元素。根据具体的使用场景和需求,选择适合的方式即可。

5. 多级指针

C语言允许有多级指针存在,多级指针是指指针的指针,也就是指向指针的指针。多级指针可以用于处理和修改指针本身的值,或者用于处理二维或多维的数据结构。

在实际的程序中一级指针最常用,其次是二级指针,二级指针就是指向一个一级指针变量地址的指针,三级及以上指针就很少能用得到了。下面是一个示例,展示了二级指针的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() 
{
int num = 10;
int* ptr = &num; // 一级指针指向变量 num
int** ptrPtr = &ptr; // 二级指针指向一级指针

printf("Value of num: %d\n", num);
printf("Value of *ptr: %d\n", *ptr);
printf("Value of **ptrPtr: %d\n", **ptrPtr);

**ptrPtr = 20; // 修改 num 的值为 20
printf("New value of num: %d\n", num);

return 0;
}

在上述示例中,我们首先定义了一个整型变量 num,然后定义了一个指向 num 的一级指针 ptr,最后定义了一个指向一级指针的二级指针 ptrPtr。我们可以通过多级指针来访问 num 的值,使用解引用操作符 * 多次解引用指针来获取最终的值。

在示例的最后部分,我们修改了 **ptrPtr 的值为 20,这实际上是修改了 num 的值。因为 ptrPtr 指向 ptr,而 ptr 指向 num,所以通过 **ptrPtr 可以修改 num 的值。输出结果验证了 num 的值确实改变了。

多级指针在某些情况下非常有用,例如在动态内存分配、函数调用和二维数组等场景中。需要注意的是,在使用多级指针时要小心确保正确初始化和使用,并避免出现空指针或非法内存访问等问题。