zoukankan      html  css  js  c++  java
  • 【Go】数组、字符串与切片

    数组、字符串与切片

    Go语言中数组、字符串和切片三者是密切相关的数据结构。这三种数据类型,在底层原始数据有着相同的内存结构。虽然数组的元素可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制。

    数组

    数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一个部分,不同长度或不同类型的数据组成的数组都是不同的类型,因此在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
    var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
    

    第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。

    第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。

    第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和 map[int]Type 类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。

    第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。

    数组与指针

    Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。

    var a = [...]int{1, 2, 3} // a 是一个数组
    var b = &a // b 是指向数组的指针
    fmt.Println(a[0], a[1]) // 打印数组的前2个元素
    fmt.Println(b[0], b[1]) // 通过数组指针访问数组元素的方式和数组类似
    

    可以将数组看作一个特殊的结构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数 len 可以用于计算数组的长度, cap 函数可以用于计算数组的容量。不过对于数组类型来说, len 和 cap 函数返回的结果始终是一样的,都是对应数组类型的长度。

    遍历
    for i := range a {
    	fmt.Printf("a[%d]: %d
    ", i, a[i])
    }
    for i, v := range b {
    	fmt.Printf("b[%d]: %d
    ", i, v)
    }
    for i := 0; i < len(c); i++ {
    	fmt.Printf("c[%d]: %d
    ", i, c[i])
    }
    

    用 for range 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。

    字符串

    一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。

    Go语言字符串的底层结构在 reflect.StringHeader 中定义:

    type StringHeader struct {
        Data uintptr
        Len int
    }
    

    字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。

    字符串其实是一个结构体,因此字符串的赋值操作也就是 reflect.StringHeader 结构体的复制过程。

    字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量):

    s := "hello, world"
    hello := s[:5]
    world := s[7:]
    s1 := "hello, world"[:5]
    s2 := "hello, world"[7:]
    

    切片

    切片的结构定义, reflect.SliceHeader :

    type SliceHeader struct {
        Data uintptr
        Len int
        Cap int
    }
    
    定义方式
    var (
        a []int // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
        b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
        c = []int{1, 2, 3} // 有3个元素的切片, len和cap都为3
        d = c[:2] // 有2个元素的切片, len为2, cap为3
        e = c[0:2:cap(c)] // 有2个元素的切片, len为2, cap为3
        f = c[:0] // 有0个元素的切片, len为0, cap为3
        g = make([]int, 3) // 有3个元素的切片, len和cap都为3
        h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
        i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
    )
    
    遍历
    for i := range a {
    	fmt.Printf("a[%d]: %d
    ", i, a[i])
    }
    for i, v := range b {
    	fmt.Printf("b[%d]: %d
    ", i, v)
    }
    for i := 0; i < len(c); i++ {
    	fmt.Printf("c[%d]: %d
    ", i, c[i])
    }
    

    在对切片本身赋值或参数传递时,和数组指针的操作方式类似,只是复制切片头信息( reflect.SliceHeader ),并不会复制底层的数据。

    添加
    var a []int
    a = append(a, 1) // 追加1个元素
    a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
    a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
    
  • 相关阅读:
    1124 vue路由配置初级实践&和npm run dev干嘛了
    1130 携程网焦点图+导航栏,flex布局实践
    1124 在vscode中快速创建vue模板
    122 携程网案例flex布局第三部分
    128 手撸轮播组件瞬时切换版本,布局部分
    1125 vscode自定义快捷键扩展选择单词等
    124 本地服务器搭建
    1128 defineProperty中getter和setter的用法
    2216 怎么快速打开powershell
    Visual Studio 2010的网站局域网发布功能(Publish)
  • 原文地址:https://www.cnblogs.com/Ryan16231112/p/12315363.html
Copyright © 2011-2022 走看看