第五章(数据)[上]
字符串
- 字符串是不可变字节(byte)序列
- 字符串默认值是"",而不是nil
- 使用反引号"`"定义不做转义处理的原始字符串,支持跨行
func main() { s := `hello \r\n, line 2` println(s) /* hello \r\n, line 2 */ }
- 编译器不会解析原始字符串的注释语句,且前置缩进空格也属于字符串内容
- 字符串使用"+"拼接时,加法操作符必须在上一行结尾
func main() {
s := "ab" +
"cd"
println(s) //abcd
}
-
允许以索引号访问字节数组,但不能获取元素地址
-
数组或字符串截取时,从头开始,到指定位置结束(不包含结束索引位置)
左闭右开区间[)
func main() {
s := "abcdefg"
s1 := s[1:3]
fmt.Printf("%v", s1) //bc
}
- for遍历字符串时,分byte和rune两种方式
package main
import "fmt"
func main() {
s := "你好"
for i := 0; i < len(s); i++ { //byte(int8)
fmt.Printf("%d:[%c]\n", i, s[i])
}
/*
0:[ä]
1:[½]
2:[ ]
3:[å]
4:[¥]
5:[½]
*/
for i, v := range s { //rune (int32)
fmt.Printf("%d:[%c]\n", i, v)
}
/*
0:[你]
3:[好]
*/
}
- 使用append函数,可将string直接追加到[]byte内
func main() {
var bs []byte
bs = append(bs, "bcd"...)
fmt.Println(bs) //[98 99 100]
fmt.Printf("%s", bs) //bcd
}
- []byte转为string
m := map[string]int{
"abc": 123,
}
key := []byte("abc")
x, ok := m[string(key)]
println(x, ok) // 123 true
- 字符串切片转字符串(strings.Join)
func test() string {
s := make([]string, 1000)
for i := 0; i < 1000; i++ {
s[i] = "a"
}
return strings.Join(s, "")
}
- 使用bytes.Buffer拼接字符串(没搞明白这玩意的核心应用是来干啥的???)
func test() string {
var b bytes.Buffer
b.Grow(1000)
for i := 0; i < 1000; i++ {
b.WriteString("a")
}
return b.String()
}
-
字符串操作通常在堆上分配内存,会对高并发应用造成较大影响,会有大量字符串对象要做垃圾回收。建议使用[]byte缓存池,或在栈上自行拼装等方式zero-garbage
-
验证utf8-字符串是否合法 utf8.ValidString(string) ,常用来判断中文字符串是否截取正常
-
utf8.RuneCountInString(string) 代替len返回准确的Unicode字符数量
数组
- 定义数组类型时,数组长度是类型的组成部分,长度必须是非负整数常量表达式
- 元素类型相同,但长度不同的数组不属于同一类型
func main() {
var a [4]int //元素自动初始化为0
b := [4]int{2, 4} //未提供初始值的元素自动初始化为0
c := [4]int{5, 3: 10} //可指定索引位置初始化
d := [...]int{1, 2, 3} //编译器按初始化值数量确定数组长度
e := [...]int{10, 3: 100} //支持索引初始化,但注意数组长度与此有关
fmt.Println(a, b, c, d, e)//[0 0 0 0] [2 4 0 0] [5 0 0 10] [1 2 3] [10 0 0 100]
}
- 定义多维数组时,仅第一维度允许使用"..."来确定数组长度
func main() {
a := [2][2]int{
{1, 2},
{3, 4},
}
b := [...][2]int{
{10, 20},
{30, 40},
}
c := [...][2][2]int{
{
{1, 2},
{3, 4},
},
{
{5, 6},
{7, 8},
},
}
fmt.Println(a, b, c)
}
-
内置函数len和cap都返回第一维数组的长度和容量
-
数组也支持
==,!==
操作符 -
指针数组和数组指针(
数组的指针
)- 指针数组是指元素为指针类型的数组
- 数组指针是获取数组变量的地址
func main() {
x, y := 100, 200
a := [...]*int{&x, &y} //指针数组:元素为指针的数组
p := &a //数组指针,存储数组地址的指针
fmt.Printf("%T,%v\n", a, a) //[2]*int,[0xc00001c0a8 0xc00001c0c0]
fmt.Printf("%T,%v\n", p, p)//*[2]*int,&[0xc00001c0a8 0xc00001c0c0]
}
- 可获取任意元素地址
func main() {
a := [...]int{1, 2}
println(&a, &a[0], &a[1]) //0xc000055f60 0xc000055f60 0xc000055f68
}
数组地址和数组第一个元素的地址一样
- 数组指针可直接用来操作元素
func main() {
a := [...]int{1, 2}
p := &a
p[1] += 10
println(p[1], a[1]) //12 12
}
可通过unsafe.Pointer转换不同长度的数组指针来实现越界访问
切片
-
切片有三种创建方式:a-引用数组 b-make函数创建切片对象 c-声明切片类型的变量后使用append函数直接添加元素
-
切片:底层通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内
切片本身是个只读对象,其工作机制类似数组指针的一种包装
-
基于数组或数组指针创建切片(左闭右开区间[))
- len 实际元素长度
len=hight-low
- cap 表示切片所引用数组片段的真实长度
cap=max-low
- len 实际元素长度
-
切片和数组一样使用索引号访问元素内容,但是从引用数组的索引开始并且从0开始
-
可使用make直接创建切片对象
func main() {
s1 := make([]int, 3, 5) // len=3 cap=5
s2 := make([]int, 3) //省略cap,和len一样 len=3 cap=3
s3 := []int{10, 20, 5: 30} //按初始化元素分配底层数组,len=6 cap=6
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
fmt.Println(s3, len(s3), cap(s3))
}
- 仅定义切片并未初始化时,切片和nil相等。slice不支持比较操作,仅能判断是否为nil
func main() {
var a []int
b := []int{}
println(a == nil, b == nil) //true fase
}
- 新建切片对象依旧指向底层数组
func main() {
d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := d[3:7]
s2 := s1[1:3]//切片重组(reslice)
for i := range s2 {
s2[i] += 100
}
fmt.Println(d) //[0 1 2 3 104 105 6 7 8 9]
fmt.Println(s1) //[3 104 105 6]
fmt.Println(s2) //[104 105]
}
- 利用reslice(切片重组)实现栈式数据结构
func main() {
//栈最大容量5
stack := make([]int, 0, 5)
//入栈
push := func(x int) error {
//获取栈的长度
stackLen := len(stack)
//获取栈的容量
stackCap := cap(stack)
//判断如果stack长度大于等于stack容量时,stack is full
if stackLen >= stackCap {
return errors.New("stack is full")
}
//利用reslice(切片重组) 重组切片 stack长度+1
stack = stack[:stackLen+1]
stack[stackLen] = x
return nil
}
//出栈
pop := func() (int, error) {
//获取栈的长度
stackLen := len(stack)
//判断栈是否为空
if 0 == stackLen {
return 0, errors.New("stack is empty")
}
//获取最后一个元素
x := stack[stackLen-1]
//利用reslice(切片重组) 重组切片
stack = stack[:stackLen-1]
return x, nil
}
//入栈测试
for i := 0; i < 7; i++ {
fmt.Printf("push %d: %v, %v\n", i, push(i), stack)
}
//出栈测试
for i := 0; i < 7; i++ {
x, err := pop()
fmt.Printf("pop: %d, %v, %v\n", x, err, stack)
}
}
- append:向切片尾部添加数据,返回新的切片对象
func main() {
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限制,则为新切片对象重新分配数组
- 是超出切片cap限制,并非是底层数组长度限制
- 新分配数组长度是原cap的2倍,而非原数组的2倍
并非总是2倍,对于较大的切片,会尝试扩容1/4,以节约内存
-
向nil切片追加数据时,会为其分配底层数组内存
func main() {
var s []int
s = append(s, 1, 2, 3, 4)
fmt.Println(s) // [1 2 3 4]
}
正因为存在重新分配底层数组的缘故,在某些场合建议预留足够多的空间,避免中途内存分配和数据复制开销
- copy
func copy(dst, src []Type) int
将元素从来源切片复制到目标切片中,copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个
func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[5:8]
n := copy(s[4:], s1) //copy函数返回复制元素的数量
fmt.Println(s, s1, n) //[0 1 2 3 5 6 7 7 8 9] [6 7 7] 3
s2 := make([]int, 6)
n = copy(s2, s) //copy函数返回复制元素的数量
fmt.Println(n, s2) //6 [0 1 2 3 5 6]
}
- 从字符串中复制数据到[]byte
func main() {
b := make([]byte, 3)
n := copy(b, "abcdef")
fmt.Println(n, b) // 3 [97 98 99]
}
- 如果切片长时间引用大数组中很小的片段,那么建议新建独立切片,复制出所需的数据,便于原数组内存可被及时回收