• go语言基础笔记


    1.基本类型

    1.1. 基本类型

        bool

        int: int8, int16, int32(rune), int64

        uint: uint8(byte), uint16, uint32, uint64

        float32, float64

        string

        复数:complex64, complex128

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

        array    -- 固定长度的数组

        

    int8 range: -128 127

    int16 range: -32768 32767

    int32 range: -2147483648 2147483647

    int64 range: -9223372036854775808 9223372036854775807

    int32: 0x3e6f54ff 1047483647

    int16: 0x54ff 21759

     // 输出各数值范围

     fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)

    注意:

        byte // uint8 的别名

        rune // int32 的别名 代表一个 Unicode 码

        int  代表  int64

        float 代表  float64

        Go 语言中不允许将整型强制转换为布尔型.,布尔型无法参与数值运算,也无法与其他类型进行转换。

        当一个变量被声明之后,系统自动赋予它该类型的零值: int 为 0,float 为 0.0,bool 为 false,string 为空字符串

        切片slice、map、channel、interface、function的默认为 nil

     

    1.2 值类型和引用类型:

    值类型:int、float、bool、string、数组array、结构体struct

    引用类型:指针、切片slice、map、接口interface、函数func、管道chan

    区别:

    1)值类型:变量直接存储值,内存通常在栈中分配。

    给新的变量赋值时(拷贝时),为拷贝,直接开辟新的内存地址存储值。

    2)引用类型:

    变量直接存储内存地址,这个地址存储值。内存通常再堆上分配。

    给新的变量赋值时(拷贝时),为浅拷贝,新的变量通过指针指向原来的内存地址。可以使用copy关键字实现引用类型的深拷贝。

    当如果没有任何一个变量引用这个地址时,这个地址就会被GC垃圾回收。

    2.变量

    2.1变量的声明

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

    如:var a int = 27

    如果变量没有初始化默认为对应类型的初始值

    2)如果变量没有指定类型可以通过变量的初始值来判断变量类型

     var d = true

    3)使用 :=

    使用格式:名称 :=

    也就是说a := 1相等于:

    var a int

    a =1

    2.2多变量声明

    可以同时声明多个类型相同的变量(非全局变量),如下图所示:

    var x, y int

    var c, d int = 1, 2

    g, h := 123, "hello"

    关于全局变量的声明如下:

    var (

        a int

        b bool

    )

    2.3匿名变量

    匿名变量的特点是一个下画线_,这本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

    使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。

    示例代码如下:

        func GetData() (int, int) {

      

            return 10, 20

        }

        func main(){

      

            a, _ := GetData()

            _, b := GetData()

            fmt.Println(a, b)

        }

    需要注意的是匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。

    2.4 变量作用域

    根据定义位置的不同,可以分为一下三个类型:

    • 局部变量
    • 全局变量以 var 关键字开头外部包中使用首字母必须大写
    • 形式参数形式参数会作为函数的局部变量来使用

    3.数组

    3.1一维数组:

        全局:

        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}, // 别忘了最后一行的逗号。

        }

    代码:

    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"}

    func main() {

        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}, // 别忘了最后一行的逗号。

        }

        fmt.Println(arr0, arr1, arr2, str)

        fmt.Println(a, b, c, d)

    }

    输出结果:

    [1 2 3 0 0] [1 2 3 4 5] [1 2 3 4 5 6] [   hello world tom]

    [1 2 0] [1 2 3 4] [0 0 100 0 200] [{user1 10} {user2 20}]

    3.2多维数组

        全局

        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 纬度不能用 "..."。

    代码:

    var arr0 [5][3]int

    var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

    func main() {

        a := [2][3]int{{1, 2, 3}, {4, 5, 6}}

        b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。

        fmt.Println(arr0, arr1)

        fmt.Println(a, b)

    }

    输出结果:

        [[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]

        [[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]

    内置函数 len 和 cap 都返回数组长度 (元素数量)。如:

    a := [2]int{}

    println(len(a), cap(a))  //得到 2    2

    3.3多维数组遍历:

    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()

        }

    }

    输出结果:

      (0,0)=1 (0,1)=2 (0,2)=3

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

    3.4. 数组拷贝和传参

    package main

    import "fmt"

    func printArr(arr *[5]int) {

        arr[0] = 10

        for i, v := range arr {

            fmt.Println(i, v)

        }

    }

    func main() {

        var arr1 [5]int

        printArr(&arr1)

        fmt.Println(arr1)

        arr2 := [...]int{2, 4, 6, 8, 10}

        printArr(&arr2)

        fmt.Println(arr2)

    }

    4.切片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。

    4.1 创建切片的各种方式

    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)

    }

    4.2. 切片初始化

    全局:

    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] //去掉切片的最后一个元素

    代码:

    package main

    import (

        "fmt"

    )

    var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    var slice0 []int = arr[2:8]

    var slice1 []int = arr[0:6]        //可以简写为 var slice []int = arr[:end]

    var slice2 []int = arr[5:10]       //可以简写为 var slice[]int = arr[start:]

    var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]

    var slice4 = arr[:len(arr)-1]      //去掉切片的最后一个元素

    func main() {

        fmt.Printf("全局变量:arr %v\n", arr)

        fmt.Printf("全局变量:slice0 %v\n", slice0)

        fmt.Printf("全局变量:slice1 %v\n", slice1)

        fmt.Printf("全局变量:slice2 %v\n", slice2)

        fmt.Printf("全局变量:slice3 %v\n", slice3)

        fmt.Printf("全局变量:slice4 %v\n", slice4)

        fmt.Printf("-----------------------------------\n")

        arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}

        slice5 := arr[2:8]

        slice6 := arr[0:6]         //可以简写为 slice := arr[:end]

        slice7 := arr[5:10]        //可以简写为 slice := arr[start:]

        slice8 := arr[0:len(arr)]  //slice := arr[:]

        slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素

        fmt.Printf("局部变量: arr2 %v\n", arr2)

        fmt.Printf("局部变量: slice5 %v\n", slice5)

        fmt.Printf("局部变量: slice6 %v\n", slice6)

        fmt.Printf("局部变量: slice7 %v\n", slice7)

        fmt.Printf("局部变量: slice8 %v\n", slice8)

        fmt.Printf("局部变量: slice9 %v\n", slice9)

    }

    输出结果:

        全局变量:arr [0 1 2 3 4 5 6 7 8 9]

        全局变量:slice0 [2 3 4 5 6 7]

        全局变量:slice1 [0 1 2 3 4 5]

        全局变量:slice2 [5 6 7 8 9]

        全局变量:slice3 [0 1 2 3 4 5 6 7 8 9]

        全局变量:slice4 [0 1 2 3 4 5 6 7 8]

        -----------------------------------

        局部变量: arr2 [9 8 7 6 5 4 3 2 1 0]

        局部变量: slice5 [2 3 4 5 6 7]

        局部变量: slice6 [0 1 2 3 4 5]

        局部变量: slice7 [5 6 7 8 9]

        局部变量: slice8 [0 1 2 3 4 5 6 7 8 9]

        局部变量: slice9 [0 1 2 3 4 5 6 7 8]

    4.3. 通过make来创建切片

        var slice []type = make([]type, len)

        slice  := make([]type, len)

        slice  := make([]type, len, cap)

    使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。

    package main

    import "fmt"

    func main() {

        s := []int{0, 1, 2, 3}

        p := &s[2] // *int, 获取底层数组元素指针。

        *p += 100

        fmt.Println(s)

    }

    输出结果:

        [0 1 102 3]

    至于 [][]T,是指元素类型为 []T 。

    data := [][]int{

            []int{1, 2, 3},

            []int{100, 200},

            []int{11, 22, 33, 44},

        }

        fmt.Println(data)

    输出结果:

        [[1 2 3] [100 200] [11 22 33 44]]

    4.4. 用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)

    }

    输出结果:

        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]

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

    package main

    import (

        "fmt"

    )

    func main() {

        s1 := make([]int, 0, 5)

        fmt.Printf("%p\n", &s1) //0xc42000a060

        s2 := append(s1, 1)

        fmt.Printf("%p\n", &s2)//0xc42000a080

        fmt.Println(s1, s2) //[] [1]

    }

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

    func main() {

        data := [...]int{0, 1, 2, 3, 4, 10: 0}

        s := data[:2:3]

        s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。

        fmt.Println(s, data)         // 重新分配底层数组,与原数组无关。

        fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。

       //得到 [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]

        //0xc4200160f0 0xc420070060

    }

    从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。 通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。

    4.6. slice中cap重新分配规律:

    package main

    import (

        "fmt"

    )

    func main() {

        s := make([]int, 0, 1)

        c := cap(s)

        for i := 0; i < 50; i++ {

            s = append(s, i)

            if n := cap(s); n > c {

                fmt.Printf("cap: %d -> %d\n", c, n)

                c = n

            }

        }

    }

    输出结果:

        cap: 1 -> 2

        cap: 2 -> 4

        cap: 4 -> 8

        cap: 8 -> 16

        cap: 16 -> 32

        cap: 32 -> 64

    4.7. 切片拷贝

    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]

    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]

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

    4.8. slice遍历:

    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)

        }

    }

    输出结果:

        inde : 0 , value : 0

        inde : 1 , value : 1 等.....

        

    4.9. 切片resize(调整大小)

    var a = []int{1, 3, 4, 5}

    fmt.Printf(a : %v , len(a) : %v\n", a, len(a))//a : [1 3 4 5] , len(a) : 4

    b := a[1:2]

    fmt.Printf("b : %v , len(b) : %v\n", b, len(b))// b: [3]

    c := b[0:3]

    fmt.Printf("c : %v , len(c) : %v\n", c, len(c))// c: [3,4,5]

    注意:

    c := b[0:3]  的结果为什么是[3,4,5]:上述b的结果是切片[3] 由于bb是切片[3],它只有一个元素。尝试使用索引0到3的范围去索引这个切片会导致一个越界的错误。在Go中,当一个切片被越界索引时,它会返回一个新的切片,该切片与原始切片共享底层数组,但具有指定的长度和容量。

    在这种情况下,bb[0:3]返回的切片将是[3 4 5]。这是因为:

    ·起始索引0对应于原始切片的第一个元素(在这种情况下,这是3)。

    ·结束索引3对应于原始切片的第四个元素(越界到5)。

    因此,c的结果是切片[3 4 5]。

    4.10. 数组和切片的内存布局

    4.11. 字符串和切片(string and slice)

    string底层就是一个byte的数组,因此,也可以进行切片操作。

    字符串转换成切片

    []byte(str)用于纯英文转换

    []rune(str)用于含有中文字符的转换

    如:

    (1) 英文字符串:[]byte

    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!

    (2) 中文字符串:[]rune

    str := "你好,世界!hello world!"

    s := []rune(str)

    s[3] = '嘿'

    s[4] = '哈'

    s[12] = 'g'

    s = s[:14]

    str = string(s)

    fmt.Println(str)// 你好,嘿哈!Hello go

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

    golang slice data[:6:8] 两个冒号的理解

    常规slice , data[6:8],从第6位到第8位(返回6, 7),长度len为2, 最大可扩充长度cap为4(6-9)

    另一种写法: data[:6:8] 每个数字前都有个冒号, slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8

    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    d1 := slice[6:8]

    fmt.Println(d1, len(d1), cap(d1))//[6 7] 2 4

    d2 := slice[:6:8]

    fmt.Println(d2, len(d2), cap(d2))//[0 1 2 3 4 5] 6 8

    4.13 数组或切片转换成字符串:

    str := strings.Replace(strings.Trim(fmt.Sprint(要转换的数组或切片), "[]"), " ", ",", -1)

    如:

     slice := []int{0, 1, 2, 3}

    //数组或切片转换成字符串

    str := strings.Replace(strings.Trim(fmt.Sprint(slice), "[]"), " ", ",", -1)

    fmt.Printf("%T,%v", str, str) //得到string, 0,1,2,3

    package main

    import (

        "fmt"

    )

    func main() {

        str := "hello world"

        s1 := str[0:5]

        fmt.Println(s1)//hello

        s2 := str[6:]

        fmt.Println(s2)//world

    }

    5. 指针

    Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:

    &(取地址)  和    *(根据地址取值)。

    5.1 指针声明和初始化

    和基础类型数据相同,在使用指针变量之前我们首先需要申明指针,声明格式如下:var var_name *var-type,其中的var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。

    代码举例如下:

    var ip *int        /* 指向整型*/

    var fp *float32    /* 指向浮点型 */

    指针的初始化就是取出相对应的变量地址对指针进行赋值,具体如下:

       var a int= 20   /* 声明实际变量 */

       var ip *int        /* 声明指针变量 */

       ip = &a  /* 指针变量的存储地址 */

        a := 10

        b := &a

        fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078

        fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int

        fmt.Println(&b)                    // 0xc00000e018

    5.2指针取值

    在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。

    func main() {

        //指针取值

        a := 10

        b := &a // 取变量a的地址,将指针保存到b中

        fmt.Printf("type of b:%T\n", b)

        c := *b // 指针取值(根据指针去内存取值)

        fmt.Printf("type of c:%T\n", c)

        fmt.Printf("value of c:%v\n", c)

    }

    得到:

      type of b:*int

      type of c:int

      value of c:10

    总结: 取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

    变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:\

        1.对变量进行取地址(&)操作,可以获得这个变量的指针变量。

        2.指针变量的值是指针地址。

        3.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

    指针传值示例:

    func modify1(x int) {

        x = 100

    }

    func modify2(x *int) {

        *x = 100

    }

    func main() {

        a := 10

        modify1(a)

        fmt.Println(a) // 10

        modify2(&a)

        fmt.Println(a) // 100

    }

    5.3 空指针

    当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。

    func main() {

        var p *string

        fmt.Println(p)

        fmt.Printf("p的值是%s/n", p)

        if p != nil {

            fmt.Println("非空")

        } else {

            fmt.Println("空值")

        }

    }

    5.4. new和make

    new它是一个内置的函数,它的函数签名: func new(Type) *Type

    其中,

        1.Type表示类型,new函数只接受一个参数,这个参数是一个类型

        2.*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

    new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:

    func main() {

        a := new(int)

        b := new(bool)

        fmt.Printf("%T\n", a) // *int

        fmt.Printf("%T\n", b) // *bool

        fmt.Println(*a)       // 0

        fmt.Println(*b)       // false

    }

    如var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

    func main() {

        var a *int

        a = new(int)

        *a = 10

        fmt.Println(*a)

    }

    make:

    make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:

    func make(t Type, size ...IntegerType) Type

    make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。

    var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:

    func main() {

        var b map[string]int

        b = make(map[string]int, 10)

        b["测试"] = 100

        fmt.Println(b)

    }

    5.5. new与make的区别

        1.二者都是用来做内存分配的。

        2.make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;

        3.而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

    6. Map

    map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

    6.1. map定义

    Go语言中 map的定义语法如下  map[KeyType]ValueType

    其中,KeyType:表示键的类型,   ValueType:表示键对应的值的类型。

    map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:

        make(map[KeyType]ValueType, [cap])

    其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。

    var result = make(map[string]interface{})

    6.2. map基本使用

    map中的数据都是成对出现的,map的基本使用示例代码如下:

    全局:

    var result = make(map[string]interface{})

    func main() {

        //局部

        scoreMap := make(map[string]int, 8)

        scoreMap["张三"] = 90

        scoreMap["小明"] = 100

        fmt.Println(scoreMap)

        fmt.Println(scoreMap["小明"])

        fmt.Printf("type of a:%T\n", scoreMap)

    }

    输出:

        map[小明:100 张三:90]

        100

        type of a:map[string]int

    map也支持在声明的时候填充元素,例如:

    func main() {

        userInfo := map[string]string{

            "username": "pprof.cn",

            "password": "123456",

        }

        fmt.Println(userInfo) //

    }

    6.3. 判断某个键是否存在

    Go语言中有个判断map中键是否存在的特殊写法,格式如下:

        value, ok := map[key]

    举个例子:

    func main() {

        scoreMap := make(map[string]int)

        scoreMap["张三"] = 90

        scoreMap["小明"] = 100

        // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值

        v, ok := scoreMap["张三"]

        if ok {

            fmt.Println(v)

        } else {

            fmt.Println("查无此人")

        }

    }

    6.4. map的遍历

    Go语言中使用for range遍历map。

    func main() {

        scoreMap := make(map[string]int)

        scoreMap["张三"] = 90

        scoreMap["小明"] = 100

        scoreMap["王五"] = 60

        for k, v := range scoreMap {

            fmt.Println(k, v)

        }

    }

    但我们只想遍历key的时候,可以按下面的写法:

    func main() {

        scoreMap := make(map[string]int)

        scoreMap["张三"] = 90

        scoreMap["小明"] = 100

        scoreMap["王五"] = 60

        for k := range scoreMap {

            fmt.Println(k)

        }

    }

    注意: 遍历map时的元素顺序与添加键值对的顺序无关。

    6.5. 使用delete()函数删除键值对

    使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

        delete(map, key)

    其中,

        map:表示要删除键值对的map

        key:表示要删除的键值对的键

    示例代码如下:

    func main(){

        scoreMap := make(map[string]int)

        scoreMap["张三"] = 90

        scoreMap["小明"] = 100

        scoreMap["王五"] = 60

        delete(scoreMap, "小明")//将小明:100从map中删除

        for k,v := range scoreMap{

            fmt.Println(k, v)

        }

    }

    6.6值为inteface的map

    var result = make(map[string]interface{}) //值是inteface表示值可以是任意类型的

    var bb = map[string]interface{}{

       "name": "1111",

       "age":  11,

    }

    6.7. 元素为map类型的切片

    下面的代码演示了切片中的元素为map类型时的操作:

    func main() {

        var mapSlice = make([]map[string]string, 3)

        for index, value := range mapSlice {

            fmt.Printf("index:%d value:%v\n", index, value)

        }

        fmt.Println("after init")

        // 对切片中的map元素进行初始化

        mapSlice[0] = make(map[string]string, 10)

        mapSlice[0]["name"] = "王五"

        mapSlice[0]["password"] = "123456"

        mapSlice[0]["address"] = "红旗大街"

        for index, value := range mapSlice {

            fmt.Printf("index:%d value:%v\n", index, value)

        }

    }

    6.8. 值为切片类型的map

    下面的代码演示了map中值为切片类型的操作:

    func main() {

        var sliceMap = make(map[string][]string, 3)

        fmt.Println(sliceMap)

        fmt.Println("after init")

        key := "中国"

        value, ok := sliceMap[key]

        if !ok {

            value = make([]string, 0, 2)

        }

        value = append(value, "北京", "上海")

        sliceMap[key] = value

        fmt.Println(sliceMap)

    }

    7.结构体

    7.1. 自定义类型

    在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。

    自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

    //将MyInt定义为int类型

        type MyInt int

    通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。

    7.2. 结构体

    Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

    Go语言中通过struct来实现面向对象。

    7.2.1. 结构体的定义

    使用type和struct关键字来定义结构体,具体代码格式如下:

        type 类型名 struct {

            字段名 字段类型

            字段名 字段类型

            …

        }

    其中:

        1.类型名:标识自定义结构体的名称,在同一个包内不能重复。

        2.字段名:表示结构体字段名。结构体中的字段名必须唯一。

        3.字段类型:表示结构体字段的具体类型。

    同样类型的字段也可以写在一行,

        type person1 struct {

            name, city string

            age        int8

        }

    7.2.2. 结构体实例化

    只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

    结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

        var 结构体实例 结构体类型

    7.2.3. 基本实例化

    type person struct {

        name string

        city string

        age  int8

    }

    func main() {

        var p1 person

        p1.name = "pprof.cn"

        p1.city = "北京"

        p1.age = 18

        fmt.Printf("p1=%v\n", p1)  //p1={pprof.cn 北京 18}

        fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"pprof.cn", city:"北京", age:18}

    }

    我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。

    7.2.3. 匿名结构体

    在定义一些临时数据结构等场景下还可以使用匿名结构体。

    package main

    import (

        "fmt"

    )

    func main() {

        var user struct{Name string; Age int}

        user.Name = "pprof.cn"

        user.Age = 18

        fmt.Printf("%#v\n", user)

    }

    7.2.1. 创建指针类型结构体

    我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:

        var p2 = new(person)

        fmt.Printf("%T\n", p2)     //*main.person

        fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

    从打印的结果中我们可以看出p2是一个结构体指针。

    需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。

    var p2 = new(person)

    p2.name = "测试"

    p2.age = 18

    p2.city = "北京"

    fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"测试", city:"北京", age:18}

    7.2.2. 取结构体的地址实例化

    使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

    p3 := &person{}

    fmt.Printf("%T\n", p3)     //*main.person

    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}

    p3.name = "博客"

    p3.age = 30

    p3.city = "成都"

    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}

    p3.name = "博客"其实在底层是(*p3).name = "博客",这是Go语言帮我们实现的语法糖。

    7.2.3. 结构体初始化

    type person struct {

        name string

        city string

        age  int8

    }

    func main() {

        var p4 person

        fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}

    }

    7.2.4. 使用键值对初始化

    使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

    p5 := person{

        name: "pprof.cn",

        city: "北京",

        age:  18,

    }

    fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}

    也可以对结构体指针进行键值对初始化,例如:

    p6 := &person{

        name: "pprof.cn",

        city: "北京",

        age:  18,

    }

    fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18}

    当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

    p7 := &person{

        city: "北京",

    }

    fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

    7.2.5. 使用值的列表初始化

    初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:

    p8 := &person{

        "pprof.cn",

        "北京",

        18,

    }

    fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}

    使用这种格式初始化时,需要注意:

        1.必须初始化结构体的所有字段。

        2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。

        3.该方式不能和键值初始化方式混用。

    7.2.6. 结构体内存布局

    type test struct {

        a int8

        b int8

        c int8

        d int8

    }

    n := test{

        1, 2, 3, 4,

    }

    fmt.Printf("n.a %p\n", &n.a)

    fmt.Printf("n.b %p\n", &n.b)

    fmt.Printf("n.c %p\n", &n.c)

    fmt.Printf("n.d %p\n", &n.d)

    输出:

        n.a 0xc0000a0060

        n.b 0xc0000a0061

        n.c 0xc0000a0062

        n.d 0xc0000a0063

    7.2.7. 构造函数

    Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。

    func newPerson(name, city string, age int8) *person {

        return &person{

            name: name,

            city: city,

            age:  age,

        }

    }

    调用构造函数

    p9 := newPerson("pprof.cn", "测试", 90)

    fmt.Printf("%#v\n", p9)

    7.2.8. 方法和接收者

    Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

    方法的定义格式如下:

        func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {

            函数体

        }

    其中,

        1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。

        2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。

        3.方法名、参数列表、返回参数:具体格式与函数定义相同。

    举个例子:

    //Person 结构体

    type Person struct {

        name string

        age  int8

    }

    //NewPerson 构造函数

    func NewPerson(name string, age int8) *Person {

        return &Person{

            name: name,

            age:  age,

        }

    }

    //Dream Person做梦的方法

    func (p Person) Dream() {

        fmt.Printf("%s的梦想是学好Go语言!\n", p.name)

    }

    func main() {

        p1 := NewPerson("测试", 25)

        p1.Dream()

    }

    方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

    7.2.9. 指针类型的接收者

    指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

        // SetAge 设置p的年龄

        // 使用指针接收者

        func (p *Person) SetAge(newAge int8) {

            p.age = newAge

        }

    调用该方法:

    func main() {

        p1 := NewPerson("测试", 25)

        fmt.Println(p1.age) // 25

        p1.SetAge(30)

        fmt.Println(p1.age) // 30

    }

    7.2.10. 值类型的接收者

    当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

    // SetAge2 设置p的年龄

    // 使用值接收者

    func (p Person) SetAge2(newAge int8) {

        p.age = newAge

    }

    func main() {

        p1 := NewPerson("测试", 25)

        p1.Dream()

        fmt.Println(p1.age) // 25

        p1.SetAge2(30) // (*p1).SetAge2(30)

        fmt.Println(p1.age) // 25

    }

    7.2.11. 什么时候应该使用指针类型接收者

        1.需要修改接收者中的值

        2.接收者是拷贝代价比较大的大对象

        3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

    7.2.12. 任意类型添加方法

    在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

    //MyInt 将int定义为自定义MyInt类型

    type MyInt int

    //SayHello 为MyInt添加一个SayHello的方法

    func (m MyInt) SayHello() {

        fmt.Println("Hello, 我是一个int。")

    }

    func main() {

        var m1 MyInt

        m1.SayHello() //Hello, 我是一个int。

        m1 = 100

        fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt

    }

    注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

    7.2.13. 结构体的匿名字段

    结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

    //Person 结构体Person类型

    type Person struct {

        string

        int

    }

    func main() {

        p1 := Person{

            "pprof.cn",

            18,

        }

        fmt.Printf("%#v\n", p1)        //main.Person{string:"pprof.cn", int:18}

        fmt.Println(p1.string, p1.int) //pprof.cn 18

    }

    匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

    7.2.14. 嵌套结构体

    一个结构体中可以嵌套包含另一个结构体或结构体指针。

    //Address 地址结构体

    type Address struct {

        Province string

        City     string

    }

    //User 用户结构体

    type User struct {

        Name    string

        Gender  string

        Address Address

    }

    func main() {

        user1 := User{

            Name:   "pprof",

            Gender: "女",

            Address: Address{

                Province: "黑龙江",

                City:     "哈尔滨",

            },

        }

        fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}

    }

    7.2.15. 嵌套匿名结构体

    //Address 地址结构体

    type Address struct {

        Province string

        City     string

    }

    //User 用户结构体

    type User struct {

        Name    string

        Gender  string

        Address //匿名结构体

    }

    func main() {

        var user2 User

        user2.Name = "pprof"

        user2.Gender = "女"

        user2.Address.Province = "黑龙江"    //通过匿名结构体.字段名访问

        user2.City = "哈尔滨"                //直接访问匿名结构体的字段名

        fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}

    }

    当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。

    7.2.16. 嵌套结构体的字段名冲突

    嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

    //Address 地址结构体

    type Address struct {

        Province   string

        City       string

        CreateTime string

    }

    //Email 邮箱结构体

    type Email struct {

        Account    string

        CreateTime string

    }

    //User 用户结构体

    type User struct {

        Name   string

        Gender string

        Address

        Email

    }

    func main() {

        var user3 User

        user3.Name = "pprof"

        user3.Gender = "女"

        // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime

        user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime

        user3.Email.CreateTime = "2000"   //指定Email结构体中的CreateTime

    }

    7.2.17. 结构体的“继承”

    Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

    //Animal 动物

    type Animal struct {

        name string

    }

    func (a *Animal) move() {

        fmt.Printf("%s会动!\n", a.name)

    }

    //Dog 狗

    type Dog struct {

        Feet    int8

        *Animal //通过嵌套匿名结构体实现继承

    }

    func (d *Dog) wang() {

        fmt.Printf("%s会汪汪汪~\n", d.name)

    }

    func main() {

        d1 := &Dog{

            Feet: 4,

            Animal: &Animal{ //注意嵌套的是结构体指针

                name: "乐乐",

            },

        }

        d1.wang() //乐乐会汪汪汪~

        d1.move() //乐乐会动!

    }

    7.2.18. 结构体字段的可见性

    结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

    7.2.19. 结构体与JSON序列化

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

    //Student 学生

    type Student struct {

        ID     int

        Gender string

        Name   string

    }

    //Class 班级

    type Class struct {

        Title    string

        Students []*Student

    }

    func main() {

        c := &Class{

            Title:    "101",

            Students: make([]*Student, 0, 200),

        }

        for i := 0; i < 10; i++ {

            stu := &Student{

                Name:   fmt.Sprintf("stu%02d", i),

                Gender: "男",

                ID:     i,

            }

            c.Students = append(c.Students, stu)

        }

        //JSON序列化:结构体-->JSON格式的字符串

        data, err := json.Marshal(c)

        if err != nil {

            fmt.Println("json marshal failed")

            return

        }

        fmt.Printf("json:%s\n", data)

        //JSON反序列化:JSON格式的字符串-->结构体

        str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`

        c1 := &Class{}

        err = json.Unmarshal([]byte(str), c1)

        if err != nil {

            fmt.Println("json unmarshal failed!")

            return

        }

        fmt.Printf("%#v\n", c1)

    }

    7.2.20. 结构体标签(Tag)

    Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

    Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

        `key1:"value1" key2:"value2"`

    结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

    例如我们为Student结构体的每个字段定义json序列化时使用的Tag:

    //Student 学生

    type Student struct {

        ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的key

        Gender string //json序列化是默认使用字段名作为key

        name   string //私有不能被json包访问

    }

    func main() {

        s1 := Student{

            ID:     1,

            Gender: "女",

            name:   "pprof",

        }

        data, err := json.Marshal(s1)

        if err != nil {

            fmt.Println("json marshal failed!")

            return

        }

        fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"女"}

    }

    7.2.21 类型别名

    类型别名是Go1.9版本添加的新功能。

    类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型

    我们之前见过的rune和byte就是类型别名,他们的定义如下:

        type byte = uint8

        type rune = int32

    7.3.22 类型定义和类型别名的区别

    类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

    //类型定义

    type NewInt int

    //类型别名

    type MyInt = int

    func main() {

        var a NewInt

        var b MyInt

        fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt

        fmt.Printf("type of b:%T\n", b) //type of b:int

    }

    结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

    8.流程控制

    8.1.循环语句for

        s := "abcd"

        for i, n := 0, length(s); i < n; i++ {     // 避免多次调用 length 函数。

            println(i, s[i])

        }

    8.2循环语句range

    Golang range类似迭代器操作,返回 (索引, 值) 或 (键, 值)。

    for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

    for key, value := range oldMap {

        newMap[key] = value

    }

    可忽略不想要的返回值, "_" 这个特殊变量。

        s := "abc"

        // 忽略 index。

        for _, c := range s {

            println(c)

        }

    8.3switch 语句

    switch默认的条件:bool=true

    fallthrough(穿透)会强制执行后面的case语句,不管下一条表达式的结果是否为true

    8.4循环控制Goto、Break、Continue

    Goto、Break、Continue:

    break 跳出后不会再进入循环

    continue 跳出后会在进入循环,但不执行初始化

    goto 则是调整执行位置,相当于代码跳到L的位置再次执行

    9.函数:

    函数是引用传递:

    普通函数和高阶函数:

    9.1.defer延迟调用:

    defer特性:

        1. 关键字 defer 用于注册延迟调用。

        2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。

        3. 多个defer语句,按先进后出的方式执行。

        4. defer语句中的变量,在defer声明时就决定了。

    defer用途:

        1. 关闭文件句柄

        2. 锁资源释放

        3. 数据库连接释放

    9.2. 内置函数

    Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。

        append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice

        close           -- 主要用来关闭channel

        delete            -- 从map中删除key对应的value

        panic            -- 停止常规的goroutine  (panic和recover:用来做错误处理)

        recover         -- 允许程序定义goroutine的panic动作

        imag            -- 返回complex的实部   (complex、real imag:用于创建和操作复数)

        real            -- 返回complex的虚部

        make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)

        new                -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针

        cap                -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)

        copy            -- 用于复制和连接slice,返回复制的数目

        len                -- 来求长度,比如string、array、slice、map、channel ,返回长度

        print、println     -- 底层打印函数,在部署环境中建议使用 fmt 包

    10.数据类型转换:

    10.1类型转换是将一种数据类型的变量转为另一种类型的变量

    Go强制要求使用显式类型转换。所以语法更能确定语句及表达式的明确含义

    转换的时候如果大的转给小的,会有精度损失(数据溢出)比如int64转int8

    转换格式:

    // 将v转成T类型,但是v本身的数据类型并不会改变,只是把v变量的值类型转成T

    达式 T(v)   

    如: string(data)   int(a)

    var a int32 = 1999999      // 小转大一样要显示转换

    var b float64 = float64(a) // a转b  a本身数据类型并不会改变,只是把a的值(1999999)转成了float64

    10.2基本类型转string:

    (1)Sprint和Sprintf()

    fmt.Sprintf():格式化为字符串

    fmt.Sprint():格式化为字符串

    Sprintf和printf的区别:printf是将一个格式化的字符串打印到控制台,Sprintf是转换为字符串

    格式:

    接收变量 = fmt.Sprintf(%格式符,参数列表)

    接收变量 = fmt.Sprint(参数)

    上述的参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。

    如:

    var (

    num1 int     = 9

    num2 float64 = 9.99

    b    bool    = false

    c    byte    = 'a'

    str  string

    )

    str = fmt.Sprintf("%d", num1)

    //str的类型: string      值:9

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    str = fmt.Sprintf("%f", num2)

    //str的类型: string      值:9.990000

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    str = fmt.Sprintf("%t", b)

       //str的类型: string      值:false

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    str = fmt.Sprintf("%c", c)

    //str的类型: string      值:a

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    (2)strconv.Format方式:

    strconv.Itoa可以将数字转换成字符串类型的数字

    var (

    num  int     = 24

    num2 float64 = 1.111

    str  string

    )

    // FormatInt参数1:要转的变量  参数2:进制

    str = strconv.FormatInt(int64(num), 10)

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    // strconv.FormatInt也可以用来转换进制,比如将10进制转换为2进制,其它进制,换掉后面的数字就可以了

    str = strconv.FormatInt(123, 2)

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    // 'f':格式  10:保留10位   64:float64

    str = strconv.FormatFloat(num2, 'f', 10, 64)

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    str = strconv.FormatBool(false)

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    str = strconv.Itoa(num)

    fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)

    输出:

    str的类型: string      值:24

    str的类型: string      值:1111011

    str的类型: string      值:1.1110000000

    str的类型: string      值:false

    str的类型: string      值:24

    10.3string转基本类型:

    将string类型转换成基本数据类型时,要确保string类型能够转成有有效的数据

    例:可以把"123"转成int类型,但是不可以将"aaa"转成int类型,编译器不会报错,go会把它变成默认值0,因为go会判断这个值能不能转成有效的数据,如果不可以会按照该数据的数据类型的默认值赋值。

    strconv.ParseInt:

    func main() {

    var (

    str string = "123"

    i   int64  // 这里只能用int64

    f   float64

    b   bool

    )

    // str:字符串base:进制bitSize:数据类型

    i, _ = strconv.ParseInt(str, 10, 64)

    fmt.Printf("i的类型: %T\t 值:%v\n ", i, i)

    f, _ = strconv.ParseFloat(str, 64)

    fmt.Printf("f的类型: %T\t 值:%v\n ", f, f)

    b, _ = strconv.ParseBool(str)

    fmt.Printf("b的类型: %T\t 值:%v\n ", b, b)

    s, _ := strconv.Atoi("str")

    fmt.Printf("s的类型: %T\t 值:%v\n ", s, s)

    }

    输出:

    i的类型: int64     值:123

    f的类型: float64   值:123

    b的类型: bool      值:false

    s的类型: int       值:0

    10.4 inteface{}转字符串

    使用.(类型)并在括号中传入想要解析的任何类型

    格式: 变量名.(要转换的类型)

    如name.(string)  将interface{}类型的name转换成string

    var daName interface{}

    fmt.Println(daName.(string))

    注:若不确定interface类型时候,使用变量名.(type)结合switch case来做判断。

    switch value.(type) {

        case string:

            // 将interface转为string字符串类型

            op, ok := value.(string)

            fmt.Println(op, ok)

        case int32:

            // 将interface转为int32类型

            op, ok := value.(int32)

            fmt.Println(op, ok)

        case int64:

            // 将interface转为int64类型

            op, ok := value.(int64)

            fmt.Println(op, ok)

        case User:

            // 将interface转为User struct类型,并使用其Name对象

            op, ok := value.(User)

            fmt.Println(op.Name, ok)

        case []int:

            // 将interface转为切片类型

            op := make([]int, 0)  //[]

            op = value.([]int)

            fmt.Println(op)

        default:

            fmt.Println("unknown")

        }

    10.5字符串转字节切片

    func main() {

    // 字符串转切片

        var b = []byte("aaaaa")

    fmt.Println("b = ", b)

    // 切片转字符串

    var str = string([]byte{97, 98, 99})

    fmt.Println("str = ", str)

    }

    输出:b =  [105 116 122 104 117 122 104 117]

    str =  abc

    byte类型的切片([]byte)与string可以相互转换

    如:

     s:="上海"

    bslice := []byte(s)

    fmt.Printf("bslice的类型是: %T,值是:%v", bslice,bslice) //得到

    []byte转换成string:

    10.6 string 与 int 类型之间的转换:

    10.6.1 strconv包

    1) Itoa():整型转字符串

    func Itoa(i int) string

    示例代码如下:

    func main() {

        num := 100

        str := strconv.Itoa(num)

        fmt.Printf("type:%T value:%#v\n", str, str)

    }

    运行结果如下所示:

    type:string value:“100”

    2) Atoi():字符串转整型

    func Atoi(s string) (i int, err error)

    func main() {

        str1 := "110"

        str2 := "s100"

        num1, err := strconv.Atoi(str1)

        if err != nil {

            fmt.Printf("%v 转换失败!", str1)

        } else {

            fmt.Printf("type:%T value:%#v\n", num1, num1)

        }

    num2, err := strconv.Atoi(str2)

    if err != nil {

            fmt.Printf("%v 转换失败!", str2)

        } else {

            fmt.Printf("type:%T value:%#v\n", num2, num2)

        }

    }

    10.6.2 Parse 系列函数

    Parse 系列函数用于将字符串转换为指定类型的值,其中包括 ParseBool()、ParseFloat()、ParseInt()、ParseUint()。

    1) ParseBool() 函数用于将字符串转换为 bool 类型的值,它只能接受 1、0、t、f、T、F、true、false、True、False、TRUE、FALSE,其它的值均返回错误,函数签名如下。

    func ParseBool(str string) (value bool, err error)

    2) ParseInt()

    ParseInt() 函数用于返回字符串表示的整数值(可以包含正负号),函数签名如下:

    func ParseInt(s string, base int, bitSize int) (i int64, err error)

    参数说明:

    base 指定进制,取值范围是 2 到 36。如果 base 为 0,则会从字符串前置判断,“0x”是 16 进制,“0”是 8 进制,否则是 10 进制。

    bitSize 指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64。

    返回的 err 是 *NumErr 类型的,如果语法有误,err.Error = ErrSyntax,如果结果超出类型范围 err.Error = ErrRange。

    11.fmt常用输出

    11.1 Print、Println 和 Printf

    Print 和 Println:用于将数据输出到标准输出(通常是终端)。

    Print 函数输出数据后不换行

    Println 函数输出数据后会自动换行。

    Printf:用于将格式化的数据输出到标准输出。它使用类似 C 语言的格式化字符串,可以通过占位符指定输出数据的格式和位置。

    11.2 Sprint、Sprintln 和 Sprintf

    SprintSprintln 和 Sprintf 是 Go 语言 fmt 包提供的函数,用于将格式化的数据转换为字符串。

    这些函数的命名类似于 PrintPrintln 和 Printf 函数,但是它们不是将数据输出到标准输出,而是将数据格式化为字符串并返回。

    Sprint 函数将传入的数据格式化为字符串,并返回该字符串。它接受可变数量的参数,并按照指定的格式进行格式化。

    Sprintln 函数与 Sprint 函数类似,但在每个参数之间会添加一个空格,并在最后添加一个换行符(\n)。

    Sprintf 函数根据指定的格式将传入的数据格式化为字符串,并返回该字符串。它接受一个格式化字符串作为第一个参数,然后根据该格式化字符串和后续的参数进行格式化

    如:

    var name = "Alice"

    var age = 25

    var height = 1.65

    // 使用 Sprint 格式化为字符串,并赋值给变量

    var info1 = fmt.Sprint("Name:", name, ", Age:", age, ", Height:", height)

    fmt.Println(info1)

    // 使用 Sprintln 格式化为字符串,并赋值给变量

    var info2 = fmt.Sprintln("Name:", name, ", Age:", age, ", Height:", height)

    fmt.Println(info2)

    // 使用 Sprintf 进行格式化,并赋值给变量

    var info3 = fmt.Sprintf("Name: %s, Age: %d, Height: %.2f", name, age, height)

    fmt.Println(info3)

    11.3Fprint、Fprintln 和 Fprintf

    FprintFprintln 和 Fprintf 是 Go 语言 fmt 包提供的函数,用于将格式化的数据输出到指定的 io.Writer。

    这些函数与 PrintPrintln 和 Printf 函数类似,但是它们不是将数据输出到标准输出,而是将数据格式化后输出到指定的 io.Writer,例如文件、网络连接等。

    · Fprint 函数将传入的数据格式化为字符串,并将结果输出到指定的 io.Writer。它接受一个 io.Writer 类型的参数作为第一个参数,后面可以跟上可变数量的参数。

    · Fprintln 函数与 Fprint 函数类似,但在每个参数之间会添加一个空格,并在最后添加一个换行符(\n)。

    · Fprintf 函数根据指定的格式将传入的数据格式化为字符串,并将结果输出到指定的 io.Writer。它接受一个 io.Writer 类型的参数作为第一个参数,后面跟着一个格式化字符串和后续的参数。

    fmt.Scan()

    fmt.Scanf()

    fmt.Scanln()

    12.go语言中常见占位符含义:

    %d:十进制整数。

    %f:浮点数。

    %s:字符串。

    %t:布尔值。

    %T:数据的类型

    %p 输出指针地址 十六进制表示

    %v:通用格式,默认格式化为相应值的字符串

    %+v:获取数据的值,如果结构体,会携带字段名。

    %#v:获取数据的值,如果是结构体,会携带结构体名和字段名。

    %b 一个二进制整数,将一个整数格式转化为二进制的表达方式

    %c 一个Unicode的字符

    %d 十进制整数

    %s字符串或字节切片。

    %o 八进制整数

    %x 小写的十六进制数值

    %X 大写的十六进制数值

    %U 一个Unicode表示法表示的整型码值

    %s 输出以原生的UTF8字节表示的字符,如果console不支持utf8编码,则会乱码

    %t 以true或者false的方式输出布尔值

    %v 使用默认格式输出值,或者如果方法存在,则使用类性值的String()方法输出自定义值

    %% 字面上的一个百分号

     %b 二进制

     %c   Unicode 码转字符。

    fmt.Printf("%c"0x82d7)// 输出

    %d、%5d、%-5d、%05d 十进制整数表示。

    fmt.Printf("%d,%d,%d"100100x10)// 输出10,8,16

    三个数据: 10 十进制,010 八进制,0x10 十六进制

    %q 同 %c 类似,都是Unicode 码转字符,只是结果多了单引号。

    fmt.Printf("%q"0x82d7)// 输出'苗'

    汉字对应表:字体编辑用中日韩汉字Unicode编码表 - 编著:资深中韩翻译金圣镇 金圣镇

    %x、%#x 十六进制表示,字母形式为小写 a-f,%#x 输出带 0x 开头。

    fmt.Printf("%x, %#x"1313)// 输出d, 0xd

    %X、%#X十六进制表示,字母形式为小写 A-F,%#X 输出带 0X 开头。

    fmt.Printf("%X, %#X"1313)// 输出D, 0XD

     %U:转化为 Unicode 格式规范。

    fmt.Printf("%U"0x82d7)// 输出U+82D7

    %#U:转化为 Unicode 格式并带上对应的字符。

    fmt.Printf("%#U"0x82d7)// 输出U+82D7 '苗'

     %b 浮点数转化为 2 的幂的科学计数法。

    fmt.Printf("%b"0.1)// 输出7205759403792794p-56

    %f、%.2f 等等

    浮点数,%.2f 表示保留 2 位小数,%f 默认保留 6 位,%f 与 %F 等价。

    保留的规则我现在还没有搞清楚,有时候符合四舍五入,有时候不符合,容我下来研究下,再告诉大家。

    fmt.Printf("%f"10.2)// 输出10.200000

    fmt.Printf("%.2f|%.2f"10.23210.235)// 输出10.23|10.23

    %q 有 Go 语言安全转义,双引号包裹。

    fmt.Printf("%q""老苗")// 输出"老苗"

    指针%p、%#p :地址,使用十六进制表示,%p 带 0x,%#p 不带。

    num := 2s := []int{12}fmt.Printf("%p|%p", &num, s)

    // 输出0xc00000a1d0|0xc00000a1e0

    13.常用

    对切片进行排序(string类型的切片):  sort.Strings(slice)

    //对切片进行排序(string类型的切片)

    var slice = []string{"a", "f", "d"}

    sort.Strings(slice)

    fmt.Println(slice) //[a d f]

    判断字符串中是否含有某字符:

    判断字符串是否以prefix开头strings.HasPrefix(str, "he")

    s := "hello world hello world"

    //判断字符串s是否以prefix开头

    ret := strings.HasPrefix(s, "he")

    fmt.Println(ret)

  • 相关阅读:
    Nginx代理配置详解
    linux配置java-web站点
    git 如何删除某个提交之后的所有提交内容
    好好回答下 TCP 和 UDP 的区别
    C++避坑小知识&&错题笔记
    基于HTML仿oppo手机商城电商项目的设计与实现6个页面
    Flink部署
    ubuntu 配置 ros 系统 以及简单使用
    影像仪激光扫描功能,无缝连接2D/3D混合测量
    最新最全的JavaScript入门视频,包含小程序和uniapp相关的JavaScript知识学习
  • 原文地址:https://blog.csdn.net/stand_forever/article/details/136630699