Go语言高级编程 一

数组、字符串和切片

这三种数据类型,在底层原始数据有着相同的内存结构,由于语法的限制,在上层有着不同的行为表现。

  • 数组是一个值类型,虽然可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。
  • 字符串底层数据也是对应的字节数组,但是字符串是只读的,禁止程序对底层字节数组的元素进行修改, 字符串赋值只是复制了数据地址和对应的长度,不会导致底层数据的复制。
  • 切片结构与字符串类似,底层也是字节数组,但是每个切片有独立的长度和容量信息,切片复制和函数传参时只是将切片头部信息按传值方式处理。切片含有底层数据的指针,赋值不对导致底层数据的复制。
    go语言的赋值和函数传参,除了闭包函数以引用的方式对外部变量访问外,其他赋值和函数传参数都是以传值的方式处理。

数组

数组是一个由固定长度的特定元素组成的序列,可以由0个或多个元素组成。
数组的长度是数组类型的组成成分,在go语言中很少直接使用数组(不同长度的数组因为类型不同无法直接赋值)
而切片,是可以动态增长和收缩的。

定义方式
var a [3]int                    
// 定义长度为3的int型数组, 元素全部为0  数组的长度明确指定,数组中的每个元素都以零值初始化。
var b = [...]int{1, 2, 3}      
 // 定义长度为3的int型数组, 元素为 1, 2, 3   数组的长度根据初始化元素的数目自动计算
var c = [...]int{2: 3, 1: 2}    
// 定义长度为3的int型数组, 元素为 0, 2, 3    数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化 
var d = [...]int{1, 2, 4: 5, 6} 
// 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6  混合了第二种和第三种的初始化方式,

go 中数组是值语义,一个数组变量就代表整个数组,是一个完整的值。当一个数组变量被赋值或者传递的时候,会复制整个数组。
当数组较大的情况话,数组的赋值也会有较大的开销,为了避免开销,可以选择传入指向数组的指针。

var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a                // b 是指向数组的指针

内置函数len可以用于计算数组的长度,cap函数可以用于计算数组的容量,对于数组而言,len和cap函数返回的结果是一样的。
用for range 迭代的性能会好,且可以保证不会出现数组越界的情况,每轮迭代元素,可以省去对下标越界的判断。
长度为0的数组在内存中并不占用空间。在一些特定场合,用于强调某种特有类型的操作避免分配额外内存空间场景下会使用,
eg:对于管道的同步操作。

    c1 := make(chan [0]int)
    go func() {
        fmt.Println("c1")
        c1 <- [0]int{}
    }()
    <-c1
// 这里不关心管道中传输数据的真实类型,管道接收和发送操作只是用于消息的同步。这种场景,用空数组来作为管道类型,可以减少管道元素赋值时候的开销。

fmt.Printf函数提供的%T或%#v谓词语法来打印数组的类型和详细信息

    fmt.Printf("b: %T\n", b)  // b: [3]int
    fmt.Printf("b: %#v\n", b) // b: [3]int{1, 2, 3}

字符串

不可变的字节序列,字符串的元素不可以修改,是一个只读的字节数组。
符串可以包含任意的数据,包括byte值0。
字符串的信息组成:

  1. 字符串指向的底层字节数组
  2. 符串的字节的长度。
    字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量)
    内置的len函数返回字符串的长度
    go对字符串和[]rune类型的相互转换提供了特殊的支持

[]rune []int32

	fmt.Printf("%#v\n", []rune("你好"))   // []int32{20320, 22909}
	fmt.Printf("%#v\n", string([]rune{'你', '好'}))  "你好"

切片

切片就是一种简化版的动态数组
切片的cap表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)
内置的len函数返回切片中有效元素的长度,内置的cap函数返回切片容量大小,容量必须大于或等于切片的长度。
切片可以和nil进行比较,只有当切片底层数据指针为空时切片本身为nil,这时候切片的长度和容量信息将是无效的
添加切片元素,可以在后面追加,也可以在开头添加,但是在开头,一般会导致内存的重新分配,而且会导致已有的元素全部复制一次,性能会比从尾部追加元素的性能差很多。
没有专门的内置函数用于扩展切片的容量,append本质是用于追加元素而不是扩展容量,扩展切片容量只是append的一个副作用
切片高效操作的要点是要降低内存分配的次数,尽量保证append操作不会超出cap的容量,降低触发内存分配的次数和每次分配内存大小
要注意切片的内存泄漏,切片不会复制底层的数据,但是底层的数组会被保存在内存中,直到它不再被使用。可以将数据复制到一个新的切片中,虽然传值增加了一定的代价,但是可以切断对原始数据的依赖。
浮点数中NaN不可排序,正0和负0相等
参考: https://books.studygolang.com/advanced-go-programming-book/ch1-basic/ch1-03-array-string-and-slice.html

posted @ 2024-12-24 20:00  lars_huan  阅读(54)  评论(0)    收藏  举报