欢迎光临
我们一直在努力

指针(3)

# 一文搞懂C语言二级指针、指针数组、数组指针与二维整型数组传参

在C语言的学习路径里,指针始终是绕不开的核心难点,而二级指针、指针数组、数组指针这几个概念,更是很多初学者的“重灾区”。不少人写了好几年代码,依然会在二维数组传参时踩坑,今天我们就把这几个知识点彻底讲透,帮你理清它们的本质区别和实际用法。

## 一、从“指针的指针”说起:二级指针

我们都知道,普通的变量有自己的内存地址,把这个地址存起来,就得到了一级指针。那一级指针本身也是一个变量,它同样占据内存空间,它的地址自然也可以被存储——用来存放一级指针地址的变量,就是二级指针。

最直观的代码示例:

`#include <stdio.h>`\\
`int main() {`\\
`    int a = 10;`\\
`    int *p = &a;   // p是一级指针,存放变量a的地址`\\
`    int **pp = &p; // pp是二级指针,存放一级指针p的地址`\\
`    `\\
`    printf("a的值:%d\\n", a);`\\
`    printf("通过一级指针访问a:%d\\n", *p);`\\
`    printf("通过二级指针访问a:%d\\n", **pp);`\\
`    return 0;`\\
`}`

运行这段代码你会发现,`**pp`最终也能拿到变量a的值。简单来说,二级指针就像一个“地址的地址”,我们日常开发里最常见的场景,就是用它来接收动态开辟的一维数组、或者作为指针数组的起始地址。

## 二、别再搞混:指针数组 vs 数组指针

这两个名字几乎一模一样的概念,是90%的C语言初学者都会踩的坑,我们只需要记住一个核心判断逻辑——**看最后一个字**,就能一秒分清它们的本质:

– 指针数组:最后一个字是“数组”,说明它本质是数组,数组里的每一个元素都是指针
– 数组指针:最后一个字是“指针”,说明它本质是指针,这个指针指向一整个数组

### 1. 指针数组:用来装一堆地址的“容器”

指针数组的核心是数组,类比我们熟悉的概念就能快速理解:

– 整型数组:每个元素都是int类型的数据
– 字符数组:每个元素都是char类型的数据
– 指针数组:每个元素都是指针类型的地址

根据运算符优先级,`[]`的优先级比`*`高,所以声明`int *arr`的时候,arr会先和\\`\\`结合,说明它是一个大小为3的数组,再和`int *`结合,说明数组里的每个元素都是int类型的指针。

指针数组最经典的用法,就是模拟“不规则二维数组”:

`#include <stdio.h>`\\
`int main() {`\\
`    // 定义3个长度不同的独立一维数组`\\
`    int arr1[] = {1, 2, 3};`\\
`    int arr2[] = {4, 5, 6, 7};`\\
`    int arr3[] = {8, 9};`\\
`    `\\
`    // 定义指针数组,存放3个数组的首地址`\\
`    int *parr = {arr1, arr2, arr3};`\\
`    `\\
`    // 遍历这个“不规则二维数组”`\\
`    for(int i = 0; i < 3; i++) {`\\
`        int len = (i == 1) ? 4 : 3;`\\
`        for(int j = 0; j < len; j++) {`\\
`            printf("%d ", parr[i][j]);`\\
`        }`\\
`        printf("\\n");`\\
`    }`\\
`    return 0;`\\
`}`

这种用法的优势非常明显:它不需要所有行的长度完全一致,内存里的各个一维数组可以分散存储,灵活度极高,我们平时处理字符串数组的时候,用的就是指针数组。

### 2. 数组指针:专门指向一整行的“行指针”

数组指针的核心是指针,它是一个专门用来存放数组地址的指针变量。声明的时候必须加括号,也就是`int (*p)`,括号让p先和`*`结合,说明它是一个指针,后面的\\`\\`说明这个指针指向的是一个包含5个int元素的数组。

数组指针最特殊的地方在于它的运算规则:普通的int指针+1,只会向后移动4个字节(一个int的大小),而数组指针+1,会直接向后移动一整个数组的长度,也就是5\\*4=20个字节。这个特性,刚好完美适配二维数组的行跳转。

## 三、二维整型数组传参:90%的人都写错了

很多人给函数传二维数组的时候,随手就写一个`void func(int **arr)`,运行的时候直接崩溃,这就是没搞懂二维数组的本质导致的错误。

我们先理清二维数组的内存特性:标准的二维数组`int a`,所有元素是按行优先连续存储的,它的数组名a不是二级指针,而是一个**数组指针常量**。a+1的时候,不是向后跳一个指针的长度,而是直接跳过一整行的4个int元素,也就是移动16个字节。

所以给函数传二维整型数组的时候,正确的接收方式有3种:

1. 直接指定列数,这是最直观的写法:

`void func(int arr, int row, int col) {`\\
`    // 直接用下标访问元素`\\
`    for(int i = 0; i < row; i++) {`\\
`        for(int j = 0; j < col; j++) {`\\
`            printf("%d ", arr[i][j]);`\\
`        }`\\
`    }`\\
`}`

2. 用数组指针接收,这是最本质的写法:

`void func(int (*arr), int row, int col) {`\\
`    for(int i = 0; i < row; i++) {`\\
`        for(int j = 0; j < col; j++) {`\\
`            printf("%d ", arr[i][j]);`\\
`        }`\\
`    }`\\
`}`

这里必须注意,数组指针的长度必须和二维数组的列数完全一致,否则指针跳转就会出错。 3. 用一级指针做“扁平化”处理,把连续的二维数组当成一维数组访问:

`void func(int *arr, int row, int col) {`\\
`    for(int i = 0; i < row; i++) {`\\
`        for(int j = 0; j < col; j++) {`\\
`            printf("%d ", arr[i * col + j]);`\\
`        }`\\
`    }`\\
`}`\\
`// 调用的时候要取二维数组第一行的首地址`\\
`func(&a, 3, 4);`

而之前错误的`int **arr`写法,是用来接收指针数组的,它的逻辑是先从地址里取出一个一级指针,再从这个一级指针里取出元素,和连续存储的二维数组的内存逻辑完全不匹配,自然会出现访问非法内存的问题。

## 四、最后总结一张表,彻底告别混淆

| 概念 | 本质 | 声明示例 | 核心特性 |
| — | — | — | — |
| 二级指针 | 指向一级指针的指针 | int \\*\\*pp | pp+1移动一个指针的长度 |
| 指针数组 | 存放指针的数组 | int \\*arr | 每个元素都是独立的地址,可模拟不规则二维数组 |
| 数组指针 | 指向一整个数组的指针 | int (\\*p) | p+1移动一整个数组的长度,适配二维数组行跳转 |
| 二维数组名 | 数组指针常量 | int a | 元素连续存储,按行优先排列 |

把这几个概念的本质彻底搞懂,你再写C语言的指针相关代码,就再也不会出现“看着眼熟但一写就错”的情况了。 

赞(0)
未经允许不得转载:171主机测评 » 指针(3)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址