C 语言指针与多维数组深度掌握:从一维到三维的内存布局与访问
C 语言中,数组是存储在连续内存位置的同类型值集合。一维或多维数组中的每个元素由一个或多个唯一整数索引标识。
指针存储变量地址。数组中第 0 个元素的地址即数组的指针。可使用“解引用运算符”访问指针指向的值。
C 语言可声明一维、二维或多维数组。“维度”指标识集合中元素所需的索引数量。
指针与一维数组
一维数组中每个元素由单个整数标识:
int a[5] = {1, 2, 3, 4, 5};
此处“1”位于第 0 索引,“2”位于索引 1,以此类推。
存储第 0 个元素地址的变量即其指针:
int *x = &a[0];
简单说,数组名也指向第 0 个元素地址。所以也可用:
int *x = a;
示例
指针值按数据类型大小递增,x++ 移动指针到数组下一元素。
#include <stdio.h>
int main(){
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int *ptr = arr;
while (i < length){
printf("arr[%d]: %d \n", i, *(ptr + i));
i++;
}
return 0;
}
输出
arr[0]: 1
arr[1]: 2
arr[2]: 3
arr[3]: 4
arr[4]: 5
指针与二维数组
一维数组像元素列表,二维数组像表格或矩阵。
二维数组元素可视为逻辑上按行列排列。任意元素位置由两个索引决定:行号和列号。行列索引均从 0 开始。
int arr[2][2];
此类数组逻辑表示为:
| Col0 | Col1 | Col2 | |
|---|---|---|---|
| Row0 | arr[0][0] | arr[0][1] | arr[0][2] |
| Row1 | arr[1][0] | arr[1][1] | arr[1][2] |
| Row2 | arr[2][0] | arr[2][1] | arr[2][2] |
注意表格排列只是逻辑表示。编译器分配连续字节块。C 语言数组分配采用行优先方式,即按行顺序读入数组。
声明三行四列的二维数组(第一个方括号数字始终指行数):
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
编译器按行优先顺序为上述二维数组分配内存。假设数组首元素地址为 1000,int 类型大小 4 字节,数组元素获得以下内存位置:
| Row 0 | Row 1 | Row 2 | |
|---|---|---|---|
| Value | 1 2 3 4 | 5 6 7 8 | 9 10 11 12 |
| Address | 1000 1004 1008 1012 | 1016 1020 1024 1028 | 1032 1036 1040 1044 |
用取地址 & 运算符将数组首元素地址赋给指针 ptr:
int *ptr = &arr[0][0];
示例 1:线性遍历二维数组
指针每加 1 移动到下一地址。“34”数组所有 12 个元素可在循环中访问:
#include <stdio.h>
int main(){
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
// 指针 ptr 指向数组 num
int *ptr = &arr[0][0];
int i, j, k = 0;
// 通过指针 ptr 打印数组 num 元素
for (i = 0; i < 3; i++){
for (j = 0; j < 4; j++){
printf("%d ", *(ptr + k));
k++;
}
printf("\n");
}
return 0;
}
输出
1 2 3 4
5 6 7 8
9 10 11 12
一般可用以下公式获取数组任意元素地址:
ith 行 jth 列元素地址 = baseAddress + [(i * no_of_cols + j) * sizeof(array_type)]
在我们的 3×4 数组中:
arr[2][2] 地址 = 1000 + (2*4 + 2)*4 = 1044
可参考上图,确认 arr[2][2] 地址为 1044(原文 arr[3][4] 系索引越界笔误,应为 arr[2][2])。
示例 2:用解引用指针按公式遍历数组
#include <stdio.h>
int main(){
// 2d array
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int ROWS = 3, COLS = 4;
int i, j;
// pointer
int *ptr = &arr[0][0];
// print the element of the array via pointer ptr
for (i = 0; i < ROWS; i++){
for (j = 0; j < COLS; j++) {
printf("%4d ",*(ptr + (i * COLS + j)));
}
printf("\n");
}
return 0;
}
输出
1 2 3 4
5 6 7 8
9 10 11 12
指针与三维数组
三维数组是二维数组的数组。声明需三个下标:
int arr [x] [y] [j];
可视为“x”层表格,每层“x”行“y”列。
三维数组示例:
int arr[3][3][3] ={
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
};
三维数组指针声明为:
int * ptr = &arr[0][0][0];
数组名本身即第 0 个元素地址,三维数组指针也可写为:
int * ptr = arr;
每层“x”行“y”列占用:
x * y * sizeof(data_type)
字节。假设上述三维数组 "arr" 分配内存从地址 1000 开始,第二层 (i=1) 起始位置为 1000 + (3×3)×4 = 1036 字节。
ptr = 三维数组 arr 基地址
若 JMAX 为行数,KMAX 为列数,第 1 层第 0 行第 0 列元素地址为:
arr[1][0][0] = ptr + (1 * JMAX * KMAX)
第 i 层第 j 行第 k 列元素值公式:
arr[i][j][k] = *(ptr + (i * JMAX*KMAX) + (j*KMAX + k))
示例:用指针解引用打印三维数组
用此公式通过指针解引用打印三维数组:
#include <stdio.h>
int main(){
int i, j, k;
int arr[3][3][3] = {
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
};
int JMAX = 3, KMAX = 3;
int *ptr = arr; // &arr[0][0][0];
for(i = 0; i < 3; i++){
for(j = 0; j < 3; j++){
for(k = 0; k < 3; k++){
printf("%d ",*(ptr+(i*JMAX*KMAX)+(j*KMAX+k)));
}
printf("\n");
}
printf("\n");
}
return 0;
}
输出
11 12 13
14 15 16
17 18 19
21 22 23
24 25 26
27 28 29
31 32 33
34 35 36
37 38 39
一般来说,用指针访问数组与用下标表示法访问数组相当相似。主要区别在于下标声明数组静态分配内存,而指针可用于动态内存分配。
向函数传递多维数组需用指针而非下标。不过,下标数组比指针更方便,对初学者指针可能较难。
C语言
- 掌握 C++ switch-case 语句:语法、流程与计算器实战案例
- C 语言变量详解:类型、命名规则、定义声明与内存管理
- C# 面试题精选 50 道(2021 版)—— 面向初学者与资深开发者
- 掌握C#嵌套循环:For、While与Do-While详解
- C 语言存储类完全解析:auto、extern、static、register
- C 语言注释完全指南:编写清晰高效的代码备注
- 掌握 C# 位运算与移位运算符:全面指南
- C++ 运算符全解析:类型、示例与示例程序
- C 语言 free() 函数实战指南:正确释放动态内存、防泄漏与避坑全解析
- 掌握 C# using 指令:命名空间导入、别名与静态导入全解析
- C# Hello World:构建你的第一个控制台应用程序
- C++ 友元函数与友元类:突破访问控制的利器