字符串是不可变字节(byte)序列,其本身是一个复合结构
type stringStruct struct{
str unsafe.Pointer
len int
}
头部指针指向字节数组,但没有null结尾,默认以utf-8编码粗出Unicode字符,字面量李允许使用十六进制、八进制和UTF编码格式。默认值是""而不是null。
使用"`"定义不做转移处理的原始字符串,支持跨行
func main() {
s := `line\r\n,
line 2`
println(s)
}
!=、==、<、>、+、+=
操作符func main() {
s := "哈喽"
for i := 0; i < len(s); i++ { // byte
fmt.Printf("%d: [%c]\n", i, s[i])
}
for i, c := range s { // rune 返回数组索引号 以及unicode字符
fmt.Printf("%d: [%c]\n", i, c)
}
0: [å]
1: [ ]
2: [ ]
3: [å]
4: [ ]
5: [½]
0: [哈]
3: [喽]
}
定义数组类型时,数组长度必须是非负整形常量表达式,长度是类型组成部分,所以,元素类型相同但是长度不同的数组不属于同一类型
func main() {
d1 := [3]int{}
d2 := [2]int{}
d1 = d2 // cannot use d2 (variable of type [2]int) as type [3]int in assignment
}
var a [4]int // 元素自动初始化为0 [0 0 0 0]
b := [4]int{2, 5} // 未提供初始值得元素自动初始化为0 [2 5 0 0]
c := [4]int{5, 3: 100} // 可指定索引位置初始化 [5 0 0 100]
d := [...]int{1, 2, 3} // 编译器按初始化值数量确定数组长度 [1 2 3]
e := [...]int{10, 3: 100} // 支持索引初始化,但是数组长度与此有关 [10 0 0 100]
fmt.Println(a, b, c, d, e)
对于结构等复合类型,可省略元素初始化类型标签
type user struct {
name string
age byte
}
d := [...]user{
{"Tome", 20}, // 省略了类型标签
{"Mary", 18}}
fmt.Printf("%#v\n", d)
在定义多维数组时,仅第一维度允许使用“…”
a := [2][2]int{
{1, 2},
{3, 4},
}
b := [...][2]int{
{10, 20},
{30, 40},
}
c := [...][2][2]int{
{
{1, 2},
{3, 4},
},
{
{10, 20},
{30, 40},
},
}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
[[1 2] [3 4]]
[[10 20] [30 40]]
[[[1 2] [3 4]] [[10 20] [30 40]]]
内置函数len和cap都返回第一维度长度;
如果元素类型支持==、!=
操作符,那么数组也支持此操作
要分清楚指针数组和数组指针的区别,指针数组是指元素为指针类型的数组,数组指针是获取数组变量的地址。
x, y := 10, 20
a := [...]*int{&x, &y} // 指针数组
p := &a // 数组指针
fmt.Printf("%T,%v\n", a, a) //[2]*int,[0xc000018088 0xc0000180a0]
fmt.Printf("%T,%v\n", p, p) //*[2]*int,&[0xc000018088 0xc0000180a0]
// 获取任意元素地址
println(&a, &a[0], &a[1])
//数组指针可以直接用来操作元素
c := [...]int{1, 2}
d := &c
d[1] += 10
println(d[1])
Go数组是值类型,赋值和传参操作都会复制整个数组数据。
func test(a [3]int) {
println(&a, &a[0], &a[1], &a[2]) // 传参 0xc0000cbf40 0xc0000cbf40 0xc0000cbf48 0xc0000cbf50
}
func main() {
a := [...]int{1, 2, 3}
println(&a, &a[0], &a[1], &a[2]) // 0xc0000cbf58 0xc0000cbf58 0xc0000cbf60 0xc0000cbf68
b := a
println(&b, &b[0], &b[1], &b[2]) //赋值 0xc0000cbf28 0xc0000cbf28 0xc0000cbf30 0xc0000cbf38
test(a)
如果需要,可改用指针或者切片,以避免数据复制
func test(a *[3]int) {
println(&a, &a[0], &a[1], &a[2]) // 指针传参 0xc00007bf50 0xc00007bf38 0xc00007bf40 0xc00007bf48
}
func main() {
a := [...]int{1, 2, 3}
println(&a, &a[0], &a[1], &a[2]) // 0xc00007bf38 0xc00007bf38 0xc00007bf40 0xc00007bf48
b := a[:]
println(&b, &b[0], &b[1], &b[2]) //切片赋值 0xc00007bf58 0xc00007bf38 0xc00007bf40 0xc00007bf48
var c = &a // 指针赋值 0xc00007bf68 0xc00007bf38 0xc00007bf40 0xc00007bf48
println(&c, &c[0], &c[1], &c[2])
test(&a)
切片本身并非动态数组或数组指针,内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内。其本身是个只读对象,工作机制类似数组指针的一种包装。
x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
x1 := x[:]
x2 := x[2:5]
x3 := x[2:5:7]
x4 := x[4:]
x5 := x[:4]
x6 := x[:4:6]
fmt.Println(x1, len(x1), cap(x1)) // [0 1 2 3 4 5 6 7 8 9] 10 10
fmt.Println(x2, len(x2), cap(x2)) // [2 3 4] 3 8
fmt.Println(x3, len(x3), cap(x3)) // [2 3 4] 3 5
fmt.Println(x4, len(x4), cap(x4)) // [4 5 6 7 8 9] 6 6
fmt.Println(x5, len(x5), cap(x5)) // [0 1 2 3] 4 10
fmt.Println(x6, len(x6), cap(x6)) // [0 1 2 3] 4 6
属性cap表示切片所引用数组片段的真实长度,len用于限定可读的写元素数量。
cap表示切片引用数组的容量,该数组可以插入多少个元素,如果cap不够则需要扩容
x3 = append(x3, 1000)
x3 = append(x3, 1001)
fmt.Println(x3, len(x3), cap(x3)) // [2 3 4 1000 1001] 5 5
println(&x3)
x3 = append(x3, 1002)
fmt.Println(x3, len(x3), cap(x3)) //cap不够,发生扩容,cap变成10 [2 3 4 1000 1001 1002] 6 10
println(&x3)
可以直接创建切片对象,无须预先准备数组,因为是引用类型,须使用make函数或显
s1 := make([]int, 3, 5) // 指定len、cap底层数组初始化为零值
s2 := make([]int, 3) // 省略cap 和len 相等
s3 := []int{10, 20, 5: 30} // 按初始化元素分配底层数组,并设置 len、cap
fmt.Println(s1, len(s1), cap(s1)) // [0 0 0] 3 5
fmt.Println(s2, len(s2), cap(s2)) // [0 0 0] 3 3
fmt.Println(s3, len(s3), cap(s3)) // [10 20 0 0 0 30] 6 6
可以获取元素地址,但是不能向数组那样直接用指针访问元素内容
s := []int{0, 1, 2, 3, 4}
p := &s
p0 := &s[0]
p1 := &s[1]
println(p, p0, p1) // 0xc00007bf58 0xc00000a360 0xc00000a368
(*p)[0] += 100 // *[]int 不支持索引操作 须先返回[]int对象 mismatched types []int and untyped int
*p1 += 100
fmt.Println(s) // [100 101 2 3 4]
对切片再次进行切片,不能超出cap,但是不受len限制。新切片对象依旧指向原底层数组,也就是说修改对所有关联切片可见。
d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := d[3:7]
s2 := s1[:3]
s2[1] = 100
println(&s1[0]) // 0xc000014298
println(&s2[0]) // 0xc000014298
fmt.Println(s1) // [3 4 100 6]
fmt.Println(s2) // [3 100 5]
向切片尾部添加数据,返回新的切片对象
s := make([]int, 0, 5)
s1 := append(s, 10)
s2 := append(s1, 20, 30)
fmt.Println(s, len(s), cap(s)) // [] 0 5
fmt.Println(s1, len(s1), cap(s1)) // [10] 1 5
fmt.Println(s2, len(s2), cap(s2)) // [10 20 30] 3 5
数据被追加到原底层数组,如超出cap限制,则为新切片对象重新分配数组。
在两个切片对象间复制数据,允许指向同一底层数组,允许目标区间重叠,最终所复制长度以较短的切片长度为准。
d := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := d[5:8]
n := copy(d[4:], s1) // 在同一底层数组的不同区间复制
fmt.Println(n, d) // 3 [0 1 2 3 5 6 7 7 8 9]
s2 := make([]int, 6)
n = copy(s2, d) // 在不同数组间复制
fmt.Println(n, s2) // 6 [0 1 2 3 5 6]
字典是引用类型,使用make函数或初始化表达语句来创建。
m := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(m)
m["a"] = 10 // 修改
m["c"] = 30 // 新增
if v, ok := m["d"]; ok { // 使用ok-idiom 判断key是否存在
println(v)
}
delete(m, "d") // 删除键值对,不存在时,不会出错
访问不存在的键值对,默认返回零值,不会引发错误,但推荐使用ok-idiom模式,毕竟通过零值无法判断键值是否存在,或许存储的value本身就是零。
因内存访问安全和河西算法等缘故,字典被设计成“not addressable” 故不能直接修改value成员。
正确做法时返回整个value,待修改后再设置字典键值,或直接用指针类型。
type user struct {
name string
age byte
}
m := map[int]user{
1: {"Tome", 19},
}
//m[1].age += 1 // Cannot assign to m[1].age
u := m[1]
u.age += 1
m[1] = u
fmt.Println(m) // map[1:{Tome 20}]
m2 := map[int]*user{
1: &user{"Jack", 20},
}
m2[1].age++
在迭代期间删除或新增键值时安全的。
m := make(map[int]int)
for i := 0; i < 10; i++ {
m[i] = i + 10
}
for k := range m {
if k == 5 {
m[100] = 1000
}
delete(m, k)
fmt.Println(k, m)
}
//1 map[0:10 2:12 3:13 4:14 5:15 9:19]
//3 map[0:10 2:12 4:14 5:15 9:19]
//4 map[0:10 2:12 5:15 9:19]
//5 map[0:10 2:12 9:19 100:1000]
//0 map[2:12 9:19 100:1000]
//2 map[9:19 100:1000]
//9 map[100:1000]
不能保证迭代操作会删除新增的键值
Go没有为map提供清空所有元素的函数,清空map唯一的办法是重新make一个新的map。
Go语言的垃圾回收比写一个清空函数更高效
map与切片相似,都是引用类型。将一个map赋值给一个新的变量时,它们指向同一块内存,因此修改两个变量的内容都能够引起它们所指向的数据发生变化。
map1 := map[string]string{
"hello":"123",
"demo":"321",
}
fmt.Println("原始map: ",map1)
newMap := map1
newMap["hello"]="456"
fmt.Println("修改后newMap: ",newMap)
fmt.Println("修改后map: ",map1)
// 原始map: map[demo:321 hello:123]
// 修改后newMap: map[demo:321 hello:456]
// 修改后map: map[demo:321 hello:456]
运行时会对字典并发操作做出检测,如果某个任务正在对字段进行写操作,那么其他任务就不能对该字典并发操作,否则会导致进程崩溃。
m := make(map[string]int)
go func() {
for {
m["a"] += 1 // 写操作
time.Sleep(time.Millisecond)
}
}()
go func() {
for {
_ = m["b"] // 读操作
time.Sleep(time.Millisecond)
}
}()
select {} // 阻止进程退出
// fatal error: concurrent map read and map write