Go语言学习与测试(1)——string类型

1. 内部结构

type StringView struct {
	strBuf uintptr
	strLen uint64
    //strCap uint64 !不存在cap
}

        golang在对于string设计上,把内存分配的控制权完全交给了runtime的相关函数,所以没有strCap的字段。个人认为golang的string,可以对应于C++中的string_view(C++17 后支持),只不过在C++中,最终字符串的内存回收还是交由程序员自己控制。

        不妨来做一些测试来验证想法:

1.1 通过unsafe暴力获取

(1)验证赋值行为,代码中s2仅复制了s1中的buf地址和长度

type StringView struct {
	strBuf uintptr
	strLen uint64
}

func main() {
	s1 := "123"
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
	s2 := s1
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
}

        结果: 

 (2)  验证新的字符串赋值,由于Ken Thomp参与了C和Golang的设计,所以猜测"123",“4567”都属于静态常量(直接放在可执行文件的数据段),它们二者之间的地址相距应很小,而且只要程序运行,它们就不会被GC(这点⚠️,仅仅是猜测)。

type StringView struct {
	strBuf uintptr
	strLen uint64
}

func main() {
	s1 := "123"
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
	s2 := s1
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)

	s2 = "4567"
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
}

        猜想大致正确,它们应该都在较低的数据段。

 (3)  验证运行时的拷贝行为

package main

import (
	"fmt"
	"unsafe"
)

type StringView struct {
	strBuf uintptr
	strLen uint64
}

func main() {
	s1 := "123"
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
	s2 := s1
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)

	s2 = "4567"
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)

	s3 := s2 + "78910"
	fmt.Println((*StringView)(unsafe.Pointer(&s3)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s3)).strLen)

	s4 := s1 + s2
	fmt.Println((*StringView)(unsafe.Pointer(&s4)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s4)).strLen)

}

         可以看到,s3、s4在使用拼接行为后,在底层创建了新的字符串,且地址位较高,极又可能是运行时分配在了堆上。

2. 使用reflect后的相关行为

2.1 一些基本的参数

package main

import (
	"fmt"
	"reflect"
)

func main() {
	s1 := "123"
	s1Value := reflect.ValueOf(&s1).Elem()
	fmt.Println(s1Value.Type().Kind())
	fmt.Println(s1Value.CanSet())
}

        在被解析为reflect.Value后,string类型并不能使用Set,其他的属性,都符合正常人类认识,不一一列出。

2.2 变成可以使用Set的方法

package main

import (
	"fmt"
	"reflect"
)

func main() {
	s1 := "123"
	s1Value := reflect.ValueOf(&s1).Elem()
	fmt.Println(s1Value.Type().Kind())
	fmt.Println(s1Value.CanSet())
}

        改成指针,再取出来就可以,不得不说,go这儿的设计,就很脱裤子放屁,都已经深入到反射了,还那么多限制。

        最后验证一下:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type StringView struct {
	strBuf uintptr
	strLen uint64
	//strCap uint64 !不存在cap
}

func main() {
	s1 := "123"
	s1Value := reflect.ValueOf(&s1).Elem()
	fmt.Println("s1的原始底层地址、长度:")
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)

	s2 := "456"
	fmt.Println("s2的底层地址、长度:")
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s2)).strLen)
	s2Value := reflect.ValueOf(&s2).Elem()
	s1Value.Set(s2Value)

	fmt.Println(s1)
	fmt.Println("s1被设置后的值:")
	fmt.Println(s1)
	fmt.Println("s1被设置后的底层地址、长度:")
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strBuf)
	fmt.Println((*StringView)(unsafe.Pointer(&s1)).strLen)
}

         

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值