如何理解Go语言的数组

什么是数组

首先下一个定义,数组是对线性的内存区域的抽象。高维数组和一维数组有着同样的内存布局。(大学生考试的时候别借鉴哈,这是自己下的定义,相当于是一篇议论文的论点。)

线性的内存区域说白了就是连续的内存区域。无论一维数组、二维数组、N维数组都处在连续的内存区域中,数据排列是连续的。CPU缓存对连续的内存区域具有较高的亲和性,这也是数组的访问速度要快于链表的一个重要原因。

一维数组

以C语言中的一维数组为例:int array[3] = {1,2,3};,此时array保存的即数组的首地址,以该地址为首的连续的内存区域中,保存了1,2,3的值。

二维数组

二维数组可以理解成:是保存了一维数组首地址(指针)一维数组

这是什么意思呢?以下面三个一维数组:array0array1array2为例,(前提,他们仨处于一块连续的内存区域上,且首尾无间断),假设它们的首地址分别是:0x00000x000C0x0018。看一下这三个数字,它们每两个之间的差都是0xc。因为C语言中int类型占4个字节宽度。所以说每个一维数组占用的内存长度都是12个字节,并且array1数组首地址刚好是array0数组末地址+1的位置,array2和array1的关系亦是如此。

c 复制代码
int array0[3] = {1,2,3};
int array1[3] = {4,5,6};
int array2[3] = {7,8,9};

那么这片连续的内存区域可以表示为这个样子:

c 复制代码
   +----++----++----++----++----++----++----++----++----+
...|  1 ||  2 ||  3 ||  4 ||  5 ||  6 ||  7 ||  8 ||  9 |...
   +----++----++----++----++----++----++----++----++----+
   ⬆                 ⬆                 ⬆
   0x0000            0x000c            0x0018
   array0            array1            array2

使用C语言定义一个一维指针数组:
int *array[3] = {array0,array1,array2};,数组中保存三个一维数组的首地址。

用该数组指针模拟一下二维数组。

c 复制代码
#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");
    }
}

输出的内容是:

c 复制代码
1       2       3
4       5       6
7       8       9

他的内存布局是这样的:

c 复制代码
   +----++----++----++----++----++----++----++----++----+
...|  1 ||  2 ||  3 ||  4 ||  5 ||  6 ||  7 ||  8 ||  9 |...
   +----++----++----++----++----++----++----++----++----+
   ⬆                 ⬆                 ⬆
   0x0000            0x000c            0x0018
   array0            array1            array2
   
   +----------++----------++----------+
...|  0x0000  ||  0x000c  ||  0x0018  |...
   +----------++----------++----------+
   ⬆
   array

我们不搞的那么麻烦,直接用C语言定义一个普通的二维数组:

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");
    }
}

其打印内容也是:

c 复制代码
1       2       3
4       5       6
7       8       9

使用访问连续内存区域的方式*((*array)+i),打印该数组:

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 * 3; i++) {
        printf("%d\t",*((*array)+i));
    }
}

其输出内容为:

c 复制代码
1       2       3       4       5       6       7       8       9

其内存布局是这样的:

c 复制代码
   +----++----++----++----++----++----++----++----++----+
...|  1 ||  2 ||  3 ||  4 ||  5 ||  6 ||  7 ||  8 ||  9 |...
   +----++----++----++----++----++----++----++----++----+
   ⬆
   array
  • 可以看出,二维数组是将元素连续排列的一维数组。
  • 同理高维数组,也是将元素线性排列的一维数组。
  • 这也佐证了我们 数组是对线性的内存区域的抽象。高维数组和一维数组有着同样的内存布局 的观点。

如何理解Go语言的数组

Go语言中的数组,和C语言的数组大同小异,数组的首地址也指向了一片连续的内存区域。这里的数组指的是array,而不是slice

如何将一个一维数组映射成一个二维数组

仁者见仁,智者见智

第一种方式:

go 复制代码
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)
}

上述方式展示了利用计算下标的方式进行转化,其结果为:

go 复制代码
[[1 2 3 4] [5 6 7 8] [9 10 11 12]]

第二种方式,终极大杀器

go 复制代码
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)
}

结果为:

go 复制代码
[[1 2 3 4] [5 6 7 8] [9 10 11 12]]

这种强制转化的方式,是利用了高维数组和一维数组有着同样的内存布局的这个原理,直接在类型层面做了一层转化,此时array 是 zero copy的,也就是说跟原数组共用同一片内存。二者只要更改任意一方元素,都会影响到对方。

相关推荐
捕鲸叉26 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer30 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
码农小旋风31 分钟前
详解K8S--声明式API
后端
Peter_chq32 分钟前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml41 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~1 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616881 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
记录成长java2 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~2 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust