字符串
简介
一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。由于Go语言的源代码要求是UTF8编码,导致Go源代码中出现的字符串面值常量一般也是UTF8编码的。
Go语言字符串底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制(重点!!)
初始化
package main
import "fmt"
func main() {
// 第一种方式
var str1 string
str1 = "string"
// 第二种方式
var str2 = "string"
// 第三种方式
str3 := "string"
fmt.Println(str1)
fmt.Println(str2)
fmt.Println(str3)
}
//string
//string
//string
底层结构
Go语言字符串的底层结构在reflect.StringHeader中定义:
type StringHeader struct {
Data uintptr
Len int
}
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个结构体,因此字符串的赋值操作也就是reflect.StringHeader结构体的复制过程,并不会涉及底层字节数组的复制。可以将字符串数组看作一个结构体数组。
我们可以看看字符串“Hello, world”本身对应的内存结构:
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
var data = [...]byte{
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
}
验证:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
str := "hello, world"
data := [...] byte{'G','o'}
fmt.Println(str)
fmt.Println(&str)
hdr :=(*reflect.StringHeader)(unsafe.Pointer(&str))
hdr.Data = uintptr(unsafe.Pointer(&data[0]))
hdr.Len = len(data)
fmt.Println(str)
fmt.Println(&str)
}
//hello, world
//0xc0000321d0
//Go
//0xc0000321d0
切片
字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量):
package main
import "fmt"
func main() {
s := "hello, world"
hello := s[:5]
world := s[7:]
fmt.Println(hello)
fmt.Println(world)
}
//hello
//world