在使用malloc()函数申请动态数组,重新对多维数组与多重指针之间的关系的思考:数组名就是指针,可以用多重指针和数组的方式访问;顺便对嵌套循环效率的一些解释。

一.先从用malloc()函数申请多维动态函数说起。

C语言中用普通的方式定义的数组其大小不能改变。如通过int a[N];定义大小为N的整型数组,其后N的改变不会再改变原来数组大小。但我们可以通过malloc()或calloc()等动态存储分配函数申请分配一块空间,将其返回的所分配单元的起始地址赋给指针,我们就可以利用得到的指针进行和数组一样的操作。因为我们知道普通的数组名其实就是数组的起始地址,相当于一个指针常量。

1.定义一个大小为n的整型动态数组,如下:

 int *a;

   int n;

   n = 10; //此处设为10

   a =(int *)malloc(sizeof(int)*n); /*动态分配存放10个整型的内存,将所分配空间起始地址转换为

                                                         整型地址赋给指针a*/

   a[5] = 100;                                  /*引用同一般数组,但不应超出所分配内存的大小

                                                       a[5]相当于*(a+5), 即从首地址向后跳过5个(整型)内存*/

   free(a);                                        //释放a所指的内存区

在知道地址和变量类型的情况下,a[i]等同于*(a+i),即从首地址开始跳过i个已知类型的数据元素。这两个内存函数原型为void *malloc(usigned size);和void *calloc(unsigned n, unsign size);它们包含在<stdlib.h>中。两者区别有二:一是前者分配size个字节大小的连续空间,后者分配n个size字节的连续空间。二是后者分配的内存初始化为0;而前者没有;如果想改变一个已分配地址的内存区的大小可以用函数void *realloc(void *p, unsign size)。这几个函数返回void 指针类型,赋值给其他类型指针时,需经过类型转换,系统一般在编译时也进行隐式转换。

2.定义一个三维动态数组大小为n1,n2,n3

想得到多维的的动态数组,可以嵌套使用前面的方法,如将得到的动态数组的元素a[i]再定义为动态数组。即定义指向指针的指针,由外向内逐级分配空间。

int ***a; //定义指针的指针的指针

int n1, n2, n3;

int i, j, k;

scanf("%d%d%d",&n1,&n2,&n3);

a=(int ***)malloc(n1*sizeof(int **));  /*a有n1个元素,每个元素仍为数组(其实是指针的指针)

                                                        a为数组首地址,a[1],a[2],...,a[n1]为其子数组元素首地址,以此类推直至得到元素*/
for(i=0; i<n1; i++)

{
    
    
   a[i]=(int **)malloc(n2*sizeof(int *));  //a[i]有n2个元素,每个元素为一维数组的首地址(其实是指针)
   for(j=0; j<n2; j++)
   {
    
    
       a[i][j]=(int *)malloc(n3*sizeof(int)); //a[i][j]为一维数组,大小为n3
       for(k=0; k<n3; k++)
       {
    
    
           a[i][j][k] = i*n2*n3+j*n3+k;          //元素a[i][j][k]的引用同一般数组
        }
     }

}

.......;

//使用后要释放存储空间,注意由内向外逐级释放

for(i=0; i<n1; i++)
{
    
    
   for(j=0; j<n2; j++) free(a[i][j]);
   free(a[i]);
}
free(a);

多维数组分配存储空间从外往内,释放空间时要由从内往外。函数分配的内存空间,即使没有指针指向,它仍然存在,不自动回收,所以不能通过这定指针为空来释放空间。

3.从上述多维动态数组的分配了解指针与数组的关系

从外向内:下图以一个P[2][2][2]的三维动态数组举例:
在这里插入图片描述
上图为申请动态数组的内存结构图,从图中可以看出来,内存并不连续。

 int a[3][4];
    a[0][0]=4;
    a[2][3]=4;
    int *p=a;
     //int (*p)[4]=a;
     //p=a;
     
    
    printf("%d%d",*(*(a+2)+3),*p);

上面这个程序可以输出结果:4 4
但是并不能通过二重指针的方式来访问对应的数组,如下打印代码:

printf("%d%d",*(*(a+2)+3),*(p+11));

要想以二重指针的方式以上面第一个格式打印出来的,得通过以下定义:

int a[3][4];
    a[0][0]=4;
    a[2][3]=4;
    //int *p=a;
     int (*p)[4]=a;  /*这里很像定义了一个指针数组,但并不是,指针数组没有括号,
     这里就是一个指针,就是相当于定义了一个指向四个int型长度为一个单位的指针变量p,
     这样就会把二维数组按照4段一个单位,把4个单位相应的首地址存储在p中,整个的存储结构和上面的三维动态数组是一样的*/
     //p=a;
     
    
    printf("%d%d%d",*(*(a+2)+3),*(*(p+2)+3)),p[2][3]);

输出结果都为4;后面加一个数组下标对应二维的数量,这里就和定义二维数组初始化一样,必须要知道低维度所有的数量,才能确定这个数组的大小,这里才能知道。动态与静态数组的区别就在于数据地址的连续性。

int a[3][4];
    a[0][0]=4;
    a[2][3]=4;
    //int *p=a;
     int (*p)[4]=a;
     //p=a;
     int c[2] ={
    
    3};
     int f[2]={
    
    1};
     int *d[2]={
    
    c,f};
     for (int i = 0; i < 2; i++)
     {
    
    
       printf("%d\n",*d[i]);
     }
     
     int r[2]={
    
    1};
     for (int i = 0; i < 2; i++)
     {
    
    
       printf("%d\n",r[i]);
     }
    
    printf("%d%d%d",*(*(a+2)+3),*(*(p+2)+3),p[2][3]);

以上代码可以看出指针数组与动态数组的区别。

二.聊聊for循环嵌套的效率问题

因为CPU流水线和因为流水线导致需要分支预判这两个工作机制的原因,会让大部分人认为for循环嵌套总是次数多的放在内部花费的时间少,原因主要是因为,次数多的放在内部,分支预判出错的概率就会少(分支预判就是if,switch这样的有判断的),但事实并非如此,有很多因素会影响花费的时间;如果处理的指令是取内存中的数据并对其赋值等操作,因为内存的取存操作需要CPU先将内存中的数据渠道CPU中的缓存中取,缓存中的数据才是直接和CPU交换的,保证了CPU的处理速度与取数据的速度保持一致,最大的利用CPU的性能,这时因为CPU中缓存的容量很小一般有5兆左右,存储的数据都是需要经常或马上会使用的,而当对二维数组存储时,数组中同一个维度的数据会在一种算法的作用下(这个算法来维持内存和缓存中数据的稳定,是缓存中的数据保留的全是需要经常使用的)先被送到缓存中去,这时如果是直接对连续的数组数据进行操作,效率就较高,如果是分层对数组数据操作,CPU在缓存中的命中率就会低很多,不断要去内存中获取数据,效率就会低不少,所以这种情况下就不应内层次数多的时间少了;
参考如下例子:

void test1()
{
    
    
 long dt = DateTime.Now.Ticks;
 for (int i = 0; i < 1000000; i++)
 {
    
    
  for (int j = 0; j < 10; j++)
  {
    
    
   a[i,j] = 0;
  }
 }
 Console.WriteLine("test1:{0}",DateTime.Now.Ticks - dt);
}

void test2()
{
    
    
 long dt = DateTime.Now.Ticks;
 for (int j = 0; j < 10; j++)
 {
    
    
  for (int i = 0; i < 1000000; i++)
  {
    
    
   a[i,j] = 0;
  }
 }
 Console.WriteLine("test2:{0}",DateTime.Now.Ticks - dt); 
}

这个就是test1更快。
https://blog.csdn.net/schumyxp/article/details/2398935?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase

https://zhidao.baidu.com/question/57090974.html

猜你喜欢

转载自blog.csdn.net/weixin_42224577/article/details/108149402