Golang进阶学习

Golang进阶学习

视频地址:https://www.bilibili.com/video/BV1Pg41187AS?p=35

1、包

1.1、包的引入

使用包的原因:

  • 我们不可能把所有函数放在同一个源文件中,可以分门别类的放在不同的文件中

  • 解决同名问题,同一个文件中不可以定义相同名字的函数

首先写一个连接数据库的工具函数:


package dbUtils



import "fmt"



func GetConn() { //如果方法首字母大写,则可以被其它包访问

    fmt.Println("我执行了dbUtils包下的getConn函数")

}

再去写main函数:


package main // 1.package是进行包的声明,建议:包的声明与所在文件夹相同



import (

    "GoStudy/advanced/dbUtils" // 2.导入其它包时,包名是从$GOPATH/src/后开始计算的,使用/分隔

    "fmt"

)



func main() {

    fmt.Println("我执行了main包下的main函数")

    dbUtils.GetConn() // 3.函数调用时,前面要定位到所在的包

} 

1.2、包的细节

  1. 建议包的声明和所在文件夹同名

  2. main包是程序的入口包,一般main函数会放在这个包下

  3. 打包语法:package 包名

  4. 引入包的语法:import “包的文件夹的路径”,包名是从$GOPATH/src/后开始计算的,使用/分隔

  5. 如果有多个包要导入,推荐采用以下格式


import (

    "GoStudy/advanced/dbUtils"

    "fmt"

)

  1. 函数调用时前面要定位到所在的包

  2. 首字母大写,函数可以被其它包访问

  3. 一个目录下不能有重复的函数

  4. 包名和文件夹的名字可以不一样


// dbUtils.go

package aaa //这里声明的包是aaa



import "fmt"



func GetConn() { 

    fmt.Println("我执行了dbUtils包下的getConn函数")

}





//main.go

package main 



import (

    "GoStudy/advanced/dbUtils" //这里引用的依然是dbUtils

    "fmt"

)



func main() {

    fmt.Println("我执行了main包下的main函数")

    aaa.GetConn() //调用的变成了aaa.GetConn()

}

  1. 一个包下如果有多个go文件,则每个文件里**package 包名**必须一致

包是什么

  • 在程序层面看,包其实是所有使用相同 package 包名 的源文件组成的代码模块

  • 在源文件层面看,其实就是一个文件夹

1.3、init()函数

在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用。

需要注意的是:init()函数没有参数,也没有返回值。

init()函数在程序运行时,会自动被调用执行,不能在代码中主动调用它。

分析init()和main()这两个函数:

相同点:

  • 在定义时不能有任何的参数和返回值

  • 只能由go自动调用,不可以被引用

不同点:

  • init()用于初始化信息,main()用于作为程序的入口

  • init可以应用于任意包中,且可以重复定义多个

  • main函数只能应用于main包中,且只能定义一个

init函数的执行顺序:

  • 对于同一个package

    • 如果是在同一个go文件下,按照init函数的顺序执行

    • 如果是在同一个包中不同go文件下,会将文件名按字符串排序,然后依次执行文件中的init函数

  • 对于不同package

    • 如果不相互依赖,按照 main 包中 import 的顺序调用其包中的 init()函数

    • 如果存在依赖,则最后被依赖的最先被初始化

2、错误处理

2.1、引入

故意写一个除0操作:


package main



import "fmt"



func main() {

    test()

    fmt.Println("继续执行。。。")

}



func test() {

    num1 := 10

    num2 := 0

    result := num1 / num2 // 这里除0了

    fmt.Println(result)

}

/*

    panic: runtime error: integer divide by zero  // panic:恐慌

*/

从输出可以看出来程序中出现错误/恐慌以后,程序被中断,无法继续执行。

类似于Java中的try catch异常捕获,Go中使用defer recover机制处理错误。


package main



import "fmt"



func main() {

    test()

    fmt.Println("继续执行。。。")

}



func test() {

    // 利用defer + recover来捕获错误:defer后加上匿名函数的调用

    defer func() {

        // 调用recover内置函数,可以捕获错误

        err := recover()

        // 如果没有捕获错误,返回值为零值:nil

        if err != nil {

            fmt.Println("错误已经捕获")

            fmt.Println(err)

        }

    }()

    num1 := 10

    num2 := 0

    result := num1 / num2

    fmt.Println(result)

}



/*

错误已经捕获

    runtime error: integer divide by zero

    继续执行。。。

*/

优点:

  • 提高程序健壮性

2.2、自定义错误

需要调用errors包下的New函数:函数返回error类型


package main



import (

    "errors"

    "fmt"

)



func main() {

    err := test()

    if err != nil {

        fmt.Println("错误为:", err)

    }

    fmt.Println("继续执行。。。")

}



func test() (err error) {

    num1 := 10

    num2 := 0

    if num2 == 0 {

        // 抛出自定义错误

        return errors.New("除数不能为0~")

    } else {

        result := num1 / num2

        fmt.Println(result)

        return nil

    }



}



/*

    错误为: 除数不能为0~

    继续执行。。。

*/

当出现这样的情况:出现错误以后,后续代码没必要执行了,直接退出。可以借助:builtin包下的内置函数:panic


package main



import (

    "errors"

    "fmt"

)



func main() {

    err := test()

    if err != nil {

        fmt.Println("错误为:", err)

        panic(err)

    }

    fmt.Println("继续执行。。。")

}



func test() (err error) {

    num1 := 10

    num2 := 0

    if num2 == 0 {

        // 抛出自定义错误

        return errors.New("除数不能为0~")

    } else {

        result := num1 / num2

        fmt.Println(result)

        return nil

    }



}



/*

    错误为: 除数不能为0~

    panic: 除数不能为0~    

*/

3、数组

3.1、数组定义格式


 var 数组名 [数组大小]数据类型

 var scores [5]int

3.2、数组内存分析

数组名中存的地址其实就是数组中下标为0的元素对应的地址。


package main



import "fmt"



func main() {





    var arr [3]int16



    fmt.Println(len(arr)) // 数组长度

    fmt.Println(arr)



    fmt.Printf("arr的地址是: %p\n", &arr)

    fmt.Printf("arr[0]的地址是: %p\n", &arr[0])

    fmt.Printf("arr[0]的地址是: %p\n", &arr[1])

}



/*

    3

    [0 0 0]

    arr中的地址是: 0xc0000a6058

    arr[0]中的地址是: 0xc0000a6058

    arr[1]中的地址是: 0xc0000a605a // 因为是int16,所以二者相差2字节

*/

3.3、数组的遍历


package main



import "fmt"



func main() {



    var arr [3]int



    arr[0] = 98

    arr[1] = 22

    arr[2] = 53



    sum := 0



    // 常规遍历

    for i := 0; i < len(arr); i++ {

        sum += arr[i]

    }



    // for range遍历

    for key, value := range arr {

        fmt.Println(key)

        sum += value

    }

    fmt.Println(sum)



}

3.4、数组初始化


package main



import "fmt"



func main() {



    // 第一种:

    var arr1 [3]int = [3]int{16, 88, 53}

    fmt.Println(arr1)



    // 第二种

    var arr2 = [3]int{16, 88, 53}

    fmt.Println(arr2)



    // 第三种

    var arr3 = [...]int{16, 88, 53}

    fmt.Println(arr3)



    // 第四种

    var arr4 = [...]int{2: 53, 0: 16, 1: 88}

    fmt.Println(arr4)



}

3.5、数组的注意事项

1.数组长度是数组类型的一部分,长度不同的数组类型也不同


package main



import "fmt"



func main() {



    var arr1 = [3]int{3, 6, 9}



    var arr2 = [6]int{1, 2, 3, 3, 6, 9}



    fmt.Printf("数组类型为:%T\n", arr1) // 数组类型为:[3]int



    fmt.Printf("数组类型为:%T\n", arr2) // 数组类型为:[6]int



}

2.Go语言中数组默认是值传递,因此会拷贝,新函数里对数组的修改不会同步到原数组


package main



import "fmt"



func main() {



    //值传递

    arr := [4]int{1, 2, 3, 4}

    fmt.Println(arr)



    update(arr, 1, 100)

    fmt.Println(arr)



}



// 更改数组中某个元素的值

func update(arr [4]int, j int, target int) {

    arr[j] = target

    fmt.Println(arr)

}



/*

    [1 2 3 4] //未修改前数组的值

    [1 100 3 4] //update方法里修改后的值

    [1 2 3 4]  //修改后数组的值

*/

3.如果修改想要同步,需要变为引用传递


package main



import "fmt"



func main() {



    //值传递

    arr := [4]int{1, 2, 3, 4}

    fmt.Println(arr)



    update(&arr, 1, 100)

    fmt.Println(arr)



}



// 更改数组中某个元素的值

func update(arr *[4]int, j int, target int) {

    (*arr)[j] = target

    fmt.Println(arr)

}

3.6、二维数组

定义:


var arr [2][3]int

内存分析:


package main



import "fmt"



func main() {



    var arr [2][3]int

    fmt.Println(arr)



    fmt.Printf("arr的地址是%p\n", &arr)

    fmt.Printf("arr[0]的地址是%p\n", &arr[0])

    fmt.Printf("arr[0][0]的地址是%p", &arr[0][0])



}



/*

[[0 0 0] [0 0 0]]

    arr的地址是0xc0000c8030      

    arr[0]的地址是0xc0000c8030   

    arr[0][0]的地址是0xc0000c803

*/

学过C语言的这里应该很好理解,高维数组实际还是一维数组,只是一维数组的每个元素又是一个数组。

初始化:


package main



import "fmt"



func main() {



    var arr [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}

    fmt.Println(arr)



}

遍历:


package main



import (

    "fmt"

)



func main() {



    var arr = [2][3]int{{1, 2, 3}, {4, 5, 6}}



    // 普通for循环遍历

    for i := 0; i < len(arr); i++ {

        for j := 0; j < len(arr[0]); j++ {

            fmt.Print(arr[i][j], "\t")

        }

        fmt.Println()

    }



    // range遍历

    for _, v1 := range arr {

        for _, v2 := range v1 {

            fmt.Println(v2)

        }

    }



}

4、切片

4.1、引入

  • 切片是golang中一种特有的数据类型。

  • 数组有其特定用处,但是由于长度固定不可变等原因,其实代码中并不常见。切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。

  • 切片(slice)是对数组一个片段的连续引用,所以切片是一个引用类型,这个片段可以是整个数组,或者是其中连续的一部分。


package main



import "fmt"



func main() {



    var intarr [6]int = [6]int{1, 4, 7, 2, 5, 8}



    // 从1开始,到3结束,不包含3

    slice := intarr[1:3]



    // 输出切片

    fmt.Println(slice)



    // 切片元素个数

    fmt.Println("slice的元素个数:", len(slice))



    // 切片容量

    fmt.Println("slice的容量:", cap(slice))



}



/*

    [4 7]

    slice的元素个数: 2

    slice的容量: 5  

*/

4.2、内存分析

切片实际上是有三个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量


package main



import "fmt"



func main() {



    var intarr = [6]int{1, 4, 7, 2, 5, 8}



    // 从1开始,到3结束,不包含3

    slice := intarr[1:3]



    // 输出切片

    fmt.Println(slice)



    // 切片元素个数

    fmt.Println("slice的元素个数:", len(slice))



    // 切片容量

    fmt.Println("slice的容量:", cap(slice))



    fmt.Printf("数组中第2个元素的地址是:%p\n", &intarr[1])

    fmt.Printf("切片中第1个元素的地址是:%p\n", &slice[0])



    slice[1] = 16

    fmt.Println(intarr[2])



}



/*

    [4 7]

    slice的元素个数: 2                  

    slice的容量: 5                      

    数组中第2个元素的地址是:0xc00000e338

    切片中第1个元素的地址是:0xc00000e338

    16  

*/

4.3、切片的定义

  1. 定义一个切片,让它去引用一个已经创建好的数组

package main



import "fmt"



func main() {



    var intarr = [6]int{1, 4, 7, 2, 5, 8}



    // 从1开始,到3结束,不包含3

    slice := intarr[1:3]



    // 输出切片

    fmt.Println(slice)



}

  1. 通过make内置函数来创建切片。

package main



import "fmt"



func main() {



    // make函数的三个参数,切片类型,切片长度,切片容量

    slice := make([]int, 4, 20)

    fmt.Println(slice)

    fmt.Printf("切片的长度为:%d\n", len(slice))

    fmt.Printf("切片的容量为:%d\n", cap(slice))



}



// 实际上是make底层创建一个数组,对外不可见,要通过slice去间接访问各个元素。

  1. 在定义的时候直接指向具体数组

func main() {

    slice := []int{1,4,7}

}

4.4、切片的遍历

  • 普通for循环遍历

  • for-range遍历

4.5、切片的注意事项

  1. 切片定义后不可以直接使用,需要让其引用到一个数组,或者make一个空间供切片使用

  2. 切片使用不能越界

  3. 简写方式

  • var slice = arr[0:end] -------> var slice = arr[:end]

  • var slice = arr[start,len(arr)] -------> var slice = arr[start:]

  • var slice = arr[0,len(arr)] -------> var slice = arr[:]

  1. 切片还可以继续切片

package main



import "fmt"



func main() {



    var intarr [6]int = [6]int{1, 4, 7, 2, 5, 8}



    var slice []int = intarr[1:4]



    slice2 := slice[1:2]



    fmt.Println(slice2)



}

  1. 切片可以动态增长

package main



import "fmt"



func main() {



    var intarr [6]int = [6]int{1, 4, 7, 2, 5, 8}



    var slice []int = intarr[1:4]



    slice2 := append(slice, 88, 60)



    fmt.Println(slice2)



}



/*

1. 底层追加元素的时候首先创建一个新数组

2. 新数组中追加append后的元素

3. 将切片指向新数组

4. 由于我们往往是想在原slice上追加新元素,所以日常使用slice = append(slice, 88, 60)

5. 通过append可以往切片后面追加切片 slice = append(slice,slice2...)

*/

  1. 切片的拷贝

package main



import "fmt"



func main() {



    var a []int = []int{1, 4, 7, 2, 5, 8}



    var b []int = make([]int, 10)



    copy(b, a) // 将a中对应的数组元素拷贝到b中对应的数组中去



    fmt.Println(b)



}

5、Map

Map 是一种无序的键值对的集合。

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 “”。

Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

5.1、引入


package main



import "fmt"



func main() {



    var a map[int]string



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



    a[2009] = "张三"

    a[2010] = "李四"



    fmt.Println(a)



}



/*

1. map在使用之前一定要make

2. map是无序的

3. key不可以重复

*/

5.2、创建

  1. 定义---->初始化----->使用

var a map[int]string

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

a[2009] = "张三"

  1. 定义初始化---->使用

b := make(map[int]string)

b[2009] = "张三"

  1. 初始化使用

c := map[int]string{

    2009: "张三",

    2010: "李四",

}

5.3、操作

5.3.1、增加/更新


map["key"] = value // 如果key存在就是更新,否则是增加

5.3.2、删除/清空


delete(map,"key") // key不存在也不会报错

清空:

  • 逐一遍历key进行删除

  • map = make(…),make一个新的,让原来的成为垃圾,被gc回收

5.3.3、查询


// value,bool = map[key]   其中value是查找的结果,bool代表是否找到

package main



import "fmt"



func main() {



    var a map[int]string



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



    a[2009] = ""

    a[2010] = "李四"



    fmt.Println(a)



    d, flag := a[2022]

    fmt.Println(flag)

    fmt.Println(d)



}



/*

由于map的返回是有默认值的,比如key为int,map为string

    由于string可以为空

    因此在value的值可能为默认值时,一定要用bool判断是否获取key成功

*/

5.3.4、获取长度


fmt.Println(len(map))

5.3.5、遍历


for k, v := range map {

fmt.Println(k, v) // k是key,v是value

}

6、面向对象

Go语言关于面向对象的说明:

  • go支持面向对象编程(oop),但是和传统面向对象编程有区别,并不是纯粹的面向对象语言。

  • go中没有类,使用结构体来实现oop特性。

  • go面向对象编程非常简洁,去掉了传统oop语言的方法重载,构造函数和析构函数、隐藏的this指针等

  • go仍然有面向对象编程的继承、封装和多态的特性,只是实现方式不太一样。比如go中通过匿名字段而不是extends关键字实现继承

以下部分参考自菜鸟教程:Go 语言结构体 | 菜鸟教程 (runoob.com)

6.1、结构体

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:

  • ID:图书ID
  • Title :标题

  • Author : 作者

  • Date:出版日期

结构体是值传递!!!

6.1.1、定义结构体


type name struct {

   member type

   member type

   ...

   member type

}


package main



import (

    "fmt"

    "time"

)



type Books struct {

    id     int

    title  string

    author string

    date   string

}



func main() {



    // 创建一个新的结构体

    fmt.Println(Books{1, "平凡的世界", "路遥", time.Now().Format("2006-01-02 15:04:05")})



    // key: value创建结构体

    fmt.Println(Books{id: 2, title: "从你的全世界路过", author: "张嘉佳", date: time.Now().Format("2006-01-02 15:04:05")})



    // 指针创建结构体

    var book2 *Books = new(Books)

    (*book2).id = 3

    book2.title = "aaa" // go在底层依然是将该句转化为(*book2).title

    fmt.Println(*book2)



}

6.1.2、访问结构体成员


// 结构体.成员名

package main



import (

    "fmt"

    "time"

)



type Books struct {

    id     int

    title  string

    author string

    date   string

}



func main() {



    book := Books{1, "平凡的世界", "路遥", time.Now().Format("2006-01-02 15:04:05")}

    fmt.Println(book.title)



}

6.1.3、结构体作为函数参数


package main



import (

    "fmt"

    "time"

)



type Books struct {

    id     int

    title  string

    author string

    date   string

}



func printBook(book Books) {

    fmt.Printf("Book id : %d\n", book.id)

    fmt.Printf("Book title : %s\n", book.title)

    fmt.Printf("Book author : %s\n", book.author)

    fmt.Printf("Book date : %s\n", book.date)

}



func main() {



    book := Books{1, "平凡的世界", "路遥", time.Now().Format("2006-01-02 15:04:05")}

    printBook(book)



}

6.1.4、结构体指针

你定义指向结构体的指针类似于其他指针变量,格式如下:


var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:


struct_pointer = &book

使用结构体指针访问结构体成员,使用 “.” 操作符:


struct_pointer.title

实例:


package main



import (

    "fmt"

    "time"

)



type Books struct {

    id     int

    title  string

    author string

    date   string

}



func printBook(book *Books) {

    fmt.Printf("Book id : %d\n", book.id)

    fmt.Printf("Book title : %s\n", book.title)

    fmt.Printf("Book author : %s\n", book.author)

    fmt.Printf("Book date : %s\n", book.date)

}



func main() {



    book := Books{1, "平凡的世界", "路遥", time.Now().Format("2006-01-02 15:04:05")}

    bookPointer := &book

    printBook(bookPointer)



}

6.1.5、结构体转化

  1. 结构体是用户自定义的类型:和其它类型转化时需要有完全相同的字段(名字、个数和类型)

package main



import "fmt"



type Student struct {

    Age int

}



type Person struct {

    Age int

}



func main() {



    var s Student

    var p Person = Person{10}



    s = Student(p)

    fmt.Println(s)



}

  1. 给结构体起别名后赋值时需要强制转换

package main



import "fmt"



type Student struct {

    Age int

}



type Person Student // 给Student起一个叫Person的别名



func main() {



    var s Student

    var p Person = Person{10}



    s = p // 这里会报错,不能直接赋值使用

    fmt.Println(s)



}

6.2、方法

方法是作用在指定的数据类型上,和指定的数据类型绑定。

6.2.1、方法的声明


type A struct {

    Num int

}



func (a A) test() {

    fmt.Println(a.Num)

}



6.2.2、方法的调用


func main() {

    a := A{10}

    a.test()

}

6.2.3、注意事项

  1. 结构体是值传递,因此如果方法中需要改变原数据的值,参数使用指针类型。如果参数中是指针类型,我们实际使用时可以进行简化,不用(*pointer).方法名,可以直接变量.方法名。

  2. Go中方法是绑定在指定数据类型之上的,因此自定义类型都可以有方法,而不仅仅是struct,比如int、float32等。

6.3、接口

面向对象中的接口的一般定义为"接口定义对象的行为"。它表示让指定对象应该做什么,实现这种行为的方法(实现细节)是针对对象的。

在Go中,接口是一组方法签名,当类型为接口中的所有方法提供定义时,它被称为实现接口。接口与类型的关系是非侵入式的。

接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

视频链接:https://www.bilibili.com/video/BV1XC4y1W7Am?p=90

6.3.1、接口的声明


// USB 接口

type USB interface {

    start() // usb设备开始工作

    end()   // usb设备结束共工作

}

6.3.2、接口的实现


// Mouse 鼠标类型

type Mouse struct {

    name string

}



// FlashDisk u盘类型

type FlashDisk struct {

    name string

}



// 实现接口方法

func (m Mouse) start() {

    fmt.Println(m.name, "鼠标准备就绪,可以开始工作了")

}



func (m Mouse) end() {

    fmt.Println(m.name, "鼠标结束工作,可以安全退出了")

}



func (f FlashDisk) start() {

    fmt.Println(f.name, "u盘准备就绪,可以开始工作了")

}



func (f FlashDisk) end() {

    fmt.Println(f.name, "结束工作,可以弹出")

}

6.3.3、注意事项

  1. 当需要接口类型的对象时,可以使用任意实现类代替

func testInterface(usb USB) { // 参数是USB接口

    usb.start()

    usb.end()

}



func main() {



    m1 := Mouse{"罗技"}



    f1 := FlashDisk{"闪迪"}



    testInterface(m1) // 实际上传实现类即可



    testInterface(f1)



}

  1. 接口对象不能访问实现类中的属性

func main() {



    m1 := Mouse{"罗技"}



    var usb USB

    usb = m1

    fmt.Println(usb.name) // 这里是不合法的



}



// 其实这里很好理解,并不是所有的实现类都有某个属性

6.3.4、空接口

空接口中不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。


package main



import "fmt"



func main() {

    var a1 A = Cat{"橘猫"}

    var a2 A = Person{"白玉秀", 13}



    fmt.Println(a1)

    fmt.Println(a2)



}



type A interface {

}



type Cat struct {

    color string

}



type Person struct {

    name string

    age  int

}

既然这样,当我们不确定某个参数的类型时,就可以使用空接口来代替(泛型)


func print(i interface{}) {

    fmt.Println("a------>", i)

}



func main() {

    var a1 A = Cat{"橘猫"}

    var a2 A = Person{"白玉秀", 13}



    print(a1)

    print(a2)



}

6.3.5、接口嵌套


type A interface {

    test1()

}



type B interface {

    test2()

}



type C interface { // C接口里嵌套A接口和B接口

    A

    B

    test3()

}





// Cat结构体实现了C同时也实现了A和B

func (c Cat) test1() {

    fmt.Println("test1.....................")

}



func (c Cat) test2() {

    fmt.Println("test2.....................")

}

func (c Cat) test3() {

    fmt.Println("test3.....................")

}



func main() {



    var cat Cat = Cat{"橘猫"}

    cat.test1()

    cat.test2()

    cat.test3()



    var catA A = cat // 此时变量是什么类型的,就只能调用什么类型的方法

    catA.test1()



    var catB B = cat

    catB.test2()



}

6.3.6、接口断言

当一个函数的形参是interface{},那么在函数中,需要对形参进行断言,从而得到它的真实类型。

语法格式:


// 安全类型断言

<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )



// 非安全类型断言

<目标类型的值> := <表达式>.( 目标类型 )

示例代码:


/*

    定义一个shape的接口,里面有周长和面积两个方法

    但是由于不同图形的计算方式不同,因此当传到一个封装好的计算周长和面积的方法时,我们需要判断图形的类型

    一般有以下两种方式:



    1. 

    if ins, ok := s.(Triangle); ok {

        fmt.Println("是三角形,三边是:", ins.a, ins.b, ins.c)

    } else {



    }



    2. switch instance := 接口对象.(type){

        case 实际类型1:

        ....

        case 实际类型2:

        ....

        ....

    }

*/

package main



import (

    "fmt"

    "math"

)



func main() {

    var t1 = Triangle{3, 4, 5}

    fmt.Println(t1.peri())

    fmt.Println(t1.area())

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



    var c1 = Circle{2}

    fmt.Println(c1.peri())

    fmt.Println(c1.area())

    fmt.Println(c1.radius)



    getType(t1)

    var t2 *Triangle = &Triangle{3, 4, 8}

    getType(t2)



}



type Shape interface {

    peri() float64 // 周长

    area() float64 // 面积

}



type Triangle struct {

    a, b, c float64

}



func (t Triangle) peri() float64 {

    return t.a + t.b + t.c

}



func (t Triangle) area() float64 {

    p := t.peri() / 2

    s := math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c))

    return s

}



type Circle struct {

    radius float64

}



func (c Circle) peri() float64 {

    return c.radius * 4 * math.Pi

}



func (c Circle) area() float64 {

    return math.Pi * c.radius * c.radius

}



func getType(s Shape) {

    if ins, ok := s.(Triangle); ok {

        fmt.Println("是三角形,三边是:", ins.a, ins.b, ins.c)

    } else if ins, ok := s.(Circle); ok {

        fmt.Println("是圆形,半径是:", ins.radius)

    } else {

        fmt.Println("我也不懂了。。。。")

    }

}



func testShape(s Shape) {

    fmt.Printf("周长为:%.2f,面积为:%.2f", s.peri(), s.area())

}

6.4、type关键字

6.4.1、定义新类型

格式:


类型定义:type 类型名 Type

实例代码:


package main



import (

    "fmt"

    "strconv"

)



type myint int

type mystr string



func main() {



    var i1 myint

    var i2 = 100

    i1 = 200

    fmt.Println(i1, i2)



    var name mystr

    name = "像鱼"

    var s1 string

    s1 = "试试水"

    fmt.Println(name, s1)



    //i1 = i2 //Cannot use 'i2' (type int) as the type myint

    //name = s1 //Cannot use 's1' (type string) as the type mystr

    fmt.Printf("%T,%T,%T,%T\n", i1, i2, name, s1)



}

6.4.2、定义函数类型

格式:


type 自定义函数名 func(int, int) string // int 、string可选

实例代码:


package main



import (

    "fmt"

    "strconv"

)



// 2. 定义函数类型

type myfun func(int, int) string



func fun1() myfun {

    fun := func(a int, b int) string {

        s := strconv.Itoa(a) + strconv.Itoa(b)

        return s

    }

    return fun

}



func main() {



    res := fun1()

    fmt.Println(res(1, 2))



}

6.4.3、类型别名

格式:


类型别名:type 类型名 = Type

实例代码:


package main



import (

    "fmt"

    "strconv"

)



// 3. 给类型起别名

type myint2 = int



func main() {



    var i3 myint2

    i3 = 1000

    i4 := 200

    fmt.Printf("%T,%T\n", i3, i4)



}

注意:非本地类型不能定义方法,可参考这篇文章:https://blog.csdn.net/wohu1104/article/details/120463014

6.4.4、结构体成员嵌入

实例代码:


package main



import "fmt"



type Person struct {

    name string

}



func (p Person) show() {

    fmt.Println("p----------->", p.name)

}



type People = Person



type Student struct {

    // 嵌入两个结构体

    Person

    People

}



func main() {

    var s Student

    s.People.name = "王二狗"

    s.name = "王二浪" // ambiguous selector s.name 因为People和Person都有name属性,无法区分是给谁的

    fmt.Println(s)

}

用Java来解释的话,可以理解为如果一个对象中有两个其它的对象,且对象中存在相同的属性,在赋值时候需要指定赋给哪个对象。

你可能感兴趣的:(Go,golang,学习,开发语言,云原生)