golang: 常用数据类型底层结构分析

【Go语言学习】——go 数据结构底层原理 go中的数组是由固定长度的特定类型元素组成的序列,数组的长度是数据类型的组成方式,所以不同长度和不同类型的元素组成的数组是不同的数组类型。数组属于值类型,因此在复制或者传递参数时,会对整个数组内容进行复制,所以在调用的函数中修改数组的值不会影响到原来的数组的值。此外由于需要复制整个数组的内容,如果数组太大会导致复制成本太大, 所以可以传递数组的指针。简单的将,切片是一种简化版的动态数组。切片的在go中的定义为如下,在对切片赋值,就是修改指向数组的指针,len,cap的值。而在拷贝的时候,如果直接使用=,则会 阅读详情

转自 http://www.cnblogs.com/moodlxs/p/4133121.html


虽然golang是用C实现的,并且被称为下一代的C语言,但是golang跟C的差别还是很大的。它定义了一套很丰富的数据类型及数据结构,这些类型和结构或者是直接映射为C的数据类型,或者是用C struct来实现。了解golang的数据类型和数据结构的底层实现,将有助于我们更好的理解golang并写出质量更好的代码。

基础类型

源码在:$GOROOT/src/pkg/runtime/runtime.h 。我们先来看下基础类型:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

/*

 * basic types

 */

typedef signed char             int8;

typedef unsigned char           uint8;

typedef signed short            int16;

typedef unsigned short          uint16;

typedef signed int              int32;

typedef unsigned int            uint32;

typedef signed long long int    int64;

typedef unsigned long long int  uint64;

typedef float                   float32;

typedef double                  float64;

 

#ifdef _64BIT

typedef uint64          uintptr;

typedef int64           intptr;

typedef int64           intgo; // Go's int

typedef uint64          uintgo; // Go's uint

#else

typedef uint32          uintptr;

typedef int32           intptr;

typedef int32           intgo; // Go's int

typedef uint32          uintgo; // Go's uint

#endif

 

/*

 * defined types

 */

typedef uint8           bool;

typedef uint8           byte;

int8、uint8、int16、uint16、int32、uint32、int64、uint64、float32、float64分别对应于C的类型,这个只要有C基础就很容易看得出来。uintptr和intptr是无符号和有符号的指针类型,并且确保在64位平台上是8个字节,在32位平台上是4个字节,uintptr主要用于golang中的指针运算。而intgo和uintgo之所以不命名为int和uint,是因为int在C中是类型名,想必uintgo是为了跟intgo的命名对应吧。intgo和uintgo对应golang中的int和uint。从定义可以看出int和uint是可变大小类型的,在64位平台上占8个字节,在32位平台上占4个字节。所以如果有明确的要求,应该选择int32、int64或uint32、uint64。byte类型的底层类型是uint8。可以看下测试:

?

1

2

3

4

5

6

7

8

9

10

11

package main

 

import (

        "fmt"

        "reflect"

)

 

func main() {

        var b byte = 'D'

        fmt.Printf("output: %v\n", reflect.TypeOf(b).Kind())

}

?

1

2

3

4

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

output: uint8

数据类型分为静态类型和底层类型,相对于以上代码中的变量b来说,byte是它的静态类型,uint8是它的底层类型。这点很重要,以后经常会用到这个概念。

rune类型

rune是int32的别名,用于表示unicode字符。通常在处理中文的时候需要用到它,当然也可以用range关键字。

string类型

string类型的底层是一个C struct。

?

1

2

3

4

5

struct String

{

        byte*   str;

        intgo   len;

};

成员str为字符数组,len为字符数组长度。golang的字符串是不可变类型,对string类型的变量初始化意味着会对底层结构的初始化。至于为什么str用byte类型而不用rune类型,这是因为golang的for循环对字符串的遍历是基于字节的,如果有必要,可以转成rune切片或使用range来迭代。我们来看个例子:

$GOPATH/src

----basictype_test

--------main.go

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package main

 

import (

    "fmt"

    "unsafe"

)

 

func main() {

    var str string = "hi, 陈一回~"

    p := (*struct {

        str uintptr

        len int

    })(unsafe.Pointer(&str))

 

    fmt.Printf("%+v\n", p)

}

?

1

2

3

4

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

output: &{str:135100456 len:14}

内建函数len对string类型的操作是直接从底层结构中取出len值,而不需要额外的操作,当然在初始化时必需同时初始化len的值。

slice类型

slice类型的底层同样是一个C struct。

?

1

2

3

4

5

6

struct  Slice

{               // must not move anything

    byte*   array;      // actual data

    uintgo  len;        // number of elements

    uintgo  cap;        // allocated number of elements

};

包括三个成员。array为底层数组,len为实际存放的个数,cap为总容量。使用内建函数make对slice进行初始化,也可以类似于数组的方式进行初始化。当使用make函数来对slice进行初始化时,第一个参数为切片类型,第二个参数为len,第三个参数可选,如果不传入,则cap等于len。通常传入cap参数来预先分配大小的slice,避免频繁重新分配内存。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package main

 

import (

    "fmt"

    "unsafe"

)

 

func main() {

    var slice []int32 = make([]int32, 5, 10)

    p := (*struct {

        array uintptr

        len   int

        cap   int

    })(unsafe.Pointer(&slice))

 

    fmt.Printf("output: %+v\n", p)

}

?

1

2

3

4

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

output: &{array:406958176 len:5 cap:10}

由于切片指向一个底层数组,并且可以通过切片语法直接从数组生成切片,所以需要了解切片和数组的关系,否则可能就会不知不觉的写出有bug的代码。比如有如下代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

package main

 

import (

    "fmt"

)

 

func main() {

    var array = [...]int32{1, 2, 3, 4, 5}

    var slice = array[2:4]

    fmt.Printf("改变slice之前: array=%+v, slice=%+v\n", array, slice)

    slice[0] = 234

    fmt.Printf("改变slice之后: array=%+v, slice=%+v\n", array, slice)

}

?

1

2

3

4

5

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

改变slice之前: array=[1 2 3 4 5], slice=[3 4]

改变slice之后: array=[1 2 234 4 5], slice=[234 4]

您可以清楚的看到,在改变slice后,array也被改变了。这是因为slice通过数组创建的切片指向这个数组,也就是说这个slice的底层数组就是这个array。因此很显然,slice的改变其实就是改变它的底层数组。当然如果删除或添加元素,那么len也会变化,cap可能会变化。

那这个slice是如何指向array呢?slice的底层数组指针指向array中索引为2的元素(因为切片是通过array[2:4]来生成的),len记录元素个数,而cap则等于len。

之所以说cap可能会变,是因为cap表示总容量,添加或删除操作不一定会使总容量发生变化。我们接着再来看另一个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

package main

 

import (

    "fmt"

)

 

func main() {

    var array = [...]int32{1, 2, 3, 4, 5}

    var slice = array[2:4]

    slice = append(slice, 6, 7, 8)

    fmt.Printf("改变slice之前: array=%+v, slice=%+v\n", array, slice)

    slice[0] = 234

    fmt.Printf("改变slice之后: array=%+v, slice=%+v\n", array, slice)

}

?

1

2

3

4

5

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

改变slice之前: array=[1 2 3 4 5], slice=[3 4 6 7 8]

改变slice之后: array=[1 2 3 4 5], slice=[234 4 6 7 8]

经过append操作之后,对slice的修改并未影响到array。原因在于append的操作令slice重新分配底层数组,所以此时slice的底层数组不再指向前面定义的array。

但是很显然,这种规则对从切片生成的切片也是同样的,请看代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

package main

 

import (

    "fmt"

)

 

func main() {

    var slice1 = []int32{1, 2, 3, 4, 5}

    var slice2 = slice1[2:4]

    fmt.Printf("改变slice2之前: slice1=%+v, slice2=%+v\n", slice1, slice2)

    slice2[0] = 234

    fmt.Printf("改变slice2之后: slice1=%+v, slice2=%+v\n", slice1, slice2)

}

?

1

2

3

4

5

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

改变slice2之前: slice1=[1 2 3 4 5], slice2=[3 4]

改变slice2之后: slice1=[1 2 234 4 5], slice2=[234 4]

slice1和slice2共用一个底层数组,修改slice2的元素导致slice1也发生变化。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

package main

 

import (

    "fmt"

)

 

func main() {

    var slice1 = []int32{1, 2, 3, 4, 5}

    var slice2 = slice1[2:4]

    fmt.Printf("改变slice2之前: slice1=%+v, slice2=%+v\n", slice1, slice2)

    slice2 = append(slice2, 6, 7, 8)

    fmt.Printf("改变slice2之后: slice1=%+v, slice2=%+v\n", slice1, slice2)

}

?

1

2

3

4

5

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

改变slice2之前: slice1=[1 2 3 4 5], slice2=[3 4]

改变slice2之后: slice1=[1 2 3 4 5], slice2=[3 4 6 7 8]

而append操作可令slice1或slice2重新分配底层数组,因此对slice1或slice2执行append操作都不会相互影响。

接口类型

接口在golang中的实现比较复杂,在$GOROOT/src/pkg/runtime/type.h中定义了:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

struct Type

{

    uintptr size;

    uint32 hash;

    uint8 _unused;

    uint8 align;

    uint8 fieldAlign;

    uint8 kind;

    Alg *alg;

    void *gc;

    String *string;

    UncommonType *x;

    Type *ptrto;

};

在$GOROOT/src/pkg/runtime/runtime.h中定义了:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

struct Iface

{

    Itab*   tab;

    void*   data;

};

struct Eface

{

    Type*   type;

    void*   data;

};

struct  Itab

{

    InterfaceType*  inter;

    Type*   type;

    Itab*   link;

    int32   bad;

    int32   unused;

    void    (*fun[])(void);

};

interface实际上是一个结构体,包括两个成员,一个是指向数据的指针,一个包含了成员的类型信息。Eface是interface{}底层使用的数据结构。因为interface中保存了类型信息,所以可以实现反射。反射其实就是查找底层数据结构的元数据。完整的实现在:$GOROOT/src/pkg/runtime/iface.c 。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package main

 

import (

    "fmt"

    "unsafe"

)

 

func main() {

    var str interface{} = "Hello World!"

    p := (*struct {

        tab  uintptr

        data uintptr

    })(unsafe.Pointer(&str))

 

    fmt.Printf("%+v\n", p)

}

?

1

2

3

4

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

output: &{tab:134966528 data:406847688}

map类型

golang的map实现是hashtable,源码在:$GOROOT/src/pkg/runtime/hashmap.c 。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

struct Hmap

{

    uintgo  count;

    uint32  flags;

    uint32  hash0;

    uint8   B;

    uint8   keysize;

    uint8   valuesize;

    uint16  bucketsize;

 

    byte    *buckets;

    byte    *oldbuckets;

    uintptr nevacuate;

};

测试代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

package main

 

import (

    "fmt"

    "unsafe"

)

 

func main() {

    var m = make(map[string]int32, 10)

    m["hello"] = 123

    p := (*struct {

        count      int

        flags      uint32

        hash0      uint32

        B          uint8

        keysize    uint8

        valuesize  uint8

        bucketsize uint16

 

        buckets    uintptr

        oldbuckets uintptr

        nevacuate  uintptr

    })(unsafe.Pointer(&m))

 

    fmt.Printf("output: %+v\n", p)

}

?

1

2

3

4

$ cd $GOPATH/src/basictype_test

$ go build

$ ./basictype_test

output: &{count:407032064 flags:0 hash0:134958144 B:192 keysize:0 valuesize:64 bucketsize:30063 buckets:540701813 oldbuckets:0 nevacuate:0}

golang的坑还是比较多的,需要深入研究底层,否则很容易掉坑里。


ResNet改进(45):结合通道混洗(ShuffleNet)的混合架构 本文提出了一种创新的卷积神经网络(CNN)架构CustomResNet,它将ResNet34与通道混洗(ChannelShuffle)技术相结合。该模型通过ShuffleBlock模块替换原始ResNet层,包含三个核心组件:1) ChannelShuffle模块实现通道分组混洗;2) ShuffleBlock模块整合点卷积、分组卷积和残差连接;3) CustomResNet主模型保持ResNet整体架构但替换核心层为ShuffleBlock。这种设计既保留了ResNet的残差学习能力,又通过通道混洗增强特 阅读详情

相关推荐

Colab虚拟环境配置全攻略:从安装到实战应用

本文详细介绍了在Google Colab中配置Python虚拟环境的完整流程与实战技巧。通过安装Anaconda、创建独立环境、解决常见错误及结合深度学习项目应用,帮助用户有效管理依赖、避免冲突,并利用环境持久化策略提升在Colab中的开发效率与项目可复现性。

weixin_29204121的博客 342

【Go】Go语言数据类型

Go语言数据类型介绍

weixin_44211968的博客 3859

电磁仿真新手必看:如何用CST Studio Suite 2023完成第一个PCB辐射仿真?

本文详细解析了如何使用CST Studio Suite 2023进行PCB辐射仿真,适合电磁仿真新手快速上手。从工程准备、求解器配置到端口设置和后处理技巧,全面覆盖三维全波电磁场仿真软件的核心操作步骤,帮助工程师精准预测和解决PCB电磁辐射问题。

y7z8a9的博客 734

【Go语言底层真相曝光】:它真是用C语言开发的吗?揭秘编程语言背后的惊天秘密

wu

DebugLoom的博客 510

学习笔记9--智能驾驶的功能安全设计之功能安全设计

本系列博客包括6个专栏,分别为:《自动驾驶技术概览》、《自动驾驶汽车平台技术基础》、《自动驾驶汽车定位技术》、《自动驾驶汽车环境感知》、《自动驾驶汽车决策与控制》、《自动驾驶系统设计及应用》。此专栏是关于《自动驾驶系统设计及应用》书籍的笔记.

FUXI_Willard 1795

使用Go语言交叉编译开发嵌入式Linux应用程序

Go语言非常适合用来开发存储空间不紧张的嵌入式Linux设备应用程序,可以在性能需求和开发速度上找到一个不错的平衡点。这篇文章将介绍使用Go语言来开发嵌入式Linux应用程序相关内容。

Naisu的各种笔记 9947

Windows应急响应实战:灰鸽子木马检测与清除全流程解析

远程控制木马(RAT)是一种基于客户端/服务器(C/S)架构的恶意软件,攻击者通过它能够远程操控受害主机,实现屏幕监控、文件窃取、键盘记录等操作,对个人隐私与企业数据安全构成严重威胁。其技术原理通常涉及进程注入、网络通信、持久化机制等核心环节,在应急响应中具有极高的分析价值。掌握此类木马的检测与清除方法,是Windows系统安全防护与事件处置的基本功。本文以经典的灰鸽子木马为例,结合进程分析、网络排查、注册表检查等实战技术,系统讲解从行为识别、痕迹取证到彻底清除的完整操作流程,并涵盖Rootkit对抗、无文

weixin_30521161的博客 415

Go语言 常用数据类型底层结构分析

虽然golang是用C实现的,并且被称为下一代的C语言,但是golang跟C的差别还是很大的。它定义了一套很丰富的数据类型及数据结构,这些类型和结构或者是直接映射为C的数据类型,或者是用C struct来实现。了解golang的数据类型和数据结构的底层实现,将有助于我们更好的理解golang并写出质量更好的代码。 基础类型 源码在:$GOROOT/src/pkg/runtime/r

Sunface撩技术 3557

从零构建法律案例数据库:Python爬虫实战与法律文本挖掘全攻略

在当今数字化浪潮席卷全球的背景下,法律行业正经历着前所未有的变革。法律案例数据库作为法律大数据的重要组成部分,已经成为法律从业者、研究人员、法学院学生乃至企业法务部门的必备工具。然而,商业法律数据库如北大法宝、威科先行、LexisNexis等往往价格不菲,且数据访问受限。对于个人研究者或小型机构而言,构建一个定制化的法律案例数据库不仅能节省成本,更能根据特定研究需求灵活调整数据结构。

2201_76125261的博客 539

RV1126 ROS2环境交叉编译及部署(基于官方Docker)

我的所有基于`RV1126`的机器人项目都是在`Ubuntu22.04`下进行开发的,在`Ubuntu18.04 Docker`容器中实现`RV1126`的`SDK`编译,详细可参考我之前的博客[《Ubuntu20.04/22.04下Docker方案实现多平台SDK编译》第2章](https://blog.youkuaiyun.com/w1820020635/article/details/147342689),`RV1126`的`ROS1`部署没什么难度,可以参考我之前的博客,这里记录了`ROS2`交叉编译及问题解决

w1820020635的博客 1143

golang中Channel详解

机制无缓冲 Channelselect的作用发送阻塞无接收者时阻塞尝试发送,失败则跳过或执行default接收阻塞无发送者时阻塞尝试接收,失败则跳过或执行default死锁风险同一协程连续读写会导致死锁通过非阻塞机制避免死锁适用场景需要严格同步的简单场景需要非阻塞或多路复用的复杂场景通过select,可以避免无缓冲 Channel 的死锁问题,同时实现更灵活的并发控制。

2301_78961159的博客 2550

从底层结构开始学习FPGA(7)----进位链CARRY4

进位链CARRY4在FPGA实现加减法运算时扮演了什么角色?

孤独的单刀 1万+

【Go】Go数据类型详解—数组与切片

本文详细介绍了Go语言的数组和切片数据类型,不仅介绍了基本语法,还介绍了高阶知识比如切片的底层实现、append扩容原理

weixin_62533201的博客 1265

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Go语言数据类型的并发安全

go语言数据类型的并发安全

huan 1134

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Go语言面试题

1.Go有哪些数据类型 2.方法与函数的区别 在Go语言中,函数和方法不太一样,有明确的概念区分;函数是指不属于任何结构体、类型的方法,也就是说函数是没有接收者的;而方法是有接收者的。 3.方法值接收者和指针接收者的区别 如果方法的接收者是指针类型,无论调用者是对象还是对象指针,修改的都是对象本身,会影响调用者; 如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者; 4.函数返回局部变量的指针是否安全 一般来说,局部变量会在函数返回后被销毁,因此被返回的引用就成为了

wang的博客 8069

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Golang之接口底层分析

GoLang之接口的动态类型和动态值 【引申1】接口类型和 作比较 【引申2】 【引申3】如何打印出接口的动态类型和值? GoLang之编译器自动检测类型是否实现接口 GoLang之接口的构造过程是怎样的 汇编行数 操作 10-14 构造调用 的参数 汇编行数 操作 15 调用 【引申1】

qq_53267860的博客 1718

Golang 数据类型

基础类型注重实用性和性能,如浮点数的容差处理、原始字符串的便利性复合类型在简洁性和性能之间找到平衡,如切片的动态特性、Map 的高效实现内存管理充分考虑现代计算机体系结构,优化缓存局部性和内存对齐理解这些类型的底层原理不仅有助于写出更高效的代码,更能帮助我们在设计系统时做出更好的技术决策。在实际开发中,应根据具体场景选择合适的数据类型和操作方法,充分发挥 Go 语言的性能优势。

Cloud_Mount的博客 783

golang 打印slice_好未来 Golang 源码系列二:Slice实现原理分析

分享老师:学而思网校 何海涛来源:学而思网校技术团队众所周知,在golang中,slice(切片)是我们最常使用到的一种数据结构,是一种可变长度的数组,本篇文章我们主要结合源码来介绍一下slice的底层实现,以及在使用slice时的一些注意事项。Slice结构体首先我们来看一段代码:package mainimport ( "fmt" "unsafe")func main() { ...

weixin_29525745的博客 1221

Golang 中 Slice的分析与使用(含源码)

Golang 中Slice 分析与使用(含源码解读)

zhw21w的博客 1099

GO语言接口数据结构

接口 接口的底层结构 runtime\runtime2.go.iface 是带方法签名的接口在运行时的具体结构 // 是带方法签名的接口在运行时的具体结构 type iface struct { // 存储接口的类型、动态数据类型、动态数据类型的函数指针 《重点》 tab *itab // 存储了接口中动态类型的函数指针 data unsafe.Pointer } 接口底层结构的完整图像 type itab struct { inter *interfacetype //

Voryla的博客 725
上一篇: 常用排序算法笔记之冒泡排序、快速排序
下一篇: Sublime Text 2支持GB2312和GBK,解决中文显示乱码问题
碎炎
博客等级 码龄16年 85粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值