问题来源: 如何正确申请多维动态数组
C 与 C++在此处有相同也有不同,C在此处尤其注意。
C++中
// 这里只讲推荐方法: 使用 数组指针
// int (*array)[x][y][z]
// 注意:方括号里面的x,y,z必须有,不能不写,不然你下面访问数据就会编译器编译报错
// m,n,p在此处为变量、常量皆可
int (* array)[n][p] = new int[m][n][p]; // 三维动态数组
// 此处array 是一个三维数组指针,怎么理解呢 (*array) 展开后是一个二维数组
array[x][y][z] = 1; // 与数组的操作无区别;
C中
介绍三种:
数组指针(推荐方式 !!! 因为可以直接使用 memset函数初始化)
// 正确方式
int (* array)[n][p] = ( int (*)[n][p] )malloc(sizeof(int) * m * n * p);
// 错误方式:
// int (* array)[][] = ( int (*)[][] )malloc(sizeof(int) * m * n * p);
array[x][y][z] = 0; // 使用错误方式时,该句在编译阶段会报错
多重指针方式: 太麻烦了要自己循环去赋值并申请,且数据真实存储空间地址可能并不连续也意味着一些一维指针偏移操作不可用
// 这里展示二维动态数组 (5 x 2) 的数组哈
// 该类方法缺点是 申请麻烦, 初始化麻烦, 操作受限制, 释放麻烦 不推荐 且容易理解错误
int **p = (int **)malloc(sizeof(int *) * 5);
for (int i = 0; i < 5; ++i)
{
p[i] = (int *)malloc(sizeof(int) * 2);
}
// 下面来个简单错误案例
// 错误 !!!!!!!指针越界
int ***array = (int***)malloc(sizeof(int)* m *n *p);
array[x][y][z] = 0;
// 错误原因:
// array换个写法你就懂了 int** *array; 意思是 array是一个指向 int** 的指针,而内存申请的是m*n*p*4字节的内存
// array[1]就是一个地址变量了, 至于你想array[x][y][z], 这里大概率会直接内存越界
// (是不是突然知道为什么C程序员喜欢把 * 贴着变量名了, 为了避免错误认知。
一维指针方式:
// 先说优缺点,直接、申请释放简单、易理解、但访问变量非常复杂
int *array = (int *)malloc(sizeof(int) * (m * n * p));
array[x * n * p + y * p + z]; // 复杂的访问方式 访问 数组的第 array[x][y][z]
// 访问复杂, 直接pass
为什么会有上述的一些错误
根本原因: 数组的中括号操作与指针的中括号操作在 多维数据 上时,行为不同!!!!!
两者行为解释
数组array[x][y]
数组 int array[3][3]; 其中 array本质上数组的起始地址,甚至不能说是变量哈哈,因为在汇编中他数组数据访问是sp + 偏移量,设 数组array的起始地址为 st_addr 这是一个地址。
int array[row_len][col_len];
array[x][y] = 0; // array[x][y] 会翻译为类似于以下的内容
*( (int *)( st_addr + (x * col_len + y) * sizeof(int) ) ) = 0;
就是起始地址加上计算的偏移量
指针int* *array_ptr[x][y]
int* *array_ptr;
// array_ptr实际上是一个指针变量, 指向一个 int* 类型的 数据
// 所以 *array_ptr 是一个int* 类型
array_ptr[x][y] // 其展开方括号是一步一步进行的,而不是像数组一样是一步展开所有
// 第一步展开 设array_ptr 变量的值为 st_addr
// => array_ptr[x] => *( (int **)(st_addr + x * sizeof(int*)) ) => 此时 为一个 int* 类型的数据
// 设第一步展开后这个数据的变量 为 step_1_val 其类型为 int* 及是等价于 int *step_1_val;
// 第二步展开
// => step_1_val[y] => *( (int* )(step_1_val + y * sizeof(int)) ) => 此时 为一个 int类型的数据
// 两步合并
// array_ptr[x][y] => *( (int *)( *( (int** )(st_addr + x * sizeof(int*)) ) + y * sizeof(int) ) )
两者行为对比总结:
- 展开公式明显不同
*( (int *)( st_addr + (x * col_len + y) * sizeof(int) ) )
对比
*( (int *)( *( (int** )(st_addr + x * sizeof(int*)) ) + y * sizeof(int) ) )
哪怕起始地址值st_addr 都一样他们结果都不同- 方括号行为顺序不同
数组的多个方括号是一次性所有方括号中的值计算,而指针后面多个方括号是一个一个加入解释的
Tips: 所以多维数据中绝对不能像一维数组那样认为两者可以等价!!!!!
一维数组时:
int array[10];
int *array_ptr = &array[0];
array_ptr[x];
// 等价
array[x];
二维数组时:
int array[10][10];
int* *array_ptr = &array[0][0]; // 这句话就有问题
array_ptr[x];
// 完全不同,
array[x];
N维数组一样不同
Tips: 对于编译器来说,数组也是一种类型,所以数组指针是指向数组,这个数组可能是n维度
int (*p)[10]; // p是一个指向长度为10,类型为int的数组指针, 这个数组的内存大小为sizeof(int)*10 => 40Byte
p[9]; // p[9] 等价于 *(st_addr + 9 * 40) 此时 为一个 int param[10]; 的起始地址
// 多维数组指针
int (*p)[9][10]; // p是一个指向长款9*10, 类型为int的二维数组的 数组指针 指针所指向的每一块内存为 9*10*4 = 360Byte
函数指针
// 原始定义
int func_1(int a, int b);
// p是一个函数指针 指向返回值为int, 输入为两个int的函数
int (*p)(int, int) = func_1; // p 是 指针(函数指针)
// 定义一个包含10个函数指针的数组 // 紧紧跟在变量名后面的方括号
int (*p[10])(int, int); // p 是 数组
// 指向函数指针的指针
// 错误定义
int (*p)(int, int)[10] // 想定义数组指针, 但语法 错误, 其并不能表示定义一个数组指针指向包含10个函数指针的数组
int (*p[10])(int, int)[10] // 想定义数组, 但语法错误
// 正确定义:
int (*(*p)[10])(int, int); // p是指针(数组指针), 定义一个数组指针,指向一个包含10个函数指针的数组
int (*(*p[10])[10])(int, int); // p是数组, 表示 p 是一个包含 10 个指向包含 10 个函数指针的数组的数组
// 利用typedef 简化函数指针定义
typedef int (*FuncPtrType)(int, int);
FuncPtrType p = func_1;
// 定义一个包含10个函数指针的数组
FuncPtrType p_lis[10] = {}; // p_lis 是数组
// 定义一个数组指针, 数组里面包含10个函数指针
FuncPtrType (*p)[10]; // p是数组指针
FuncPtrType (*func_ptr)(int, int); // 来点绕的
// 感觉好绕
实验展示
将下面代码粘贴到https://godbolt.org/中选择你熟悉的平台(x86、ARM、RISCV)编译器在线编译下,看汇编代码,注意观察下面注释的语句的汇编代码对比下就能明显发现差异
void test_alloc()
{
int array[3][3];
volatile int(*array_ptr)[3];
int* *array_ptr_2;
array_ptr =(int(*)[3])(&array);
array_ptr_2= (int **)(array);
array_ptr[2][2] = 0;
array[2][2] = 0; // 对比
array_ptr_2[2][2] = 0; // 对比
}
发现没, 指针的方括号操作汇编语句好长,先不论他是否错误,提出一个问题,函数传参多维数组时,使用哪种方式好?
//4个版本的求和函数
//方式一:数组形式
int TwoDimArraySum1(int twoDimAr[][COL], int row, int col);
//方式二:指针形式,prArray是一个指向包含COL个int的数组的指针
int TwoDimArraySum2(int (*prArray)[COL], int row, int col);
//方式三:指针形式,pr是一个指向int的指针
int TwoDimArraySum3(int *pr, int row, int col);
//方式四:变长数组(C99开始支持)
int TwoDimArraySum4(int row, int col, int twoDimAr[row][col]);
// 我个人倾向于方式3 灵活
// 但函数在里面需要加一句
// int (*array_ptr)[col] = (int (*)[col])(pr); 这样就解决了访问元素复杂的问题且指令数量更少