第五章(数据)[上]

字符串

  • 字符串是不可变字节(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
  • 切片和数组一样使用索引号访问元素内容,但是从引用数组的索引开始并且从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]
}

  • 如果切片长时间引用大数组中很小的片段,那么建议新建独立切片,复制出所需的数据,便于原数组内存可被及时回收
posted @ 2023-01-09 00:05  巴达克  阅读(16)  评论(0编辑  收藏  举报