指针和二维数组的使用讲解

在C++中,二维数组元素值在内存中是按行的顺序存放的,即先存二维数组的第一行数据,然后再存储第二行数据,一次类推,可以把它看成是一个特殊的一维数组。因此,与一维数组类似,可用指针变量来访问二维数组。我们可以像使用一维数组一样,对二维数组进行操作。但是那样非常麻烦,所以C++提供了关于二维数组的单独操作方法。


首先搞清楚三个概念:

1.二维数组行首地址

二维数组各元素按行排列可写成矩阵形式,若将第i行中的元素a[i][0])、a[i][1]… a[i][n]组成一维数组a[i](i=0, …,n),则有:

a[0] = (a[0][0], ..., a[0][n])

a[m] = (a[m][0], a[m][1], …, a[m][n]

因为数组名可用来表示数组的首地址,所以一维数组名a[i]可表示一维数组(a[i][0], a[i][1], a[i][2], ...,a[i][n])的首地址&a[i][0],即可表示第i行元素的首地址。因此,二维数组a的第i行首地址(即第i行第0列元素地址)可用a[i]表示。

一维数组的第i个元素地址可表示为“数组名+i”,因此一维数组a[i]中第j个元素a[i][j]的地址可表示为a[i]+j,即二维数组a中第i行第j列元素a[i][j]的地址可用a[i]+j来表示,而元素a[i][j]的值为*(a[i]+j)。


2.二维数组行地址

为了区别数组指针与指向一维数组的指针,C++引入了行地址的概念,并规定二维数组a中第i行地址用a+i或&a[i]表示。行地址的值与行首地址的值是相同的,即a+i = &a[i] = a[i] = &a[i][0],但两者类型不同,所以行地址a+i与&a[i]只能用于指向一维数组的指针变量,而不能用于普通指针变量,如下所示:

int a[3][3];
int *p = a + 0

对于上面两行语句,编译第二条指令时将会出错,编译系统会提示用户p与a+0的类型不同。如果要将行地址赋数组指针变量,必须用强制类型转换,如下所示:
int *p = (int *)(a + 0);

二维数组名a可用于表示二维数组的首地址,但C++规定该首地址并不是二维数组中第0行第0列的地址,即a != a[0][0],而是第0行的行地址,即a = a+0 = &a[0]。

3.二维数组的元素地址与元素值

知道了二维数组的行地址与行首地址后,可以讨论二维数组的元素地址。

因为a[i] = *(&a[i]) = *(a+i),所以*(a+i)可以表示第i行的首地址。因此二位数组第i行的首地址有3种表示方法,即a[i]、*(a+i)、&a[i][0]。

由此可推知,第i行第j列元素a[i][j]的地址有4种表示方法,即a[i]+j、*(a+i)+j、&a[i][0]+j、&a[i][j]。

而第i行第j列元素a[i][j]的值也有4种表示方法:*(a[i]+j)、*(*(a+i)+j)、*(&a[i][0]+j)、a[i][j]。

二位数组有关行地址、行首地址、元素地址、元素值的各种表示方法归纳:
第i行 行地址 a+i、&a[i]
第i行 首地址(第i行第0列地址) a[i]、*(a+i)、&a[i][0]
元素a[i][j]的地址 a[i]+j、*(a+i)+j、&a[i][0]+j、&a[i][j]
第i行第j列元素值 *(a[i]+j)、*(*(a+i)+j)、*(&a[i][0]+j)、a[i][j]
在C语言中,二级指针二维数组虽然都涉及到多维数据,但它们的内存布局使用方式存在本质区别。以下是详细分析: --- ### 1. **内存布局** - **二维数组**:内存是连续分配的,行与行之间紧密排列。例如: ```c int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; ``` 内存布局:`[1, 2, 3, 4, 5, 6]`,通过`arr[i][j]`直接计算偏移量访问。 - **二级指针**:需手动分配内存,通常指向一个指针数组(行指针),每个行指针再指向动态分配的列数据。例如: ```c int **ptr; ptr = malloc(2 * sizeof(int *)); // 分配行指针 ptr[0] = malloc(3 * sizeof(int)); // 第一行数据 ptr[1] = malloc(3 * sizeof(int)); // 第二行数据 ``` 内存布局:行指针(`ptr[0]`、`ptr[1]`)数据(`1,2,3`、`4,5,6`)可能分散。 --- ### 2. **访问方式** - **二维数组**:直接通过下标访问,编译器计算地址: ```c int val = arr[1][2]; // 访问第2行第3列(值为6) ``` - **二级指针**:通过解引用访问,需确保内存已正确分配: ```c int val = ptr[1][2]; // 等价于 *(*(ptr + 1) + 2) ``` --- ### 3. **关键区别** | **特性** | **二维数组** | **二级指针** | |------------------|----------------------------|----------------------------------| | **内存连续性** | 连续 | 可能不连续(行指针数据分离) | | **大小计算** | `sizeof(arr)`返回总字节数 | `sizeof(ptr)`仅返回指针本身大小 | | **参数传递** | 可直接传递数组名(退化为指针)| 需传递行数列数(因无大小信息) | | **动态分配** | 大小固定 | 可动态调整行/列 | --- ### 4. **常见误区** - **错误转换**:二维数组名(如`arr`)是数组指针(`int (*)[3]`),不能直接赋值给`int **`。 ```c int **ptr = arr; // 错误!类型不匹配 ``` - **正确用法**:若需用二级指针模拟二维数组,需逐层分配内存(如前面的`malloc`示例)。 --- ### 5. **适用场景** - **二维数组**:适合静态大小、内存连续的场景(如矩阵运算)。 - **二级指针**:适合动态大小(如读取文件时未知行列数)或锯齿数组(每行长度不同)。 --- ### 示例代码对比 ```c // 二维数组 int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; printf("%d", arr[1][2]); // 输出6 // 二级指针 int **ptr = malloc(2 * sizeof(int *)); ptr[0] = malloc(3 * sizeof(int)); ptr[1] = malloc(3 * sizeof(int)); ptr[1][2] = 6; printf("%d", ptr[1][2]); // 输出6 ``` --- ### 总结 二级指针二维数组在多维数据表示上相似,但底层实现不同。二维数组是静态的、连续的,而二级指针更灵活但需手动管理内存。理解这些差异有助于避免内存错误并优化代码。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值