C语言多维数组抽象理解:切格子思维
其实早在两年前我就写过一篇关于多维数组的文章:详解多维数组与指针之间的关系,随着时间的推移,我的工作与学习逐渐深入,对C语言有了更深入的理解,觉得之前写的文章里关于多维数组部分有些复杂,不能以最简单的方式描述出来,我觉得振作厉害的人是能够将一个复杂的知识点用最简单的方式描述出来,一个非常复杂的知识点,当你在解释时任何一个人乃至小学生一听就知道你在说什么,那么说明你这个人的知识能力可以说是达到了一个较高的境界,其实我很早就想写一篇这样的文章,好让大家能更容易理解多维数组,因为本质上其实多维数组的运算速度最简便与快速,但是通常情况下大家只用到了二维,极少有人能用到四维、五维、乃至六维、七维…,其实这个东西在C语言角度来说它是固定的,有一套固定的分配公式的,无论多少维其实对于C语言来说只是一维,一组连续的地址而已,只是通过[]可以快速定位不同段上的地址。
这里我用图文的形式来描述多维数组以便大家能够更好的理解,我把这个思维命名维切格子思维,以方便大家能理解多维数组的原理,首先是一维:
上图是一个一维数组,大小为2,其存储的数值分别是1、2,其实如果真的要用数组角度来形容它,它真正的实际大小为[2][1],表示有两个格子每个格子里有一个元素,我们扩展一下它,将它扩展为二维数组:
可以看到刚刚的格子里面多了一个元素,就好像从格子里切了一刀,分出了一个格子一样,存储的元素多了一倍,那么我们在扩维到三维:
可以看到三维数组和刚刚二维一样,从刚刚切过的每个小格子里在切一刀,就是给每个格子增加了一个小格子,又增加了一倍的维度,让我们在扩维到四维:
可以看到四维是在每个小格子里又切了一刀,那么五维、六维、七维其实都是如此,不停的在切切切,以上这些这是抽象上的理解,其实内存角度来说就是线性的,通过索引来进行递增得到对饮的数值,如果一开始就从内存角度来说会让人感觉到云里雾里,所以我先以上面这样抽象的方式给大家讲解一下,便于让大家脑海中有一个大体的框架,能够理解多维是怎样的一个计算思路,抽象的理解就是不停的切切切,跟套娃一样,在小格子里不停的切切切。
接下来我用C语言角度来给大家讲解一下,首先用C语言形式来生成一个四维的数组:
int array[2][2][2][2] =
{{{{1,2},{3,4},},{{5,6},{7,8}} },{{{{9,10},{11,12},},{{13,14},{15,16}}},
}
当你理解了上面的切格子思维之后就可以很轻松的定义出这样的维度,并且能够轻松的运用它,接下来从索引加图文并茂的形式来给大家一步一步讲解它的寻址过程,我会将选中的颜色标红:
[0]
[0][1]
[0][1][0]
[0][1][0][0]
最终输出为5
以上就是切格子思维的理解,这是我目前能够想到用最简单的方式来形容多维数组,无论是多少维哪怕一百维就可以用这个思维来理解它,它是有通用逻辑的。
其实在内存角度来说,假设array在内存的首地址是0x40000
那么[0]对应的地址是0x40000,也就是它的首地址,那么[0][1]对应的就是0x40000+(sizeof(array[0][0])*1),需要计算每个格子的总大小,然后按格子来进行计算,[0][1][0]对应的就是0x40000+(sizeof(array[0][0])*1)+(sizeof(array[0][0][0])*0),因为是第0个,*0其实就为0那么得到的就是这段地址的首地址,[0][1][0][0]就是0x40000+(sizeof(array[0][0])*1)+(sizeof(array[0][0][0])*0)+sizeof(array[0][0][0][0]*0),这样就得到5元素,可以看到后面两个其实加的都是0,因为在内存角度来说其实没有格子,格子只是我们抽象的理解,当你使用array[0][1]时,其实它输出的也是5,[0][1]对标[0][1][0][0],如果要从内存角度理解把格子去掉就可以了:
从内存角度来说,就是连续的,公式就是我刚刚给大家列的:0x40000+(sizeof(array[0][0])*1)+(sizeof(array[0][0][0])*0)+sizeof(array[0][0][0][0]*0),需要注意,array[0][0][0],会返回array[0][0][0]这一行的大小,也就是两个int,sizeof它计算时会将每组的大小全部返回,例如计算array[0],返回的实际上是第一个格子的全部大小:
也就是这一组的全部大小,如果想获取数组全部大小则把索引去除就可以了sizeof(array),这也是C语言的特性。