Go   发布时间:2022-04-09  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了golang: 常用数据类型底层结构分析大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

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

基础类型

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

/*
*basictypes
*/
typedefsignedcharint8;
typedefunsignedcharuint8;
typedefsignedshorTint16;
typedefunsignedshortuint16;
typedefsignedinTint32;
typedefunsignedintuint32;
typedefsignedlonglonginTint64;
typedefunsignedlonglongintuint64;
typedeffloatfloat32;
typedefdoublefloat64;

#ifdef_64BIT
typedefuint64uintptr;
typedefint64intptr;
typedefint64intgo;//Go'sint
typedefuint64uintgo;//Go'suint
#else
typedefuint32uintptr;
typedefint32intptr;
typedefint32intgo;//Go'sint
typedefuint32uintgo;//Go'suint
#endif

/*
*definedtypes
*/
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。可以看下测试:@H_618_1@

packagemain

import(
"fmt"
"reflect"
)

funcmain(){
varbbyte='D'
fmt.Printf("output:%v\n",reflect.TypeOf(b).Kind())
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
output:uint8

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

rune类型

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

String类型

String类型的底层是一个C struct。@H_618_1@

structString
{
byte*str;
intgolen;
};

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

$GOPATH/src@H_618_1@

----basictype_test@H_618_1@

--------main.go@H_618_1@

packagemain

import(
	"fmt"
	"unsafe"
)

funcmain(){
	varstrString="hi,陈一回~"
	p:=(*struct{
		struintptr
		lenint
	})(unsafe.Pointer(&str))

	fmt.Printf("%+v\n",p)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
output:&{str:135100456len:14}

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

slice类型

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

struct	Slice
{				//mustnotmoveanything
	byte*	array;		//actualdata
	uintgo	len;		//numberofelements
	uintgo	cap;		//allocatednumberofelements
};

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

packagemain

import(
	"fmt"
	"unsafe"
)

funcmain(){
	varslice[]int32=make([]int32,5,10)
	p:=(*struct{
		arrayuintptr
		lenint
		capint
	})(unsafe.Pointer(&slicE))

	fmt.Printf("output:%+v\n",p)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
output:&{array:406958176len:5cap:10}

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

packagemain

import(
	"fmt"
)

funcmain(){
	vararray=[...]int32{1,2,3,4,5}
	varslice=array[2:4]
	fmt.Printf("改变slice之前:array=%+v,slice=%+v\n",array,slicE)
	slice[0]=234
	fmt.Printf("改变slice之后:array=%+v,slicE)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
改变slice之前:array=[12345],slice=[34]
改变slice之后:array=[1223445],slice=[2344]

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

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

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

packagemain

import(
	"fmt"
)

funcmain(){
	vararray=[...]int32{1,5}
	varslice=array[2:4]
	slice=append(slice,6,7,8)
	fmt.Printf("改变slice之前:array=%+v,slicE)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
改变slice之前:array=[12345],slice=[34678]
改变slice之后:array=[12345],slice=[2344678]

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

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

packagemain

import(
	"fmt"
)

funcmain(){
	varslice1=[]int32{1,5}
	varslice2=slice1[2:4]
	fmt.Printf("改变slice2之前:slice1=%+v,slice2=%+v\n",slice1,slice2)
	slice2[0]=234
	fmt.Printf("改变slice2之后:slice1=%+v,slice2)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
改变slice2之前:slice1=[12345],slice2=[34]
改变slice2之后:slice1=[1223445],slice2=[2344]

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

packagemain

import(
	"fmt"
)

funcmain(){
	varslice1=[]int32{1,slice2)
	slice2=append(slice2,8)
	fmt.Printf("改变slice2之后:slice1=%+v,slice2=[34]
改变slice2之后:slice1=[12345],slice2=[34678]

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

接口类型

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

structType
{
	uintptrsize;
	uint32hash;
	uint8_unused;
	uint8align;
	uint8fieldAlign;
	uint8kind;
	Alg*alg;
	void*gc;
	String*String;
	UncommonType*x;
	Type*ptrto;
};

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

structIface
{
	Itab*	tab;
	void*	data;
};
structEface
{
	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 。@H_618_1@

packagemain

import(
	"fmt"
	"unsafe"
)

funcmain(){
	varStrinterface{}="HelloWorld!"
	p:=(*struct{
		tabuintptr
		datauintptr
	})(unsafe.Pointer(&str))

	fmt.Printf("%+v\n",p)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
output:&{tab:134966528data:406847688}
@H_123_4@map类型

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

structHmap
{
	uintgocount;
	uint32flags;
	uint32hash0;
	uint8B;
	uint8keysize;
	uint8valuesize;
	uint16bucketsize;

	byte*buckets;
	byte*oldbuckets;
	uintptrnevacuate;
};

测试代码如下:@H_618_1@

packagemain

import(
	"fmt"
	"unsafe"
)

funcmain(){
	varm=make(map[String]int32,10)
	m["Hello"]=123
	p:=(*struct{
		counTint
		flagsuint32
		hash0uint32
		Buint8
		keysizeuint8
		valuesizeuint8
		bucketsizeuint16

		bucketsuintptr
		oldbucketsuintptr
		nevacuateuintptr
	})(unsafe.Pointer(&m))

	fmt.Printf("output:%+v\n",p)
}
$cd$GOPATH/src/basictype_test
$gobuild
$./basictype_test
output:&{Count:407032064flags:0hash0:134958144B:192keysize:0valuesize:64bucketsize:30063buckets:540701813oldbuckets:0nevacuate:0}

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

大佬总结

以上是大佬教程为你收集整理的golang: 常用数据类型底层结构分析全部内容,希望文章能够帮你解决golang: 常用数据类型底层结构分析所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。