在go语言里面定义字符串如下:
var ss string = "12345"
对于一个熟悉C/C++的程序员来说,马上就会想到这个string是一个什么内容,占多大空间,内存如何分配等,下面我们来分析这个问题。
go语言的string是一种数据类型,这个数据类型占用16字节空间,前8字节是一个指针,指向字符串值的地址,后八个字节是一个整数,标识字符串的长度;注意go语言的字符串内部并不以'\0'作为结尾,而是通过一个长度域来表示字符串的长度。
type mystr struct {
strbuf uintptr;
strlen uint64;
}
上述就是string的类型定义。下面我们通过代码来验证这个问题:
package main
import (
"fmt"
"unsafe"
// "reflect"
)
type mystr struct {
strbuf uintptr;
strlen uint64;
}
func printmemory(p uintptr, size int) {
fmt.Printf("[0x%16x:%2d] =", p, size)
for i := 0; i < size; i++ {
p1 := unsafe.Pointer(p + uintptr(i))
p2 := (*byte)(unsafe.Pointer(p1))
fmt.Printf(" %x", *p2)
}
fmt.Printf("\n")
}
func main() {
var ss string = "12345";
fmt.Printf("string=%v\n", ss)
fmt.Printf("length=%v\n", len(ss))
fmt.Printf("size=%v\n", unsafe.Sizeof(ss))
fmt.Printf("address=%v\n", &ss)
ptr := unsafe.Pointer(&ss)
//value := reflect.ValueOf(&ss)
//fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value).Kind())
//ptr1 := value.Pointer()
ptr1 := uintptr(ptr)
printmemory(ptr1, 16);
ptr2 := (* mystr)(ptr)
fmt.Printf("mystr.strbuf=%x\n", ptr2.strbuf)
fmt.Printf("mystr.strlen=%v\n", ptr2.strlen)
printmemory(ptr2.strbuf, len(ss)+1);
}
执行结果如下:
$ go build main.go && ./main
string=12345
length=5
size=16
address=0xc42000e2c0
[0x c42000e2c0:16] = f1 70 4a 0 0 0 0 0 5 0 0 0 0 0 0 0
mystr.strbuf=4a70f1
mystr.strlen=5
[0x 4a70f1: 6] = 31 32 33 34 35 31
我们看到string的内存结构,包含一个指向字符串数据的指针,和一个标识字符串长度的整数值;而且字符串的结尾并没有一个'\0'来标识,在上述例子中是一个随机值(0x31)。
关于go语言的指针操作
go语言指针和C/C++指针的唯一差别就是:go语言不允许对指针做算术运算(+、-、++、--)。
但是,Go 提供了一套底层库 reflect 和 unsafe,它们可以把任意一个 go 指针转成 uintptr类型的值,然后再像 C/C++一样对指针做算术运算,最后再还原成 go 类型。所以从这个角度上看,go 指针也是可以和 C/C++ 指针一样使用的,只是会比较绕,这同时也要求使用者自己明白,如果真要把指针这么用,那么请记得后果自负。
下面是一个go语言指针运算的例子:
https://play.golang.org/p/z_GMnh38Z1