如何理解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的,也就是说跟原数组共用同一片内存。二者只要更改任意一方元素,都会影响到对方。

相关推荐
重生之我在20年代敲代码14 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文16 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people19 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
2401_858286113 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py3 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
C-SDN花园GGbond5 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
罗政6 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
迷迭所归处6 小时前
C++ —— 关于vector
开发语言·c++·算法