字符串
字符串为不可变字节序列,其本身为复合结构:
type stringStruct struct {
str unsafe.Pointer
len int
}
其中头部指针指向字节数组,没有NULL结尾,默认以UTF-8编码存储Unicode字符。
- 字符串的默认值不是nil,而是””
- 支持 !=、 ==、 <、 >、+、 += 操作符
- 允许以索引访问字节数组,但是不能获取元素地址
func main() { s := "abc" println(s[1]) println(&s[1]) //错误:不能取地址 }
遍历
func main() {
s := "雨痕"
//byte 遍历
for i:=0; i < len(s); i++ {
fmt.Printf("%d : [%c]\n", i, s[i])
}
//rune 遍历
for i, c := range s {
fmt.Printf("%d : [%c]\n", i ,c)
}
}
转换
如果要修改字符串,需要将其转化为可变类型([]byte或者[]rune),然后再转换回来,这需要重新分配内存。
func main() {
s := "hello world"
bs := []byte(s)
s2 := string(bs)
rs := []rune(s)
s3 := string(rs)
}
在某些情况下,为了提高性能会对其进行优化,避免额外分配内存和赋值操作
- 在类型为
map[string]
中查询时,默认将[]byte
转换为string key for
循环迭代时,将string
转换为[]byte
,直接取字节赋值给局部变量
性能
构建字符串容易造成性能问题,用加法操作拼接字符串每次都需要重新分配内存。
通常的解决方式是通过预先分配足够的内存空间,然后使用strings.Join函数拼接成字符串
Unicode
类型rune
专门用来存储Unicode码,它是int32的别名。
数组
定义数组类型的时候,长度必须是非负整型常量表达式,长度是类型的组成部分;也可以使用...
来代替数组长度,此时编译器按照初始化值的数量确定数组长度,并且定义多维数组的时候仍然可以使用。
a := [4]int{2, 5}
b := [...]int{10, 3:100}
len
和cap
都返回多维数组的第一维长度- 如果元素类型支持 != == 操作符,那么数组也支持
- 可以获取任意的数组元素
- 数组指针可以直接用来操作元素
- 数组是值类型,赋值和传参操作都会复制整个数组的数据
切片
其复合结构如下:
type slice struct {
array unsafe.Pointer
len int
cap int
}
- 可以基于数组和数组指针创建切片
cap
表示切片所引用数组片段的真实长度,len
用于限定可读的写元素数量- 通过
make
指定cap
和len
的指定切片的长度和容量 - 初始化的区别:
- 定义
int[]
类型变量,并未执行初始化操作 - 完成定义以及初始化全部操作
var a int[] b := []int{}
- 定义
- 不支持比较,及时元素类型支持
*int[]
类型不支持索引操作,必须返回[]int
类型对象- 可以在切片的基础上创建切片,但是不能超出去原来的
cap
,并且不受len
的限制
append
超出切片的cap
限制,新分配的数组长度是原来cap
的2倍,而非原数组的二倍,对于较大的切片,会尝试扩容1/4
copy
允许两个切片指向同一个数组,允许目标区域重叠,最终所复制的长度以较短的长度为准
字典
- 要求key必须支持相等运算
- 访问不存在的key值,返回value的默认值
- 对字典进行迭代,每次迭代的循序都不一样
-
字典被设计成“not addressable”,不能直接修改value成员
func main() { type user struct { name string age byte } m := map[int]user { 1: {"Tom", 19}, } m[1].age += 1 //错误,不可寻址 u := m[1] u.age += 1 m[1] = u //正确 m2 := map[int]*user { 1: &user{"jack", 20}, } m2[1].age++ }
- 不能对nil字典进行写操作,但是可以读
func main() { var m map[string]int println(m["a"]) m["a"] = 1 var m2 map[string]int{} }
- 在迭代期间,新增或者删除key是安全的
- 字典运行时会进行并发操作检测,如果某个任务正在进行写操作,那个其他任务就不能对字典进行并发操作(读、写、删除)
性能
- 字典对象本身就是指针的封装,传参时无需再次取地址
- 创建时预先准备足够空间有助于提升性能,减少扩张时内存分配和重哈希操作
结构
- 匿名结构类型
u := struct { name string age byte } { name: "XXXX", age: 12, } type file struct { name string attr struct { owner int perm int } } f := file{ name: "YY", // attr: { 错误使用 // owner: 1, // perm: 5, //}, } f.attr.owner = 1 f.attr.perm = 1
-
只有结构内部所有的字段类型支持相等比较时,才可以进行相等比较
- 可以使用指针直接操作字段,但是不能使用多级指针
空结构
struct {}
- 长度为0
- 可作为通道元素类型,用于事件通知
匿名字段
- 可以直接引用匿名字段,但是初始化的时候必须显示独立初始化
- 隐式字段名不包括包名
- 除接口指针和多级指针之外任何命名类型都可以作为匿名字段
- 不能将基础类型和其指针类型同时嵌入,因为两者隐式名字相同
- 如果存在重名,从当前显示命名字段开始,逐步向内查找字段成员,如果匿名字段成员被外层同名字段遮蔽,必须使用显示字段命名
字段标签
type user struct {
name string `昵称`
sex byte `性别`
}
func main() {
u := user{"Tom", 1}
v := reflect.ValueOf(u)
t : v.Type()
for i, n := 0, t.NumField(); i < n; i++ {
fmt.Println("%s: %v\n", t.Field(i),Tag(), v.Field(i))
}
}
内存布局
- 各字段在相邻地址空间内按照定义顺序排列
- 字段需要进行对齐处理,通常以所有字段中最长的基础类型宽度为标准