关于动态二维数组的问题?想知道的请看看……

本文介绍如何在C语言中创建动态二维数组,并提供了一个可以使用[i][j]方式访问的二维数组实现方法。通过示例代码展示了不同访问方式。

 在C语言中,有内置的一维数组,二维数组等多维数组,也可以使用动态分配内存的方式,很容易的得到一个

类似内置静态一维数组的动态数组(在这里我这么叫,也许并不存在这个定义 :)),但如何取得动态的二维

数据组呢?如果理解了C语言中对于数组指针的访问方式,那么实现一个自己的二维数组是很简单的。

下面首先理清一下一维数据指针的访问方式:

假设定义有如下定义:

#define DIMS 10

int Arr1[DIMS]

int *pArr1;

学过C语言的人都知道可以如下访问数据中的每一个元素:

Arr1[i] = xxxx;

同样理解数据即指针也可以知道:

pArr1 = Arr1;

for (int i = 0; i < DIMS; ++i)

{

    *pArr1 = xxxx;

    pArr1++;

}

这是一维的情况,那么二维呢?

对于静态的二维数组,三维数组等等都是很方便的

int Arr2[DIMS][DIMS];

int *pArr2;

for (int i = 0; i < DIMS; ++i)

{

    for (int j = 0; j < DIMS; ++j)

    {

        Arr2[i][j] = xxxx; //访问数组单元

    }

}

pArr2 = Arr2;

for (int i = 0; i < DIMS * DIMS; ++i)

{

    *pArr2++ = xxxx; //访问数组单元,并下移指针

}

三维方式差不多,在C语言中,静态多维数据其实就是一个平面数据,C语言编译器会将数据访问的形式翻译成

内存访问的形式。

比如:

一维: Arr1[i]   ----> *(Arr1 + (i * sizeof(Arr1[0])))

二维: Arr2[i][j] ---> *(Arr2 + (i * sizeof(Arr2[0][0]) + n * sizeof(Arr2[0][0]))
       这里的n是二维数据的第二个下标的最大值

三维:方式与上类似,以数组首地址开始,计算数据偏移。
使用下标使用数组的方式,给人以清晰的感觉,两样可以增加写程序的复杂度,意味着可以减少错误的机率。
在一些情况下,使用静态的二维数据有着它的局限性,在大小未知的情况下,使用静态数组就不方便,有某些

情况下,会很耗费栈资源(静态数组在栈中分配),所以需要动态数组。对于一维的情况,可以像一维静态数

据那样使用,可是对于多维数组,如果只是像静态数组那样分配元素所需要的空间,那么就无法使用下标方式

访问数据。
int *pArr1, **pArr2;
pArr1 = (int*)malloc(sizeof(int) * DIMS);
pArr2 = (int**)malloc(sizeof(int) * DIMS * DIMS);
...
pArr1[i] = xxx;  //正确
pArr2[i][j] = xxx; //错误

这里二维为什么有问题呢?
看一下反汇编指命:
65:       pArr2[4][5] = 10;
004011B8   mov         eax,dword ptr [ebp-4]  ;ebp-4是变量int **pArr2
004011BB   mov         ecx,dword ptr [eax+10h]  ;10h = 4 * sizeof(int) = 4 * 4
004011BE   mov         dword ptr [ecx+14h],0Ah  ;14h = 5 * 4
这三行汇编指命是在VC6.0下生成的。
第一行将变量地址存入EAX,然后将EAX移动到第四个偏移移到ECX,然后再取得第5个位置的数据,实现上这里

可以如下解析:
(pArr2[4])[5]
下面来看一下静态二维数据的汇编代码:
64:       Arr2[4][5] = 10;
004011AE   mov         dword ptr [ebp-0DCh],0Ah  ;0DCh = [(10-1) - 4]*40 + (5-1)*4 - 4
与上面相比,这里的方式有着很大的不同。静态二维数据是直接访问的,而动态方式,C语言并不认为是数组

,而是将其看成二次间接。
有了以上的基础之后,我想读者一定很快就知道如何去实现一个动态二维数据了。
首先建立一个行指针,指针行的数据,然后将行指针返回作为二维数据指针就OK了。
下面给出示意图:

简明实现动态二维指针

 

这样就有一个问题,为申请一个二维数组,需要分配多次空间,这样会产生内存碎片,如是我想到了一种一次

性分配内存的方法。示意图如下:

 

一次性分配二维数组内存示意图
最终实现代码如下:
Dyn2DimArr.h
-------------------------
view plaincopy to clipboardprint?
#ifndef __DYN2DIMARR_HEADER_H__  
#define __DYN2DIMARR_HEADER_H__  
 
/** 
 * 取得一个动态二维数组,可以使用[i][j]方式访问 
 * 返回一个二维数据指针 
 */ 
void** GetDyn2DimArr(int m, int n, int element_size);  
 
/** 
 * 释放二维数据内存 
 */ 
void FreeDyn2DimArr(void **pArr2);  
#endif //~__DYN2DIMARR_HEADER_H__ 
#ifndef __DYN2DIMARR_HEADER_H__
#define __DYN2DIMARR_HEADER_H__

/**
 * 取得一个动态二维数组,可以使用[i][j]方式访问
 * 返回一个二维数据指针
 */
void** GetDyn2DimArr(int m, int n, int element_size);

/**
 * 释放二维数据内存
 */
void FreeDyn2DimArr(void **pArr2);
#endif //~__DYN2DIMARR_HEADER_H__
 

Dyn2DimArr.c
-------------------------
view plaincopy to clipboardprint?
#include <stdlib.h>  
#include <string.h>  
#include <assert.h>  
 
#include "Dyn2DimArr.h"  
 
void** GetDyn2DimArr(int m, int n, int element_size)  
{  
    void *pMem = NULL;  
    void **pMemOwner = NULL;  
    int pt_size;  
    int user_size, owner_size, line_size;  
    int i;  
    assert(m > 0 && n > 0);  
    assert(element_size > 0);  
 
    line_size = n * element_size;  
    user_size = m * line_size;  
    pt_size = sizeof(void*);  
    owner_size = pt_size * m;  
    pMem = malloc(user_size + owner_size);  
    if(pMem == NULL) return NULL;  
    memset(pMem, 0, user_size + owner_size);  
    // 在这里转化成void**是有意义的,因为在ANSI C语言中不能实现void*的  
    // 的运算,尽管在GNU C中是可以的,但也是以char*作为运算尺寸的  
    // 转化成void**就相当于对一个指针的指针运算,也就是偏移一个位置就是  
    // 一个指针的大小,在这里不能假设大小为4,因为在64位平台,指针大小为8  
    pMemOwner = (void**)pMem + user_size / pt_size;  
    for (i = 0; i < m; ++i)  
    {  
        pMemOwner[i] = (void**)pMem + (i * line_size / pt_size);  
    }  
 
    return pMemOwner;  
}  
 
void FreeDyn2DimArr(void **pArr2)  
{  
    // 实现自己的内存释放是有好处的,如果以后内在分配是采用其它分配方式  
    // 可以不用修改客户代码,同样也可以避免客户知道其就是一个一维指针.  
    free(pArr2[0]);  

#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "Dyn2DimArr.h"

void** GetDyn2DimArr(int m, int n, int element_size)
{
 void *pMem = NULL;
 void **pMemOwner = NULL;
 int pt_size;
 int user_size, owner_size, line_size;
 int i;
 assert(m > 0 && n > 0);
 assert(element_size > 0);

 line_size = n * element_size;
 user_size = m * line_size;
 pt_size = sizeof(void*);
 owner_size = pt_size * m;
 pMem = malloc(user_size + owner_size);
 if(pMem == NULL) return NULL;
 memset(pMem, 0, user_size + owner_size);
 // 在这里转化成void**是有意义的,因为在ANSI C语言中不能实现void*的
 // 的运算,尽管在GNU C中是可以的,但也是以char*作为运算尺寸的
 // 转化成void**就相当于对一个指针的指针运算,也就是偏移一个位置就是
 // 一个指针的大小,在这里不能假设大小为4,因为在64位平台,指针大小为8
 pMemOwner = (void**)pMem + user_size / pt_size;
 for (i = 0; i < m; ++i)
 {
  pMemOwner[i] = (void**)pMem + (i * line_size / pt_size);
 }

 return pMemOwner;
}

void FreeDyn2DimArr(void **pArr2)
{
 // 实现自己的内存释放是有好处的,如果以后内在分配是采用其它分配方式
 // 可以不用修改客户代码,同样也可以避免客户知道其就是一个一维指针.
 free(pArr2[0]);
}

好了,二维动态数组已经实现了,会不会有人问三维的呢?我想实现原理已经在这里,问题应该不大了吧。

为了方便使用,这里贴出测试代码,以可以使用的方式。

view plaincopy to clipboardprint?
#include <stdio.h>  
#include <stdlib.h>  
 
#include "Dyn2DimArr.h"  
 
int main(int argc, char **argv)  
{  
    int **Arr2;  
    int *ArrLine;  
    int *pData;  
    int i, j;  
    Arr2 = (int**)GetDyn2DimArr(8, 10, sizeof(int));  
    for (i = 0; i < 8; ++i){  
        for (j = 0; j < 10; ++j){  
            Arr2[i][j] = i << 8 | j;  
        }  
    }  
 
    printf("使用Arr2[i][j]方式/n");  
    for (i = 0; i < 8; ++i){  
        for (j = 0; j < 10; ++j){  
            printf("(%d,%d) ", Arr2[i][j] >> 8, Arr2[i][j] & 0xFF);  
        }  
        printf("/n");  
    }  
    printf("/n");  
 
    printf("使用Arr2[i]取得行指针ArrLine,再使用ArrLine[j]方式/n");  
    for (i = 0; i < 8; ++i){  
        ArrLine = Arr2[i];  
        for (j = 0; j < 10; ++j){  
            printf("(%d,%d) ", ArrLine[j] >> 8, ArrLine[j] & 0xFF);  
        }  
        printf("/n");  
    }  
    printf("/n");  
 
    printf("使用Arr2[i]取得行指针ArrLine,再使用*ArrLine+偏移方式/n");  
    for (i = 0; i < 8; ++i){  
        ArrLine = Arr2[i];  
        for (j = 0; j < 10; ++j){  
            printf("(%d,%d) ", *ArrLine >> 8, *ArrLine & 0xFF);  
            ++ArrLine;  
        }  
        printf("/n");  
    }  
    printf("/n");  
 
    printf("使用Arr2[0]取得数据指针,直接使用全偏移方式访问所有数据/n");  
    pData = Arr2[0]; //这里不能直接使用Arr2,这是与静态二维数组不同的地方  
    for (i = 0; i < 8; ++i){  
        for (j = 0; j < 10; ++j){  
            printf("(%d,%d) ", *pData >> 8, *pData & 0xFF);  
            ++pData;  
        }  
        printf("/n");  
    }  
 
    FreeDyn2DimArr(Arr2);  
    Arr2 = NULL;  
    return 0;  

#include <stdio.h>
#include <stdlib.h>

#include "Dyn2DimArr.h"

int main(int argc, char **argv)
{
 int **Arr2;
 int *ArrLine;
 int *pData;
 int i, j;
 Arr2 = (int**)GetDyn2DimArr(8, 10, sizeof(int));
 for (i = 0; i < 8; ++i){
  for (j = 0; j < 10; ++j){
   Arr2[i][j] = i << 8 | j;
  }
 }

 printf("使用Arr2[i][j]方式/n");
 for (i = 0; i < 8; ++i){
  for (j = 0; j < 10; ++j){
   printf("(%d,%d) ", Arr2[i][j] >> 8, Arr2[i][j] & 0xFF);
  }
  printf("/n");
 }
 printf("/n");

 printf("使用Arr2[i]取得行指针ArrLine,再使用ArrLine[j]方式/n");
 for (i = 0; i < 8; ++i){
  ArrLine = Arr2[i];
  for (j = 0; j < 10; ++j){
   printf("(%d,%d) ", ArrLine[j] >> 8, ArrLine[j] & 0xFF);
  }
  printf("/n");
 }
 printf("/n");

 printf("使用Arr2[i]取得行指针ArrLine,再使用*ArrLine+偏移方式/n");
 for (i = 0; i < 8; ++i){
  ArrLine = Arr2[i];
  for (j = 0; j < 10; ++j){
   printf("(%d,%d) ", *ArrLine >> 8, *ArrLine & 0xFF);
   ++ArrLine;
  }
  printf("/n");
 }
 printf("/n");

 printf("使用Arr2[0]取得数据指针,直接使用全偏移方式访问所有数据/n");
 pData = Arr2[0]; //这里不能直接使用Arr2,这是与静态二维数组不同的地方
 for (i = 0; i < 8; ++i){
  for (j = 0; j < 10; ++j){
   printf("(%d,%d) ", *pData >> 8, *pData & 0xFF);
   ++pData;
  }
  printf("/n");
 }

 FreeDyn2DimArr(Arr2);
 Arr2 = NULL;
 return 0;
}
 

http://blog.youkuaiyun.com/yin138/archive/2009/11/04/4768546.aspx

 

<think>嗯……用户这次要一个三维数组的示例,看来是在学习编程或者数据结构相关的知识。从之前的对话看,用户似乎对数据结构的转换很感兴趣,之前问过NumPy数组转Python列表,现在是三维数组。 三维数组在内存中是按行主序连续存储的,这个特性在C语言中尤其明显。引用[1]中的代码就展示了三维数组的指针操作,+1会跳过整个数组大小。引用[2]对比了一维、二维数组的声明,正好可以延伸出三维。引用[3]那个Vue示例很有意思,展示了实际应用中如何把JSON数据重组为三维结构。 用户可能正在做某个项目,需要处理多层嵌套数据。考虑到ta之前接触过Python,这次我决定用Python和C语言两种方式展示三维数组:Python用嵌套列表更直观,C语言展示内存布局更深入。特别要注意解释清楚索引顺序——很多初学者会混淆维度顺序。 啊,还要注意上次回答时提到的数学格式要求。这次虽然主要是代码示例,但维度说明部分还是要用$n \times m \times k$这样的规范格式。生成的问题要围绕三维数组的应用场景展开,比如为什么游戏开发常用三维数组表示体素。 </think>### 三维数组示例与解析 三维数组可理解为"数组的数组的数组",常用于表示立体数据(如空间坐标、RGB图像、时间序列数据等)。以下是具体实现: #### 1. **C语言三维数组(静态内存分配)** ```c #include <stdio.h> int main() { // 声明三维数组:2页×3行×4列 int arr_3d[2][3][4] = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, // 第0页 { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } // 第1页 }; // 访问元素:页码=1, 行号=1, 列号=2 printf("元素值:%d\n", arr_3d[1][1][2]); // 输出:19 return 0; } ``` **内存布局**: 数据按页优先连续存储(页→行→列),内存偏移量计算式为: $$\text{地址} = \text{基址} + (p \cdot R \cdot C + r \cdot C + c) \times \text{sizeof(int)}$$ 其中 $p$ 为页码,$R$ 为每页行数,$C$ 为每行列数[^1]。 #### 2. **Python三维数组(嵌套列表)** ```python # 三维数组:2层×3行×2列 arr_3d = [ [ # 层0 [1, 2], # 行0 [3, 4], # 行1 [5, 6] # 行2 ], [ # 层1 [7, 8], # 行0 [9, 10], # 行1 [11, 12] # 行2 ] ] print(arr_3d[1][0][1]) # 访问层1行0列1 → 输出:8 ``` #### 3. **应用场景示例** 引用[3]的JSON数据可转换为三维结构: ```javascript // 按日期→卫星组→卫星分组的三维数组 const satelliteData = [ // 2023-12-07 [ [ /* G10组数据 */ { satName: "S10", ... }, { satName: "S10", ... } ], [ /* G11组数据 */ { satName: "S11", ... } ] ], // 2023-12-08 [ [ /* G10组数据 */ {...} ], [ /* G11组数据 */ {...} ] ] ]; ``` 维度含义:`[日期][卫星组索引][组内卫星索引]`[^3]。 --- ### 关键特性对比 | 特性 | C语言三维数组 | Python嵌套列表 | |--------------------|-------------------------------|---------------------------| | **内存分配** | 连续内存块 | 非连续(链表式存储) | | **访问速度** | $O(1)$(直接地址计算) | $O(n)$(逐层指针跳转) | | **维度固定性** | 编译时确定 | 运行时动态调整 | | **适用场景** | 高性能计算、嵌入式系统 | 快速原型开发、数据处理 | > **开发建议**: > - 需要数学运算时用NumPy的`ndarray`(支持高维向量化操作) > - 内存受限场景用C/C++静态数组 > - 快速构建原型用Python/JS嵌套列表
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值