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)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+SpringBoot+vue+element实现校园闲置物品交易网站

基于JavaSpringBootvueelement实现校园闲置物品交易网站 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 ** 作者主页 央顺技术团队** 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录 基于…

【Unity】Tag、Layer、LayerMask

文章目录 层&#xff08;Layer&#xff09;什么是LayerLayer的应用场景Layer层的配置&#xff08;Tags & Layers&#xff09;Layer的数据结构LayerMaskLayer的选中和忽略Layer的管理&#xff08;架构思路&#xff09;层碰撞矩阵设置&#xff08;Layer Collision Matrix&…

人工智能入门学习笔记1:什么是人工智能

一、什么是人工智能 人工智能(Artificial Intelligence)&#xff0c;是一个以计算机科学&#xff08;Computer Science&#xff09;为基础&#xff0c;由计算机、心理学、哲学等多学科交叉融合的交叉学科、新兴学科&#xff0c;研究、开发用于模拟、延伸和扩展人的智能的理论、…

【探索程序员职业赛道:挑战与机遇】

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

【JetsonNano】onnxruntime-gpu 环境编译和安装,支持 Python 和 C++ 开发

1. 设备 2. 环境 sudo apt-get install protobuf-compiler libprotoc-devexport PATH/usr/local/cuda/bin:${PATH} export CUDA_PATH/usr/local/cuda export cuDNN_PATH/usr/lib/aarch64-linux-gnu export CMAKE_ARGS"-DONNX_CUSTOM_PROTOC_EXECUTABLE/usr/bin/protoc&qu…

使用 Docker Compose 快速搭建监控网站 uptime-kuma

有时候需要监控自己搭建的一些网站、服务是否正常运行&#xff0c; 这时候可以考虑使用一个监控网站&#xff0c; 定时的进行检测&#xff0c; 记录网站、服务的运行状态&#xff0c; 在这推荐使用 uptime-kuma。 博主博客 https://blog.uso6.comhttps://blog.csdn.net/dxk539…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Menu)

以垂直列表形式显示的菜单。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 Menu组件需和bindMenu或bindContextMenu方法配合使用&#xff0c;不支持作为普通组件单独使用。 子组件 包含MenuIt…

【Claude 3】一文谈谈Anthropic(Claude) 亚马逊云科技(Bedrock)的因缘际会

文章目录 前言1. Anthropic的诞生2. Anthropic的“代表作”——Claude 3的“三驾马车”3. 亚马逊云科技介绍4. 强大的全托管服务平台——Amazon Bedrock5. 亚马逊云科技(AWS)和Anthropic的联系6. Claude 3模型与Bedrock托管平台的关系7. Clude 3限时体验入口分享【⚠️截止3月1…

HTML5:七天学会基础动画网页11

CSS3动画 CSS3过渡的基本用法: CSS3过渡是元素从一种样式逐渐改变为另一种样式的效果。 过渡属性-transition 值与说明 transition-property 必需&#xff0c;指定CSS属性的name&#xff0c;transition效果即哪个属性发生过渡。 transition-duration 必需&#xff0c;t…

细说C++反向迭代器:原理与用法

文章目录 一、引言二、反向迭代器的原理与实现细节三、模拟实现C反向迭代器反向迭代器模板类的设计反向迭代器的使用示例与测试 一、引言 迭代器与反向迭代器的概念引入 迭代器&#xff08;Iterator&#xff09;是C标准模板库&#xff08;STL&#xff09;中的一个核心概念&am…

一篇文章认识【性能测试】

一、 性能测试术语解释 1. 响应时间 响应时间即从应用系统发出请求开始&#xff0c;到客户端接收到最后一个字节数据为止所消耗的时间。响应时间按软件的特点再可以细分&#xff0c;如对于一个 C/S 软件的响应时间可以细分为网络传输时间、应用服务器处理时间、数据库服务器…

layuiAdmin-通用型后台模板框架【广泛用于各类管理平台】

1. 主页 1.1 控制台 2. 组件 3. 页面 3.1 个人主页 3.2 通讯录 3.3 客户列表 3.4 商品列表 3.5 留言板 3.6 搜索结果 3.7 注册 3.8 登入 3.9 忘记密码 4. 应用 4.1 内容系统 4.1.1 文章列表 4.1.2 分类管理 4.1.3 评论管理 4.2 社区系统 4.2.1 帖子列表 4.2.2 回…

支小蜜AI校园防欺凌系统可以使用在宿舍吗?

随着人工智能技术的快速发展&#xff0c;AI校园防欺凌系统已成为维护校园安全的重要手段。然而&#xff0c;关于这一系统是否适用于宿舍环境&#xff0c;仍存在一些争议和讨论。本文将探讨AI校园防欺凌系统在宿舍中的适用性&#xff0c;分析其潜在的优势与挑战&#xff0c;并提…

解析Perl爬虫代码:使用WWW__Mechanize__PhantomJS库爬取stackoverflow.com的详细步骤

在这篇文章中&#xff0c;我们将探讨如何使用Perl语言和WWW::Mechanize::PhantomJS库来爬取网站数据。我们的目标是爬取stackoverflow.com的内容&#xff0c;同时使用爬虫代理来和多线程技术以提高爬取效率&#xff0c;并将数据存储到本地。 Perl爬虫代码解析 首先&#xff0…

微信小程序开发学习笔记《21》uni-app框架-楼层图片跳转

微信小程序开发学习笔记《21》uni-app框架-楼层图片跳转 博主正在学习微信小程序开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。建议仔细阅读uni-app对应官方文档 一、创建新的分包goods_list 二、将请求到的楼层数据url调整为本地的 可以看到上图是请求…

矢量图片转换软件Vector Magic mac中文版功能特色

Vector Magic mac中文版是一款非常流行的矢量图片转换软件&#xff0c;它的功能特色主要体现在以下几个方面&#xff1a; 首先&#xff0c;Vector Magic mac中文版拥有出色的矢量转换能力。它采用世界上最好的全彩色自动描摹器&#xff0c;能够将JPG、PNG、BMP和GIF等位图图像…

【C++ 】list 类

1. 标准库中的list类 list 类 的介绍&#xff1a; 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代 2. list与forward_list非常相似&#xff1a;最主要的不同在于forward_list是单链表 3. 与其他的序列式容器相比(a…

三星计划将其NAND闪存芯片价格上调最高20%

韩国媒体一份报告显示&#xff0c;三星电子的内存业务成功挺过了去年的市场低迷时期。最近&#xff0c;其减产策略终于见效&#xff0c;芯片价格随之上升。 据报导&#xff0c;今年第一季度&#xff0c;三星计划将其NAND闪存芯片价格上调最高20%&#xff0c;目标是恢复其内存芯…

渗透测试实战思路分析

免责声明&#xff1a;文章来源真实渗透测试&#xff0c;已获得授权&#xff0c;且关键信息已经打码处理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人…

王道机试C++第 5 章 数据结构二:队列queue和21年蓝桥杯省赛选择题Day32

目录 5.2 队列 1&#xff0e;STL-queue 课上演示&#xff1a; 基本代码展示&#xff1a; 2. 队列的应用 例:约瑟夫问题 No. 2 题目描述&#xff1a; 思路提示&#xff1a; 代码展示&#xff1a; 例&#xff1a;猫狗收容所 题目描述&#xff1a; 代码表示&#xff1…