C语言中的指针在数组上的应用详解

0. 前言

📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

在C语言中,指针和数组是密不可分的两个概念数组名本质上就是一个指向其第一个元素的常量指针,而指针则可以用来遍历数组、操作数组内容,甚至模拟多维数组等。

理解指针与数组之间的关系,对于掌握C语言编程的核心技能至关重要。本文将系统地讲解指针在数组上的各种应用,包括:

  • 指针访问数组元素
  • 使用指针遍历数组
  • 指针与字符串(字符数组)
  • 二维数组与指针数组

1. 基本概念回顾

1.1 数组的本质

在C语言中,数组是一块连续的内存区域,用于存储相同类型的数据。

int arr[5] = {1, 2, 3, 4, 5};

此时 arr 是一个包含 5 个整型元素的数组。

1.2 指针的本质

指针是一个变量,它保存的是某个数据对象的地址。

int *p = arr; // p 指向数组 arr 的第一个元素

此时 p == &arr[0],也就是说,p 指向数组的第一个元素。

2. 指针访问数组元素

通过以下指针访问数组元素的示例,可以说明指针与数组下标等价性:

#include<stdio.h>

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

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

	printf("**************************\n");

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

	return 0;
}

输出结果:

在这里插入图片描述

  • *(p + i) 等价于 arr[i]
  • 这种方式通过指针移动来访问数组元素。

3. 使用指针遍历数组

指针不仅可以访问单个元素,还可以高效地遍历整个数组, 示例如下:

#include<stdio.h>

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

	while (p < &arr[4])
	{
		printf("arr[%d]=%d\n",p-&arr[0],*p );
		p++;
	}

	return 0;
}

输出结果:

在这里插入图片描述

4. 指针与字符串(字符数组)

在C语言中,字符串本质上是一个字符数组并以 \0 结尾

#include<stdio.h>

int main()
{
	char str[] = "hello";
	char str2[] = { 'w','o','r','l','d' };   //末尾没加'\0',输出乱码
	char str3[] = { 'w','o','r','l','d','\0'};  //末尾要加'\0'
	printf("%s", str);  //%s 需要一个 char* 指针
	printf("\n");
	printf("%s", str2);
	printf("\n");
	printf("%s", str3);
}

输出为:

在这里插入图片描述
字符串常量(如 "Hello")实际上是一个指向其第一个字符的指针。示例如下:

#include<stdio.h>

int main()
{
	const char* str = "hello";  //不写const会报错
	char str2[] = "world";
	printf("str:%s\n", str);
	printf("str2:%s\n", str2);

	return 0;
}

输出为:

在这里插入图片描述

这里最重要的区别是char *str 必须写成const char *str,表示常量不可修改。

在这里插入图片描述

类型是否可修改特点
char *str❌ 不可修改指向字符串常量,尝试修改会出错
char str[]✅ 可修改实际上是一个字符数组

通过指针也可以操作字符串,示例如下:

#include<stdio.h>

int main()
{
	char str[] = "Programming";
	char* p = str;

	while (*p != '\0')  // '\0'是结尾符
	{
		printf("%c", *p);
		p++;
	}
}

输出结果:

在这里插入图片描述

5. 多级指针与二维数组

5.1 二维数组

二维数组在内存中是按行优先顺序存储的,也可以用指针访问。示例如下:

#include <stdio.h>

int main() {
    int matrix[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    int (*p)[3] = matrix; // p 是指向含有 3 个整数的数组的指针

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

    return 0;
}
  • p是一个指向包含3个整数的数组的指针(即指向二维数组的一行)。

  • p + i将指针p移动i行(因为p的类型是int(*)[3],所以指针算术会以行为单位)。

  • *(p + i) 解引用这个指针,得到第i行的数组(相当于matrix[i])。

  • *(p + i) + j 现在是一个指向第i行第j列的指针(因为 *(p + i) 是一个int[3]数组,它会退化为指向其首元素的指针)。

  • *(*(p + i) + j) 最后解引用这个指针,得到第i行第j列的实际整数值(相当于matrix[i][j])。

5.2 指针数组(难点)

有时我们需要处理多个字符串或不同长度的一维数组,这时可以使用指针数组。

#include <stdio.h>

int main() {
    // 定义一个指针数组colors,包含3个指向字符串常量的指针
    // "red", "green", "blue"是存储在只读内存区的字符串常量
    char *colors[] = {"Red", "Green", "Blue"};


    // 定义一个二级指针p,指向colors数组的首元素
    // 由于colors数组的元素是char*类型,所以p的类型是const char**
    char **p = colors;

    for(int i = 0; i < 4; i++) {
    	// *(p + i) 等价于 colors[i],即第i个字符串的地址
        printf("colors[%d]: %s\n", i, *(p + i)); 
    }   

    return 0;
}

输出结果:

在这里插入图片描述

关键点解析

  1. const char* colors[]

    • 这是一个指针数组,每个元素是一个指向字符串常量的指针。
    • const 表示这些字符串不可修改(存储在只读内存区)。
  2. const char** p = colors

    • colors 是数组名,(在大多数情况下)会退化为指向首元素的指针(即 &colors[0])。
    • 由于 colors[0]const char* 类型,所以 p 的类型是 const char**(指向 const char* 的指针)。
  3. *(p + i)

    • p + i 是指针算术运算,相当于 &colors[i]
    • *(p + i) 解引用后得到 colors[i],即第 i 个字符串的地址。

5.3 二维数组与指针数组对比

在 C 语言中,字符串数组还可以通过二维数组的方式进行初始化:

#include <stdio.h>

int main() {
    // 方式1:显式指定每个字符串(二维数组)
    char colors1[3][6] = {  // 每个字符串最长5字符(+1给'\0')
        "red",
        "green",
        "blue"
    };

    // 方式2:不指定行数,自动推断
    char colors2[][6] = {
        "red",
        "green",
        "blue"
    };

    // 访问示例
    printf("%s\n", colors1[0]); // 输出: red
    printf("%s\n", colors2[1]); // 输出: green

    return 0;
}

特点

  • 每个字符串占用固定长度(如 char colors1[3][6] 表示 3 个字符串,每个最多 5 字符 + '\0')。
  • 如果字符串长度不同,会浪费空间(如 "red" 只占 4 字节,但会占用 6 字节)。

两种方式对比如下:

方式示例特点
二维数组char colors[3][6] = { "red", "green", "blue" };固定长度,可能浪费空间
指针数组char *colors[] = { "red", "green", "blue" };不可修改,但节省内存

推荐:

  • 如果字符串不需要修改,用 指针数组char *colors[])。
  • 如果字符串需要修改,用 二维数组 或动态分配内存。

6. 函数传参中的指针与数组

在C语言中,函数参数传递数组时,实际上传递的是数组的首地址(即指针)。

#include<stdio.h>


void PrintArray(int* arr, int size) 
{	for(int i = 0; i < size; i++) {
		printf("%d", *(arr + i));
	}
	printf("\n");
}


int main() {
	int arr[] = { 1,2,3,4,5 };
	PrintArray(arr, 5);

	return 0;
}

输出结果:

在这里插入图片描述

  • 函数内部无法获取数组长度,必须手动传入。
  • 函数形参写成 int arr[]int *arr 都是等价的。

7. 总结

  1. 指针与数组的等价关系

    • 数组名本质是首元素地址的常量指针(arr ≡ &arr[0]
    • 指针运算与数组索引完全等价(*(p+i) ≡ arr[i]
  2. 多维结构的访问

    • 二维数组:int (*p)[3]指向行
    • 指针数组:char **p处理变长字符串集合
  3. 内存安全三原则

    • 严禁越界访问(数组/指针移动必须严格受限)
    • 字符串必须NUL终止('\0'缺失是万恶之源)
    • 常量数据加const保护(const char*防误改)

指针是C语言的灵魂所在,它给予你接近硬件的自由,同时也要求你承担相应的责任。唯有严谨和练习,才能让这份力量为你所用!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

使者大牙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值