什么是数组
首先下一个定义,数组
是对线性的内存区域
的抽象。高维数组和一维数组有着同样的内存布局。(大学生考试的时候别借鉴哈,这是自己下的定义,相当于是一篇议论文的论点。)
线性的内存区域说白了就是连续的内存区域。无论一维数组、二维数组、N维数组都处在连续的内存区域中,数据排列是连续的。CPU缓存对连续的内存区域具有较高的亲和性,这也是数组
的访问速度要快于链表
的一个重要原因。
一维数组
以C语言中的一维数组为例:int array[3] = {1,2,3};
,此时array
保存的即数组的首地址
,以该地址为首的连续的内存区域中,保存了1,2,3
的值。
二维数组
二维数组可以理解成:是保存了一维数组首地址(指针)
的一维数组
。
这是什么意思呢?以下面三个一维数组:array0
、array1
、array2
为例,(前提,他们仨处于一块连续的内存区域上,且首尾无间断),假设它们的首地址分别是:0x0000
,0x000C
,0x0018
。看一下这三个数字,它们每两个之间的差都是0xc
。因为C
语言中int
类型占4个字节宽度。所以说每个一维数组占用的内存长度都是12个字节,并且array1数组首地址刚好是array0数组末地址+1的位置,array2和array1的关系亦是如此。
int array0[3] = {1,2,3};
int array1[3] = {4,5,6};
int array2[3] = {7,8,9};
那么这片连续的内存区域可以表示为这个样子:
+----++----++----++----++----++----++----++----++----+
...| 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 |...
+----++----++----++----++----++----++----++----++----+
⬆ ⬆ ⬆
0x0000 0x000c 0x0018
array0 array1 array2
使用C
语言定义一个一维指针数组:
int *array[3] = {array0,array1,array2};
,数组中保存三个一维数组的首地址。
用该数组指针模拟一下二维数组。
#include "stdio.h"
int main() {
int array0[3] = {1,2,3};
int array1[3] = {4,5,6};
int array2[3] = {7,8,9};
int *array[3] = {array0,array1,array2};
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++){
printf("%d\t",*(*array+j)+i*3);
}
printf("\n");
}
}
输出的内容是:
1 2 3
4 5 6
7 8 9
他的内存布局是这样的:
+----++----++----++----++----++----++----++----++----+
...| 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 |...
+----++----++----++----++----++----++----++----++----+
⬆ ⬆ ⬆
0x0000 0x000c 0x0018
array0 array1 array2
+----------++----------++----------+
...| 0x0000 || 0x000c || 0x0018 |...
+----------++----------++----------+
⬆
array
我们不搞的那么麻烦,直接用C
语言定义一个普通的二维数组:
#include "stdio.h"
int main() {
int array[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}};
for(int i = 0; i < 3; i++) {
for( int j = 0; j < 3; j++) {
printf("%d\t",array[i][j]);
}
printf("\n");
}
}
其打印内容也是:
1 2 3
4 5 6
7 8 9
使用访问连续内存区域的方式*((*array)+i)
,打印该数组:
#include "stdio.h"
int main() {
int array[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}};
for( int i = 0; i < 3 * 3; i++) {
printf("%d\t",*((*array)+i));
}
}
其输出内容为:
1 2 3 4 5 6 7 8 9
其内存布局是这样的:
+----++----++----++----++----++----++----++----++----+
...| 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 |...
+----++----++----++----++----++----++----++----++----+
⬆
array
- 可以看出,二维数组是将元素连续排列的一维数组。
- 同理高维数组,也是将元素线性排列的一维数组。
- 这也佐证了我们
数组
是对线性的内存区域
的抽象。高维数组和一维数组有着同样的内存布局 的观点。
如何理解Go语言的数组
Go语言中的数组,和C语言的数组大同小异,数组的首地址也指向了一片连续的内存区域。这里的数组指的是array
,而不是slice
。
如何将一个一维数组映射成一个二维数组
仁者见仁,智者见智
第一种方式:
package main
import "fmt"
func main() {
array0 := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var array [3][4]int
for i := 0; i < 12; i++ {
array[i/4][i%4] = array0[i]
}
fmt.Println(array)
}
上述方式展示了利用计算下标的方式进行转化,其结果为:
[[1 2 3 4] [5 6 7 8] [9 10 11 12]]
第二种方式,终极大杀器
package main
import (
"fmt"
"unsafe"
)
func main() {
array0 := [12]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
var array = *(*[3][4]int)(unsafe.Pointer(&array0))
fmt.Println(array)
}
结果为:
[[1 2 3 4] [5 6 7 8] [9 10 11 12]]
这种强制转化的方式,是利用了高维数组和一维数组有着同样的内存布局的这个原理,直接在类型层面做了一层转化,此时array 是 zero copy
的,也就是说跟原数组共用同一片内存。二者只要更改任意一方元素,都会影响到对方。