• 第四节 基本运算符和变量,数组切片


    下划线

    “_”是特殊标识符,用来忽略结果。

    下划线在import中

    在Golang里,import的作用是导入其他package。
    import 下划线(如:import hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
    举例:
    import _ “github.com/mysql”
    不直接使用该包,只是执行该包的init函数。

    下划线在代码中

    两种解释
    第一种:忽略这个返回值的变量,有些函数返回值有两个,但是我们只需要用第一个或者是第二个,此时就可以用下划线来忽略不需要关注的变量。
    第二种:表示了占位符的意思,本来这个下划线的位置要赋值给某个变量,但是我们不需要该变量,则用下划线进行占位。因为赋值了变量,但是不用该变量,则编译器会报错。

    变量

    定义

    数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。

    功能

    功能是存储数据,不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。
    Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

    声明

    Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用。
    Go语言的变量声明格式为:

    var 变量名 变量类型
    
    • 1

    批量声明

    每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

    var (
        a string
        b int
        c bool
        d float32
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    变量的初始化

    Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。

    我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:

    var 变量名 类型 = 表达式
    
    • 1

    一次初始化多个变量,如下:

    var 变量名, 变量名 = 表达式, 表达式
    
    • 1

    变量的类型推导

    我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。

    var 变量名 = 表达式, 表达式
    
    • 1

    短变量声明(常用–重要)

    函数内部,可以使用更简略的 := 方式声明并初始化变量。

    package main
    
    import (
        "fmt"
    )
    // 全局变量m
    var m = 100
    
    func main() {
        n := 100
        m := 1000 // 此处声明局部变量m
        fmt.Println(m, n)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    匿名变量

    在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示。
    注:
    匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
    函数外的每个语句都必须以关键字开始(var、const、func等)
    :=不能使用在函数外
    _多用于占位,表示忽略值

    常量

    相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。
    常量单独声明如下:

        const a = 1
        const b = 2
    
    
    • 1
    • 2
    • 3

    多个常量一起声明如下:

    const (
       a = 1
       b = 2
    )
    
    • 1
    • 2
    • 3
    • 4

    const同时声明多个常量时,如果省略了值则表示和上面一行的值相同:

    const (
        a = 1
        b     // b = 1
        c     // c = 1
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    iota

    1. iota是go语言的常量计数器,只能在常量的表达式中使用。
    2. iota在const关键字出现时将被重置为0。
    3. const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
    4. 使用iota能简化定义,在定义枚举时很有用:
    const (
            a = iota //0
            b        //1
            c        //2
            d        //3
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    iota使用场景

    1. _跳过某些值
    const (
            n1 = iota //0
            n2        //1
            _
            n4        //3
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. iota声明中间插队
    const (
            n1 = iota //0
            n2 = 100  //100
            n3 = iota //2
            n4        //3
        )
    const n5 = iota //0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 定义数量级
      <<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8
    const (
            _  = iota
            KB = 1 << (10 * iota)
            MB = 1 << (10 * iota)
            GB = 1 << (10 * iota)
            TB = 1 << (10 * iota)
            PB = 1 << (10 * iota)
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 多个iota定义在一行
    const (
            a, b = iota + 1, iota + 2 //1,2
            c, d                      //2,3
            e, f                      //3,4
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    基本类型

    注:空指针值 nil,而非C/C++ NULL。

    整型

    按长度分为:int8、int16、int32、int64
    对应的无符号整型:uint8、uint16、uint32、uint64
    注:
    uint8就是我们熟知的byte型
    int16对应C语言中的short型
    int64对应C语言中的long型

    浮点型

    float32和float64
    数据格式遵循IEEE 754标准:
    float32 的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32
    float64 的浮点数的最大范围约为1.8e308,可以使用常量定义:math.MaxFloat64

    复数

    complex64和complex128
    复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位

    布尔值

    Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值
    注:

    1. 默认false
    2. 不能将整型强制转换为布尔型
    3. 布尔型无法参与数值运算,也无法与其他类型进行转换

    字符串

    1. 字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样
    2. Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为 双引号 中的内容
    3. Go语言的源码中可以直接添加非ASCII码字符

    字符串转义符

    Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等

    转义含义
    \r回车符(返回行首)
    \n换行符(直接跳到下一行的同列位置)
    \t制表符
    单引号
    "双引号
    \反斜杠

    多行字符串

    Go语言中要定义一个多行字符串时,就必须使用反引号字符:

       s1 := `第一行
        第二行
        第三行
        `
        fmt.Println(s1)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出

    字符串的常用操作(重要)

    方法介绍
    len(str)求长度
    +或fmt.Sprintf拼接字符串
    strings.Split分割
    strings.Contains判断是否包含
    strings.HasPrefix,strings.HasSuffix前缀/后缀判断
    strings.Index(),strings.LastIndex()子串出现的位置
    strings.Join(a[]string, sep string)join操作

    byte和rune类型

    组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用 单引号 包裹起来
    字符类型如下:

     uint8类型,或者叫 byte 型,代表了ASCII码的一个字符
    
     rune类型,代表一个 UTF-8字符
    
    • 1
    • 2
    • 3

    当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理

    数组

    1. 数组:是同一种数据类型的固定长度的序列。
    2. 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
    3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。
    4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
    for i := 0; i < len(a); i++ {
    }
    for index, v := range a {
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
    2. 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
    3. 支持 “==”、“!=” 操作符,因为内存总是被初始化过的。
    4. ** 指针数组 [n]*T,数组指针 *[n]T。 **

    数组的初始化

    全局:
        var arr0 [5]int = [5]int{1, 2, 3}
        var arr1 = [5]int{1, 2, 3, 4, 5}
        var arr2 = [...]int{1, 2, 3, 4, 5, 6}
        var str = [5]string{3: "hello world", 4: "tom"}
    局部:
        a := [3]int{1, 2}           // 未初始化元素值为 0。
        b := [...]int{1, 2, 3, 4}   // 通过初始化值确定数组长度。
        c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
        d := [...]struct {
            name string
            age  uint8
        }{
            {"user1", 10}, // 可省略元素类型。
            {"user2", 20}, // 别忘了最后一行的逗号。
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    多维数组

    全局
        var arr0 [5][3]int
        var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
        局部:
        a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
        b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    多维数组的遍历:(后续有文章具体举例并描述

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
    
        for k1, v1 := range f {
            for k2, v2 := range v1 {
                fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
            }
            fmt.Println()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出结果:

        (0,0)=1 (0,1)=2 (0,2)=3 
        (1,0)=7 (1,1)=8 (1,2)=9
    
    • 1
    • 2

    切片Slice

    slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案
    注:
    1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
    2. 切片的长度可以改变,因此,切片是一个可变的数组。
    3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
    4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
    5. 切片的定义:var 变量名 []类型,比如 var str []string ; var arr []int。
    6. 如果 slice == nil,那么 len、cap 结果都等于 0。

    创建切片的方式

    package main
    
    import "fmt"
    
    func main() {
       //1.声明切片
       var s1 []int
       if s1 == nil {
          fmt.Println("是空")
       } else {
          fmt.Println("不是空")
       }
       // 2.:=
       s2 := []int{}
       // 3.make()
       var s3 []int = make([]int, 0)
       fmt.Println(s1, s2, s3)
       // 4.初始化赋值
       var s4 []int = make([]int, 0, 0)
       fmt.Println(s4)
       s5 := []int{1, 2, 3}
       fmt.Println(s5)
       // 5.从数组切片
       arr := [5]int{1, 2, 3, 4, 5}
       var s6 []int
       // 前包后不包
       s6 = arr[1:4]
       fmt.Println(s6)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    切片初始化

    全局:
    var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    var slice0 []int = arr[start:end] 
    var slice1 []int = arr[:end]        
    var slice2 []int = arr[start:]        
    var slice3 []int = arr[:] 
    var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素
    局部:
    arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    slice5 := arr[start:end]
    slice6 := arr[:end]        
    slice7 := arr[start:]     
    slice8 := arr[:]  
    slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    解释:

    1. s[n]:切片中索引位置为n的项
    2. s[:]:从切片的索引位置0~len()-1处所得到的切片
    3. s[low:]:从切片的索引位置low到len()-1处所得到的切片
    4. s[:high]:从切片的索引位置0到high处所得到的切片
    5. s[low:high:max]:从切片的索引位置low到high处所得到的切片
      len = high - low
      cap = max - low
    6. len():切片的长度,小于等于cap()
    7. cap():切片的容量,大于等于len()

    通过make来创建切片

    var slice []type = make([]type, len)
    slice  := make([]type, len)
    slice  := make([]type, len, cap)
    
    • 1
    • 2
    • 3

    用append内置函数操作切片(切片追加)

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        var a = []int{1, 2, 3}
        fmt.Printf("slice a : %v\n", a)
        var b = []int{4, 5, 6}
        fmt.Printf("slice b : %v\n", b)
        c := append(a, b...)
        fmt.Printf("slice c : %v\n", c)
        d := append(c, 7)
        fmt.Printf("slice d : %v\n", d)
        e := append(d, 8, 9, 10)
        fmt.Printf("slice e : %v\n", e)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
        slice a : [1 2 3]
        slice b : [4 5 6]
        slice c : [1 2 3 4 5 6]
        slice d : [1 2 3 4 5 6 7]
        slice e : [1 2 3 4 5 6 7 8 9 10]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注:append :向 slice 尾部添加数据,返回新的 slice 对象。

    超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满

    通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

        cap: 1 -> 2
        cap: 2 -> 4
        cap: 4 -> 8
        cap: 8 -> 16
        cap: 16 -> 32
        cap: 32 -> 64
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    切片拷贝

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        s1 := []int{1, 2, 3, 4, 5}
        fmt.Printf("slice s1 : %v\n", s1)
        s2 := make([]int, 10)
        fmt.Printf("slice s2 : %v\n", s2)
        copy(s2, s1)
        fmt.Printf("copied slice s1 : %v\n", s1)
        fmt.Printf("copied slice s2 : %v\n", s2)
        s3 := []int{1, 2, 3}
        fmt.Printf("slice s3 : %v\n", s3)
        s3 = append(s3, s2...)
        fmt.Printf("appended slice s3 : %v\n", s3)
        s3 = append(s3, 4, 5, 6)
        fmt.Printf("last slice s3 : %v\n", s3)
    
    }
    /*
    结果:
        slice s1 : [1 2 3 4 5]
        slice s2 : [0 0 0 0 0 0 0 0 0 0]
        copied slice s1 : [1 2 3 4 5]
        copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
        slice s3 : [1 2 3]
        appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
        last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        fmt.Println("array data : ", data)
        s1 := data[8:]
        s2 := data[:5]
        fmt.Printf("slice s1 : %v\n", s1)
        fmt.Printf("slice s2 : %v\n", s2)
        copy(s2, s1)
        fmt.Printf("copied slice s1 : %v\n", s1)
        fmt.Printf("copied slice s2 : %v\n", s2)
        fmt.Println("last array data : ", data)
    
    }
    /*
    结果:
        array data :  [0 1 2 3 4 5 6 7 8 9]
        slice s1 : [8 9]
        slice s2 : [0 1 2 3 4]
        copied slice s1 : [8 9]
        copied slice s2 : [8 9 2 3 4]
        last array data :  [8 9 2 3 4 5 6 7 8 9]
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。

    slice遍历

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        slice := data[:]
        for index, value := range slice {
            fmt.Printf("inde : %v , value : %v\n", index, value)
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    切片resize(调整大小)

    如果从一个数组进行切片,则得到一个slice。若对该切片继续进行切片,且切片长度要大于第一次切片的长度。会从原数组中的数据进行调整大小。保证最后一次切片的内容最少要包括原数组内容。

    字符串和切片

    字符串底层是byte的数组,可以进行切片
    string本身是不可变的,因此要改变string中字符。需要如下操作:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        str := "Hello world"
        s := []byte(str) //中文字符需要用[]rune(str)
        s[6] = 'G'
        s = s[:8]
        s = append(s, '!')
        str = string(s)
        fmt.Println(str)
    }
    /*
    结果:
        Hello Go!
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x

    数组or切片转字符串

        strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)
    
    
    • 1
    • 2
  • 相关阅读:
    Android:RecyclerView的ChildHelper
    机器学习中岭回归、LASSO回归和弹性网络与损失函数
    【算法与数据结构】700、LeetCode二叉搜索树中的搜索
    2.1.6.16 漏洞利用-rlogin最高权限登陆
    基于Javamail的邮件收发系统(系统+论文+开题报告+任务书+外文翻译+文献综述+答辩PPT)
    (c语言)五子棋<可修改棋数>
    LiveNVR流媒体服务Onvif/RTSP平台支持云端录像服务器上面集中录像存储在部署LiveNVR的服务器上面
    【RuoYi-Vue-Plus】问题笔记 05 - V3.5.0 Maven 打包导致文件损坏问题
    web渗透测试----5、暴力破解漏洞--(1)SSH密码破解
    Android | 关于 OOM 的那些事
  • 原文地址:https://blog.csdn.net/Re_view/article/details/126584862