在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