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 变量作用域

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

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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/458748.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yocto是个什么东东

yocto不是个什么东东 在我们了解Yocto项目是什么之前&#xff0c;让我们先了解一下它不是什么。 Yocto项目不是用于现有硬件的软件开发工具包&#xff08;SDK&#xff09;&#xff0c;而是用于构建这样一个工具包。 Yocto项目不是可以部署到硬件上的系统二进制镜像&#xff…

客服销冠偷偷用的提效神器!无广很实用

近期发现我的同事每天上班必登录的一款软件——客服宝聊天助手&#xff0c;用过才发现&#xff1a;真客服办公的提效神器&#xff01;感兴趣的小伙伴请往下看~一、客服宝的简介&#xff1a;客服宝聊天助手&#xff0c;是一款跨平台快捷回复工具。自带多种功能&#xff0c;有效帮…

leetcode判断子序列

本题中&#xff0c;我们可以删除原始字符串的一些字符但是不能改变其他字符的位置&#xff0c;这种求子序列的题都可以用动态规划来解决。 首先我们要确定dp数组的定义&#xff0c;这里我们将dp数组定义为dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和以下标j-1为结尾的…

蓝桥杯[OJ 1621]挑选子串-CPP-双指针

目录 一、题目描述&#xff1a; 二、整体思路&#xff1a; 三、代码&#xff1a; 一、题目描述&#xff1a; 二、整体思路&#xff1a; 要找子串&#xff0c;则必须找头找尾&#xff0c;找头可以遍历连续字串&#xff0c;找尾则是要从头的基础上往后遍历&#xff0c;可以设头…

OSCP靶场--BlackGate

OSCP靶场–BlackGate 考点(1.redis rce 2. CVE-2021-4034提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC -p- 192.168.163.176 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-14 03:32 EDT Nmap scan report for 192.168.163.…

MongoDB实战面试指南:常见问题一网打尽

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! MongoDB是一款流行的非关系型数据库&#xff0c;以其高效、可扩展的特性受到开发者的青睐。了解MongoDB的架构、存储引擎和数据结…

python 基础知识点(蓝桥杯python科目个人复习计划63)

今日复习内容&#xff1a;做题 例题1&#xff1a;蓝桥骑士 问题描述&#xff1a; 小蓝是蓝桥王国的骑士&#xff0c;他喜欢不断突破自我。 这天蓝桥国王给他安排了N个对手&#xff0c;他们的战力值分别为a1,a2,...,an&#xff0c;且按顺序阻挡在小蓝的前方。对于这些对手小…

剪辑设计软件如何跨系统使用?PC也能用Mac Final Cut

我猜你工作中&#xff0c;常常遇到这样那样的麻烦&#xff1a; 临时接手一个项目&#xff0c;之前的同事用Final Cut&#xff0c;而你是Windows系统&#xff1b; 临时有紧急需求要调整&#xff0c;而本地电脑却没有工作软件/性能不给力&#xff1b; 那这样的情况&#xff0c…

SSL证书如何实现数据加密传输?

在当前互联网的洪流中&#xff0c;用户对网站隐私与安全性的重视程度日益提升。为了确保用户信息和交易数据的安全传输&#xff0c;SSL证书在网络世界中扮演了关键角色。本文将深入解析SSL证书的核心功能及其重要作用。 1、SSL证书采用加密技术保障数据传输安全 通过应用公钥加…

Mysql 无法启动,mysql-bin.日志丢失删除处理

在linux操作系统中&#xff0c;当mysql无法启动时候&#xff0c;先看日志 2024-03-15T05:20:16.352075Z 0 [Warning] [MY-000081] [Server] option max_allowed_packet: unsigned value 107374182400 adjusted to 1073741824. 2024-03-15T05:20:16.352156Z 0 [Warning] [MY-010…

(008)Unity StateMachineBehaviour的坑

文章目录 StateMachineBehaviour同名函数的调用问题StateMachineBehaviour 的 OnState*、OnStateMachine* 的区别 StateMachineBehaviour同名函数的调用问题 1.如果脚本中&#xff0c;两个同名的函数都存在&#xff0c;那么两个函数都会被调用&#xff1b;如果只有其中一个同名…

IO流——字节流

常见字符集 标准ASCII码字符集 ASCII(American Standard Code for Information Interchange)&#xff1a;美国信息交换标准代码&#xff0c;包括英文、符号等标准ASCII码使用1个字节存储一个字符&#xff0c;首位是0&#xff0c;总共可表示128个字符 而对于国内而言&a…

橡胶工厂5G智能制造数字孪生可视化平台,推进橡胶工业数字化转型

橡胶5G智能制造工厂数字孪生可视化平台&#xff0c;推进橡胶工业数字化转型。随着信息技术的迅猛发展和智能制造的不断推进&#xff0c;数字化转型已成为制造业转型升级的重要方向。橡胶工业作为传统制造业的重要领域&#xff0c;正面临着产业升级和转型的迫切需求。橡胶5G智能…

计算机网络笔记(湖科大教书匠版本)

第一章、 ①三种交换方式 电路交换、分组交换、报文交换&#xff08;被分组交换所取代&#xff09; 1.电路交换&#xff1a;会一直占用通道&#xff0c;不适合计算机之间的数据通信 2.分组交换&#xff1a;通常我们把表示该数据的整块数据称为一个报文。 先把较长的报文划…

MySQL—redo log、undo log以及MVCC

MySQL—redo log、undo log以及MVCC 首先回忆一下MySQL事务的四大特性&#xff1a;ACID&#xff0c;即原子性、一致性、隔离性和持久性。其中原子性、一致性、持久性实际上是由InnoDB中的两份日志保证的&#xff0c;一份是redo log日志&#xff0c;一份是undo log日志&#xff…

Linux——基础指令

一、Linux目录结构 1、树形结构 Linux只有一个根目录 / &#xff0c;所有文件都在它下面 2、Linux路径的描述方式 在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用&#xff1a; / 来表示 eg&#xff1a; /usr/local/hello.txt 注意&#xff1a; 开头/表示根…

解决:黑马webpack视频中出现的问题总结

问题 1 ERROR in main Module not found: Error: Can‘t resolve ‘./src‘ 解决 Webpack 中 ERROR in main Module not found: Error: Can‘t resolve ‘./src‘ 问题 黑马AJAX-Node.js-Webpack教学视频&#xff08;BV1MN411y7pw 其中P98&#xff09;中webpack部分&#xff0c…

phpcms上传导致getshell详解及案例

一、环境 这里我根据大佬的文章将环境复原 phpcms上传导致getshell详解及案例 | 离别歌 回忆phpcms头像上传漏洞以及后续影响 | 离别歌 二、代码&#xff1a; php&#xff1a; <?php header("Content-Type:text/html; charsetutf-8"); require_once(pclzip…

Unload-labs

function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") {alert("请选择要上传的文件!");return false;}//定义允许上传的文件类型var allow_ext ".jpg|.png|.gif";//提取上传文件的类…

Pytorch学习 day10(L1Loss、MSELoss、交叉熵Loss、反向传播)

Loss loss的作用如下&#xff1a; 计算实际输出和真实值之间的差距为我们更新模型提供一定的依据&#xff08;反向传播&#xff09; L1Loss 绝对值损失函数&#xff1a;在每一个batch_size内&#xff0c;求每个输入x和标签y的差的绝对值&#xff0c;最后返回他们平均值 M…