|
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
|
<channel>
|
|
|
<title>数据类型与数据结构 on 深入Go语言之旅</title>
|
|
|
<link>https://go.cyub.vip/type/</link>
|
|
|
<description>Recent content in 数据类型与数据结构 on 深入Go语言之旅</description>
|
|
|
<generator>Hugo -- gohugo.io</generator>
|
|
|
<language>zh-cn</language><atom:link href="https://go.cyub.vip/type/index.xml" rel="self" type="application/rss+xml" />
|
|
|
<item>
|
|
|
<title>nil类型</title>
|
|
|
<link>https://go.cyub.vip/type/nil/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/nil/</guid>
|
|
|
<description>nil # 在探究 nil 之前,我们先看看零值的概念。
|
|
|
零值 # 零值(zero value)1 指的是当声明变量且未显示初始化时,Go语言会自动给变量赋予一个默认初始值。对于值类型变量来说不同值类型,有不同的零值,比如整数型零值是 0,字符串类型是 &quot;&quot;,布尔类型是 false。对于引用类型变量其零值都是 nil。
|
|
|
类型 零值 数值类型 0 字符串 &quot;&quot; 布尔类型 false 指针类型 nil 通道 nil 函数 nil 接口 nil 映射 nil 切片 nil 结构体 每个结构体字段对应类型的零值 自定义类型 其底层类型的对应的零值 从零值的定义,可以看出Go语言引入 nil 概念,是为了将其作为引用类型变量的零值而存在。
|
|
|
nil # nil 是Go语言中的一个变量,是预先声明的标识符,用来作为引用类型变量的零值。
|
|
|
// nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. var nil Type // Type must be a pointer, channel, func, interface, map, or slice type nil 不能通过:=方式赋值给一个变量,下面代码是编译不通过的:</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>切片</title>
|
|
|
<link>https://go.cyub.vip/type/slice/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/slice/</guid>
|
|
|
<description>切片 # 切片是Go语言中最常用的数据类型之一,它类似数组,但相比数组它更加灵活,高效,由于它本身的特性,往往也更容易用错。
|
|
|
不同于数组是值类型,而切片是引用类型。虽然两者作为函数参数传递时候都是值传递(pass by value),但是切片传递的包含数据指针(可以细分为pass by pointer),如果切片使用不当,会产生意想不到的副作用。
|
|
|
初始化 # 切片的初始化方式可以分为三种:
|
|
|
使用make函数创建切片
|
|
|
make函数语法格式为:make([]T, length, capacity),capacity可以省略,默认等于length
|
|
|
使用字面量创建切片
|
|
|
从数组或者切片派生(reslice)出新切片
|
|
|
Go支持从数组、指向数组的指针、切片类型变量再reslice一个新切片。
|
|
|
reslice操作语法可以是[]T[low : high],也可以是[]T[low : high : max]。其中low,high,max都可以省略,low默认值是0,high默认值cap([]T),max默认值cap([]T)。low,hight,max取值范围是0 &lt;= low &lt;= high &lt;= max &lt;= cap([]T),其中high-low是新切片的长度,max-low是新切片的容量。
|
|
|
对于[]T[low : high],其包含的元素是[]T中下标low开始,到high结束(不含high所在位置的,相当于左闭右开[low, high))的元素,元素个数是high - low个,容量是cap([]T) - low。
|
|
|
func main() { slice1 := make([]int, 0) slice2 := make([]int, 1, 3) slice3 := []int{} slice4 := []int{1: 2, 3} arr := []int{1, 2, 3} slice5 := arr[1:2] slice6 := arr[1:2:2] slice7 := arr[1:] slice8 := arr[:1] slice9 := arr[3:] slice10 := slice2[1:2] fmt.</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>字符串</title>
|
|
|
<link>https://go.cyub.vip/type/string/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/string/</guid>
|
|
|
<description>字符串 # 我们知道C语言中的字符串是使用字符数组 char[] 表示,字符数组的最后一位元素是 \0,用来标记字符串的结束。C语言中字符串的结构简单,但获取字符串长度时候,需要遍历字符数组才能完成。
|
|
|
Go语言中字符串的底层结构中也包含了字符数组,该字符数组是完整的字符串内容,它不同于C语言,字符数组中没有标记字符串结束的标记。为了记录底层字符数组的大小,Go语言使用了额外的一个长度字段来记录该字符数组的大小,字符数组的大小也就是字符串的长度。
|
|
|
数据结构 # Go语言字符串的底层数据结构是 reflect.StringHeader( reflect/value.go),它包含了指向字节数组的指针,以及该指针指向的字符数组的大小:
|
|
|
type StringHeader struct { Data uintptr Len int } 字符串复制 # 当将一个字符串变量赋值给另外一个变量时候,他们 StringHeader.Data 都指向同一个内存地址,不会发生字符串拷贝:
|
|
|
a := &#34;hello&#34; b := a 从上图中我们可以看到a变量和b变量的Data字段存储的都是0x1234,而0x1234是字符数组的起始地址。
|
|
|
接来下我们借助 GDB 工具来验证Go语言中字符串数据结构是不是按照上面说的那样。
|
|
|
package main import ( &#34;fmt&#34; ) func main() { a := &#34;hello&#34; b := a fmt.Printf(&#34;a变量地址:%p\n&#34;, &amp;a) fmt.Printf(&#34;b变量地址:%p\n&#34;, &amp;b) print(&#34;断点打在这里&#34;) } 将上面代码构建二进制应用, 然后使用 GDB 调试一下:
|
|
|
go build -o string string.go # 构建二进制应用 gdb .</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>指针</title>
|
|
|
<link>https://go.cyub.vip/type/pointer/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/pointer/</guid>
|
|
|
<description>指针 # Golang支持指针,但是不能像C语言中那样进行算术运算。对于任意类型T,其对应的的指针类型是*T,类型T称为指针类型*T的基类型。
|
|
|
引用与解引用 # 一个指针类型*T变量B存储的是类型T变量A的内存地址,我们称该指针类型变量B引用(reference)了A。从指针类型变量B获取(或者称为访问)A变量的值的过程,叫解引用。解引用是通过解引用操作符*操作的。
|
|
|
func main() { var A int = 100 var B *int = &amp;A fmt.Println(A == *B) } 转换和可比较性 # 对于指针类型变量能不能够比较和显示转换需要满足以下规则:
|
|
|
指针类型*T1和*T2相应的基类型T1和T2的底层类型必须一致。 type MyInt int type PInt *int type PMyInt *MyInt func main() { p1 := new(int) var p2 PInt = p1 // p2底层类型是*int p3 := new(MyInt) var p4 PMyInt = p3 // p4底层类型是*MyInt fmt.Println(p1, p2, p3, p4) } uintptr # uintptr是一个足够大的整数类型,能够存放任何指针。不同C语言,Go语言中普通类型指针不能进行算术运算,我们可以将普通类型指针转换成uintptr然后进行运算,但普通类型指针不能直接转换成uintptr,必须先转换成unsafe.Pointer类型之后,再转换成uintptr。
|
|
|
// uintptr is an integer type that is large enough to hold the bit pattern of // any pointer.</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>数组</title>
|
|
|
<link>https://go.cyub.vip/type/array/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/array/</guid>
|
|
|
<description>数组 # 数组是Go语言中常见的数据结构,相比切片,数组我们使用的比较少。
|
|
|
初始化 # Go语言数组有两个声明初始化方式,一种需要显示指明数组大小,另一种使用 ...保留字, 数组的长度将由编译器在编译阶段推断出来:
|
|
|
arr1 := [3]int{1, 2, 3} // 使用[n]T方式 arr2 := [...]int{1, 2, 3} // 使用[...]T方式 arr3 := [3]int{2: 3} // 使用[n]T方式 arr4 := [...]int{2: 3} // 使用[...]T方式 注意:
|
|
|
上面代码中 arr3 和 arr4 的初始化方式是指定数组索引对应的值。实际使用中这种方式并不常见。
|
|
|
可比较性 # 数组大小是数组类型的一部分,只有数组大小和数组元素类型一样的数组才能够进行比较。
|
|
|
func main() { var a1 [3]int var a2 [3]int var a3 [5]int fmt.Println(a1 == a2) // 输出true fmt.Println(a1 == a3) // 不能够比较,会报编译错误: invalid operation: a1 == a3 (mismatched types [3]int and [5]int) } 值类型 # Go语言中数组是一个值类型变量,将一个数组作为函数参数传递是拷贝原数组形成一个新数组传递,在函数里面对数组做任何更改都不会影响原数组:</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>映射类型</title>
|
|
|
<link>https://go.cyub.vip/type/map/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/map/</guid>
|
|
|
<description>映射 # 映射也被称为哈希表(hash table)、字典。它是一种由key-value组成的抽象数据结构。大多数情况下,它都能在O(1)的时间复杂度下实现增删改查功能。若在极端情况下出现所有key都发生哈希碰撞时则退回成链表形式,此时复杂度为O(N)。
|
|
|
映射底层一般都是由数组组成,该数组每个元素称为桶,它使用hash函数将key分配到不同桶中,若出现碰撞冲突时候,则采用链地址法(也称为拉链法)或者开放寻址法解决冲突。下图就是一个由姓名-号码构成的哈希表的结构图:
|
|
|
Go语言中映射中key若出现冲突碰撞时候,则采用链地址法解决,Go语言中映射具有以下特点:
|
|
|
引用类型变量 读写并发不安全 遍历结果是随机的 数据结构 # Go语言映射的数据结构 Go语言中映射的数据结构是 runtime.hmap( runtime/map.go):
|
|
|
// A header for a Go map. type hmap struct { count int // 元素个数,用于len函数返回map元素数量 flags uint8 // 标志位,标志当前map正在写等状态 B uint8 // buckets个数的对数,即桶数量 = 2 ^ B noverflow uint16 // overflow桶数量的近似值,overflow桶即溢出桶,即链表法中存在链表上的桶的个数 hash0 uint32 // 随机数种子,用于计算key的哈希值 buckets unsafe.Pointer // 指向buckets数组,如果元素个数为0时,该值为nil oldbuckets unsafe.Pointer // 扩容时指向旧的buckets nevacuate uintptr // 用于指示迁移进度,小于此值的桶已经迁移完成 extra *mapextra // 额外记录overflow桶信息 } 映射中每一个桶的结构是 runtime.bmap( runtime/map.</description>
|
|
|
</item>
|
|
|
|
|
|
<item>
|
|
|
<title>空结构体</title>
|
|
|
<link>https://go.cyub.vip/type/empty_struct/</link>
|
|
|
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
|
|
|
|
|
<guid>https://go.cyub.vip/type/empty_struct/</guid>
|
|
|
<description>空结构体 # 空结构体指的是没有任何字段的结构体。
|
|
|
大小与内存地址 # 空结构体占用的内存空间大小为零字节,并且它们的地址可能相等也可能不等。当发生内存逃逸时候,它们的地址是相等的,都指向了 runtime.zerobase。
|
|
|
// empty_struct.go type Empty struct{} //go:linkname zerobase runtime.zerobase var zerobase uintptr // 使用go:linkname编译指令,将zerobase变量指向runtime.zerobase func main() { a := Empty{} b := struct{}{} fmt.Println(unsafe.Sizeof(a) == 0) // true fmt.Println(unsafe.Sizeof(b) == 0) // true fmt.Printf(&#34;%p\n&#34;, &amp;a) // 0x590d00 fmt.Printf(&#34;%p\n&#34;, &amp;b) // 0x590d00 fmt.Printf(&#34;%p\n&#34;, &amp;zerobase) // 0x590d00 c := new(Empty) d := new(Empty) fmt.Sprint(c, d) // 目的是让变量c和d发生逃逸 println(c) // 0x590d00 println(d) // 0x590d00 fmt.Println(c == d) // true e := new(Empty) f := new(Empty) println(e) // 0xc00008ef47 println(f) // 0xc00008ef47 fmt.</description>
|
|
|
</item>
|
|
|
|
|
|
</channel>
|
|
|
</rss>
|