go中的 字符串,数组,切片

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(编译器负责)
slice底层结构
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未改变是因为在上一层的时候底层数组已经发生了变化,所以说呢,切片的截取很大的坑,小心使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔可南-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值