1.字符串
type stringStruct struct {
str unsafe.Pointer
len int
}
1.1字符串本质
字符串本质是个结构体,Data指针指向底层byte数组,字符串是不可变的。所以一旦创建了一个字符串就不能修改内容,如果要修改只能转换为可变[]byte类型然后在转为字符串类型。
比如以下两个例子
s:=“我爱你”
fmt.Println(unsafe.sizeof(s))
输出结果为16 因为len长度为指向字符串的指针,长度为16.
s:=“我爱你”
sh:=(*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println(sh.len)
输出的结果为9 , 编码为Unicode,底层汉字表示每位长度为3
UTF-8变长编码:128个US-ASCII只需要一个字节,西方常用字符为两个字节,其他需要三个字节。
1.2.访问字符串元素
s:=“我爱你”
for _,char :=range s{
fmt.Printf("%c\n",char)
}
不能直接访问s[i],因为每个字符可能不是占一个位,得到的是字节不是字符。
要用range方法访问,在字符串被遍历时自动解码为rune类型的字符。
则字符串的切分方法:转为rune数组—切片—转为string
s=string([]rune(s)[:5])
2.数组
数组是定长的,在声明数组时候必须指定数组的长度,并且一旦声明了长度固定不能动态变化。
2.1.数组的定义
var name[容量] int 类型,类型不能混合使用
2.2.数组的遍历
①for len(array)类型
strArr := [...]string{1: "Tom", 2: "Marry", 0: "Ming"}
fmt.Println("strArr=", strArr)
for i := 0; i < len(strArr); i++ {
fmt.Printf("strArr[%d]=%s\n", i, strArr[i])
}
②for range方法 for index , value := range array{}
3.Go的数组属于值类型,默认情况下是值传递,会进行拷贝,并且数组之间不会相互影响。
func test(arr [3]int) { //定义时不可用[...]int 必须的明确指定长度
arr[0] = 15
}
func main() {
//Go的数组属于**值类型**,在默认情况下是值传递,因此会进行值拷贝。
// **数组间不会相互影响**。
arr := [...]int{0: 11, 1: 12, 2: 13}
test(arr)// [11 12 13]
fmt.Println(arr)
}
输出:[11,12,13]
3.切片
- 一句话:切片就是对数组的引用,改变其值会改变切片的数组对象的值。
slice的数据结构
type slice struct {
array unsafe.Pointer //元素指针
len int //切片引用的部分
cap int //底层数组长度
}
3.1 切片的创建
①根据数组创建:arr[0:3],slice[0:3]
②字面量:slice=[]int{1,2,3}
底层实现:新建一个长度为3的数组ar,然后新建一个sliece结构体{arr,3,4},把三个数字塞进结构体里面。
③make方法: slice:=make([]int , 10)
底层实现:调用makeslice 根据传入的参数,返回切片的指针。
3.2 切片的扩容
1.不扩容时,只调整len(编译器负责)
2.len==cap ,需要扩容 编译器调用growslice()
扩容时要整体迁移到新的位置,先将cap翻倍,再讲底层数组复制到新的更大的数组之中,最后将元素插入到新的底层数组之中。(面试宝典里面说这个说法不准确,但是具体是多少我也没看明白就这样吧。)
- 如果原slice的容量小于1024,将容量翻倍。
- 如果原slice的容量大于1024,每次增加25%。
PS:为什么上面的说法不准确,就是在源码中调用了roundupsize,就是内存对齐的过程。在go中内存分配是根据对象的大小来分配不同的mspan,所以silce后的扩容时内存对齐之后的结果。
切片扩容的时候是并发不安全的!
3.3slice截取
Go语言中的切片还支持截取操作,可以通过切片来获取一个数组或者切片的一部分,但是稍有不慎就有很多的坑。slice[start:end],其中start表示开始位置,end表示结束位置,但不包括为end的元素。截取的结果是一个新的切片,它引用了原始切片中的一段连续内存区域。
下面举个例子吧
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := slice[2:5]
s2 := s1[2:7]
fmt.Printf("len=%-4d cap=%-4d slice=%-1v \n", len(slice), cap(slice), slice)
fmt.Printf("len=%-4d cap=%-4d s1=%-1v \n", len(s1), cap(s1), s1)
fmt.Printf("len=%-4d cap=%-4d s2=%-1v \n", len(s2), cap(s2), s2)
fmt.Println("--------append 200----------------")
s2 = append(s2, 200)
··
fmt.Println("--------append 200----------------")
s2 = append(s2, 200)
··
fmt.Println("--------modify s1----------------")
s1[2] = 20
··
}
结果如下
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 9]
len=3 cap=8 s1=[2 3 4]
len=5 cap=6 s2=[4 5 6 7 8]
--------append 200----------------
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 200]
len=3 cap=8 s1=[2 3 4]
len=6 cap=6 s2=[4 5 6 7 8 200]
--------append 200----------------
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 200]
len=3 cap=8 s1=[2 3 4]
len=7 cap=12 s2=[4 5 6 7 8 200 200]
--------modify s1----------------
len=10 cap=10 slice=[0 1 2 3 20 5 6 7 8 200]
len=3 cap=8 s1=[2 3 20]
len=7 cap=12 s2=[4 5 6 7 8 200 200]
分析可知:
可以看到在第二次append200的时候,s2的cap已经不够所以需要扩容,底层数组发生了复制系统分配了一块新的内存地址给s2,s2也完成了容量的翻倍。接下来在对s2进行append操作也不会改变slice和s1.
但是此时改变s1,影响了slice,因为两者还是公用一个底层数组,s2未改变是因为在上一层的时候底层数组已经发生了变化,所以说呢,切片的截取很大的坑,小心使用。