• Go知识点大纲


    注:还有部分未整理,持续整理中...

    1. 基本介绍

    暂略
    

    2. 安装及配置

    暂略
    

    3. 变量

    func main() {
        // 变量定义的三种方法
        //1. var 变量名 类型	//只定义,不赋值
        // var 变量名 类型 = 值	// 定义并赋初值
        var a int = 3
        fmt.Println(a)
    
        // 2. var 变量名 = 值	//类型推导
        var b = 3
        fmt.Println(b)
    
        // 3. 变量名 := 值	//推荐用法
        c := 3
        fmt.Println(c)
    
        // 交叉赋值
        d := 1
        e := 2
        fmt.Println("交换前指针", &d, &e)
        d, e = e, d
        fmt.Println(d, e)
        fmt.Println(&d, &e)
    }
    

    4. 常量

    func main() {
        // 常量的定义 使用 const 关键字
        const a int = 3
        const b = "qwerty"
        fmt.Println(a, b)
    }
    

    5. 数据类型

    5.1 numeric(数字)

    // int/uint int8/uint8 int16/uint16 int32/uint32 int64/uint64
    // float32/float64
    // byte/rune
    

    5.2 string(字符串)

    // string
    func main() {
        var s1 string = "qwerty"
        s2 := "asdfgh"
        fmt.Println(s1)
        fmt.Println(s2)
    }
    

    5.3 array(数组)

    func main() {
        //var a = [4]int{1, 2, 3, 4}
        // 一维数组
        a := [4]int{1, 2, 3, 4}
        b := [3]string{"b1", "b2"}
        c := [4]int{2: 5}
        d := [...]int{1, 2, 3, 4}
    
        // 数组赋值
        a[2] = 10
    
        fmt.Println(a, b, c)
    
    
        // 数组遍历
        for k, v := range a {
            fmt.Println(k, v)
        }
    
        // 多维数组
        d := [2][3]int{{1, 2, 3}, {4, 5, 6}}
        fmt.Println(d)
    }
    

    5.4 slice(切片)

    func main() {
        // 切片的定义方式
        // 1.申明切片
        var s1[] int
        fmt.Println(s1)
    
        // 2, 使用 :=
        s2 := []int{}
        fmt.Println(s2)
    
        // 3. 使用 make([]type, len, cap)
        var s3[] int = make([]int, 5)	//容量为5
        var s4[] int = make([]int, 3, 5)	//大小为3,长度为5
        fmt.Println(s3, len(s3), cap(s3))
        fmt.Println(s4, len(s4), cap(s4))
    
        // 初始化赋值
        var s5 = []int{1, 2, 3}
        s6 := [] int{1, 2, 3}
        fmt.Println(s5,s6)
    
        // 从数组切片
        arr := [5]int{1, 2, 3, 4, 5}
        // var s7 = arr[1:3]
        s7 := arr[1: 3]
        fmt.Println(s7)
    
        arr2 := [5]int{1, 2, 3, 4, 5}
        s8 := arr2[:4]
        fmt.Println(arr2, s8, len(s8), cap(s8))
        s8 = append(s8, 5, 2)
        fmt.Println(s8, len(s8), cap(s8))
        // 对比底层数组的起始指针
        fmt.Println(&arr2[0], &s8[0])
    
        // 切片遍历与数组一样
    }
    

    5.5 Map(字典)

    5.5.1 什么是 map

    map 是 go 中 key-value 形式的一种数据结构,通过 key 可以获取到 value,类似于 Python 中的字典
    

    5.5.2 map 的定义

    // key 的类型:必须是支持相等运算符(==,!=)的数据类型,如数字,字符串,指针,数组,结构体,以及对应的接口类型
    
    func main() {
        /* 
        map 的定义
        1. 使用
        var 变量名 map[key 类型] value 类型 {
          key1: value1,
          key2: value2,
          ...
        }
        
        2. 使用 make 创建
        变量名 := make(map[key 类型] value 类型)
        赋值操作,添加,删除等...
        */
        
        m1 := map[string] int{
           "a": 1,
           "b": 2,
        }
        fmt.Println(m1)
        
        m2 := make(map[string] int)
        m2["c"] = 3
        m2["d"] = 4
        fmt.Println(m2)
    }
    

    5.5.3 map 操作

    func main() {
        m = map[string] int{
          "a": 1,
          "b": 2
        }
        m["c"] = 3  //新增
        m["a"] = 10  //修改
        
        // 访问不存在的值,默认返回零值,但推荐使用 ok-idiom,因为返回零值无法判断元素是否存在
        if v, ok := m["a"]; ok {  // 判断 key 是否存在,返回 value
          fmt.Println(v)
        }
      
        delete(m, "a")  // 删除元素,不存在不会报错
    }
    

    5.6 pointer(指针)

    5.6.1 什么是指针

    • 指针就是一个变量,用来存储另一个变量的内存地址
    func main() {
        // 指针地址,指针类型,指针取值
        // &取地址,*根据地址取值(解引用)
        a := 10
        b := &a
        fmt.Println(*b)
    
        //指针类型 *int *unit *float *string *array *struct 等
        
        // 指针的定义
        // var 变量名 指针类型
        var p1 *int  //定义空指针
        fmt.Println(p1) // nil
        p1 = &1
        fmt.Println(p1)
    }
    

    5.6.2 数组指针和指针数组

    • 数组指针:是一个指针,用来存储数组的内存地址
    func main() {
        arr := [4] int{1, 2, 3, 4}
        fmt.Println(arr)
      
        // 定义一个数组指针
        var p1 *[4] int
        fmt.Println(p1)  // nil 空指针
        fmt.Ptintf("%T
    ", p1)  // *[4] int
        
        p1 = &arr1
        fmt.Println(p1)
        fmt.Printf("%p
    ", p1)
        fmt.Printf("%p
    ", &p1)
        
        // 通过数组指针操作数组
        (*p1)[0] = 100
        // 可以简写为
        // p1[0] = 200
        fmt.Println(arr)  // [100 2 3 4]
    }
    
    • 指针数组:是一个数组,元素为指针
    func main() {
        a := 1
        b := 2
        c := 3
        d := 4
        arr1 := [4] int{a, b, c, d}
        arr2 := [4] *int{&a, &b, &c, &d}
        fmt.Println(arr1)
        fmt.Println(arr2)
        
        // 操作数组与指针数组的区别
        //arr1[0] = 100
        //fmt.Println(a)
        //fmt.Println(arr1, &arr1[0])  // 值类型,将a,b,c,d的值拷贝过来放到数组中,修改的是数组空间内的数据,与a,b,c,d没有关系
        //fmt.Println(arr2)
        
        *arr2[0] = 1000
        fmt.Println(a)
        fmt.Println(arr1, &arr1[0])
        fmt.Println(arr2, *arr2[0])       // 通过指针数组中的内存地址,修改了a,b,c,d的值,类似于Python列表
        
        b = 2000
        fmt.Println(b)
        fmt.Println(arr1, &arr1[1])
        fmt.Println(arr2, *arr2[1])
    }
    

    总结:

    数组是值类型,将值拷贝了一份放到数内存中,二者相互独立,互不影响,修改数组后数组内存中的值改变,不会影响拷贝的源数据,源数据改变,也不会影响数组

    Go中切片是对原数组的引用,二者互相关联,修改切片元素的值后,与之关联的底层数组的也会受到影响,同理,底层数组的改变也会影响切片的值

    Python中的列表是引用类型,基于指针数组,修改可变元素后(可变类型与不可变类型),引用的源数据也会受到影响---

    • Go中切片与Python中列表的区别
      • go的切片,其成员是相同类型的,python的列表和元组则不限制类型。
      • 两种语言都有[a: b]这种切片操作,意义也类似,但是go的a、b两个参数不能是负数,python可以是负数,此时就相当于从末尾往前数。
      • 两种语言都有[a: b: c]这种切片操作,意义却是完全不同的。go的c,表示的是容量;而python的c表示的是步长
      • python的切片产生的是新的对象,对新对象的成员的操作不影响旧对象;go的切片产生的是旧对象一部分的引用,对其成员的操作会影响旧对象。
      • 底层实现的不同
        • go的切片,底层是一个三元组,一个指针,一个长度,一个容量。指针指向一块连续的内存,长度是已有成员数,容量是最大成员数。切片时,一般并不会申请新的内存,而是对原指针进行移动,然后和新的长度、容量组成一个切片类型值返回。也就是说,go的切片操作通常会和生成该切片的切片共用内存。不仅是切片,字符串、数组的切片也是一样的,通常会共用内存。当然也有异常情况,那就是切片时,提供的容量过大,此时会申请新内存并拷贝;或者对切片append超出容量,也会如此。这时,新的切片,才不会和老切片共享内存。(如果你切片/创建时提供的容量小于长度,会panic)
        • python的列表,其实是个指针数组。当然在下层也会提供一些空位之类的,但基本就是个数组。对它们切片,会创建新的数组,注意,是创建新的数组!python的列表可没有容量的概念。这其实也体现了脚本语言和编译语言的不同。虽然两个语言都有类似的切片操作;但是python主要目标是方便;go主要目标却是快速(并弥补丢弃指针运算的缺陷)。

    5.6.3 函数指针与指针函数

    // 函数指针
    // Go中函数默认就是一个指针类型,不需要*
    func main() {
        var a func()
        a = func1
        a()
    }
    func func1() {
        fmt.Println("这是func1()")
    }
    
    // 指针函数
    // 返回值为指针的函数
    func main() {
        // arr1是数组,值传递,将func1中返回的arr的值拷贝到arr1中,当func1调用结束,arr被销毁
        arr1 := func1()
        fmt.Printf("arr1的类型:%T,内存地址:%p,值:%v
    ",arr1, &arr1, arr1)
        
        // arr2是指针类型,值传递,将func2中返回的arr的内存地址保存到arr2中,arr不会随着func2的结束而销毁(和闭包一样改变了变量的生命周期?)
        arr2 := func2()
        fmt.Printf("arr2的类型:%T,内存地址:%p,值:%v
    ",arr2, &arr2, arr2)
    }
    
    // 这是普通函数
    func func1() {
        arr := [4] int{1, 2, 3, 4}
        return arr
    }
    
    // 这是指针函数
    func func2() *[4] int {
        arr := [4] int{1, 2, 3, 4}
        return &arr
    }        
    

    5.6.4 指针作为参数

    func main() {
        /*
        指针作为参数
        参数:值传递和引用传递
        
        总结:值传递,拷贝一份,不会影响原数据,但消耗内存
             引用传递通过指针操作数据,会改变原数据,但节省内存(拷贝的数据可能很大)
        */
        n := 10
        // 值传递,func1改变不会影响n,a随着func1结束而被销毁
        func1(n)
        fmt.Println(n)
    
        // 引用传递,将n的内存地址拷贝到a中,通过*a更改了n,a也会随着func2的结束而被销毁,但n已经改变
        func2(&n)
        fmt.Println(n)
    }
    
    func func1(a int) {
        a = 100
        fmt.Println(a)
    }
    
    func func1(a *int) {
        *a = 100
        fmt.Println(*a)
    

    5.7 function(函数)

    见 7. 函数

    5.8 struct(结构体)

    见 9. 结构体

    5.9 interface(接口)

    见 11. 接口

    5.10 channel(通道)

    见 12.通道

    5.11 boolean(布尔值)

    // true
    // false
    

    6. 流程控制

    6.1 if 判断

    func main() {
        if 条件 {
            代码块1
        } else if 条件 {
            代码块2
        } else {
            代码块3
        }
    }
    

    6.2 for循环

    func main() {
        // 完整结构
        for 初始化;终止条件;自增{
            代码块
        }
        // 在外部定义初始化值
        i := 0
        for ; i<10; i++ {
            代码块
        }
    
        // 在内部实现自增/自减
        for i:=0; i<5; {
            i++
        }
    
        // 利用for循环实现其他语言while的效果
        for true {
            代码块
        }
    }
    

    6.3 switch语句

    func main() {
        // switch
        switch 变量名: {	// 对此变量做条件判断
            case 值1, 值2, ...:
                代码块
            case 值3, 值4, ...:
                代码块
            default:	// 不管以上打码是否执行,此部分肯定执行
                代码块
        }	
    }
    

    6.4 break与continue

    // break终止当前循环,执行循环下面的代码
    // continue终止本次循环,继续执行下次循环 
    

    7. 函数

    7.1 函数的定义

    /*
    func 函数名(参数1 类型,参数2 类型,...)(返回值1 类型, 返回值2 类型,...){
        //函数体
    }
    */
    func f1(a int, b int)(c int){
        return a+b
    }
    

    7.2 函数参数

    // 函数参数
    func f2(a, b int)() {	//相邻的相同类型的参数可以合并
        fmt.Println(a, b)
    }
    
    func f3(a int, b ...int) {	// b ...int 表示接受int类型可变长参数,必须放在尾部
    	fmt.Println(a)
    	fmt.Println(b)
    }
    

    7.3 函数返回

    // 函数返回值
    func f1(a int, b int)(c int){	// 第二个括号中的返回参数必须与return的数量一致
        return a+b
    }
    
    // 返回多个值,要有多个变量去接收,不想要的值可以用“_”忽略,不能将所有返回值都忽略
    _, a := f4(3, 4)
    
    func f4(a int, b int)(c int, d int){
        reyurn a, b
    }
    

    7.4 匿名函数

    // 匿名函数
    // 没有定义名字符号的函数
    // 可以复制给变量,作为参数,作为返回值使用
    func main() {
        func(s string) {
            fmt.Println(s)
        }("abc")
    }
    

    7.5 闭包

    外层函数中的局部变量被内层函数引用,外层函数的返回值是内层函数,按理来说,外层函数中的变量应该随着外层函数的结束而销毁,但由于它被内层函数引用,其生命周期也发生了改变

    func main() {
    	res := closure()
    	fmt.Printlm(res1())		// 1
    	fmt.Printlm(res1())		// 2
    }
    
    // 闭包函数
    func closure() func int {
        i := 0
    	return func() int {
            i++
            return i
        }
    }
    

    7.6 延迟调用

    // 延迟调用
    func main() {
        f,err := os.Open("./main.go")
        if err != nil {
            log.Fatalln(err)
        }
        
        drfer f.close()	// 仅注册,知道main退出前才执行
    }
    
    // 多个延迟调用按栈(FILO)次序执行,最前面的最后执行
    

    8. 错误处理

    Golang没有类似try..catch 这种异常处理机制,而是使用 panic 和 recover 处理异常. 其实相当于python的raise。

    8.1 error

    // error 是一种数据类型
    // 标准库将error定义为接口类型,以便实现自定义错误类型
    type error interface {
        Error() string
    }
    
    // 创建error对象
    var errDivByZore = errors.New("divis by zore")
    
    func div(x, y int) (int error) {
        if y == 0 {
            return 0, errDivByZore
        }
        return x/y, nil
    }
    
    func main() {
        z, err := div(5, 0)
        if err != nil {
            log.Fatalln(err)
        }
        fmt,Println(z)
    }
    

    自定义错误类型

    type DivError struct {		// 自定义错误类型
        x, y int
    }
    
    func (DivError) Error() string {	// 实现error接口方法
        return "division by zore"
    }
    
    func div(x, y int) (int, error) {
        if y == 0 {
            return 0, DivError{x, y}	// 返回自定义错误类型
        }
        return x/y, nil
    }
    
    func main() {
        z, err := div(4, 0)
        
        if err != nil {
            switch e := err.(type) {	// 根据类型匹配
            case DivError:
                fmt.Println(e, e.x, e.y)
            default:
                fmt.Println(e)
            }
            
            log.Fatalln(err)
        }
        
        fmt.Println(z)
    }
    

    8.2 panic/recover

    更接近try/catch结构化异常,它们是内置函数而非语句

    panic会立即中断当前函数流程,执行延迟调用(类似于Python中raise?)

    在延迟调用函数中,recover可捕获并返回panic提交的错误对象

    func main() {
        defer func() {
            if err := recover(); err != nil {	// 捕获错误
                log.Fatalln(err)
            }
        }()
        panic("i am dead")		// 引发错误
        fmt.Println("exit.")	// 永远不会执行
    }
    

    总结:

    ​ 使用panic抛出异常,抛出异常后将立即停止当前函数的执行并运行所有被defer的函数,然后将panic抛向上一层,直至程序crash。但是也可以使用被defer的recover函数来捕获异常阻止程序的崩溃,recover只有被defer后才是有意义的。

    必须注意:

    1. defer 需要放在 panic 之前定义,另外recover只有在 defer 调用的函数中才有效。
    2. recover处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点.
    3. 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用

    panic:主动抛出异常)

    recover :收集异常

    recover 用来对panic的异常进行捕获. panic 用于向上传递异常,执行顺序是在 defer 之后。

    defer有点类似try...catch...finally中的finally

    9. 结构体

    Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

    9.1 自定义类型

    // 通过type关键字自定义类型
    type MyInt int	//自定义了一个MyInt类型,它具有int类型的特性
    

    9.2 类型别名

    type TypeAlias = type	//TypeAlias就是type的别名
    
    // 数据类型中,byte,rune也是别名,他们的定义如下
    type byte = uint8
    type rune = int32
    

    9.3 自定义类型与类型别名的区别

    //类型定义
    type NewInt int
    
    //类型别名
    type MyInt = int
    
    func main() {
        var a NewInt
        var b MyInt
    
        fmt.Printf("type of a:%T
    ", a) 	//type of a:main.NewInt
        fmt.Printf("type of b:%T
    ", b) 	//type of b:int
    }
    
    // 总结:类型别名只是在代码中存在,编译后不会存在,而自定义类型会一直存在
    

    9.4 结构体的定义

    /*
    使用type和struct关键字来定义结构体,具体代码格式如下:
    type 类型名 struct {
        字段名 字段类型
        字段名 字段类型
        ...
    }
    */
    
    // 定义一个人类结构体
    type person struct {
        name string
        age int
        height int
        // age, height int	// 同类型的字段可以写在一起
    }
    

    9.5 结构体实例化

    // var 结构体实例 结构体类型
    type person struct {
        name string
        age, height int
    }
    
    func main() {
        
        // 方法1
        var p1 person
        p1.name = 'qqq'
        p1.age = 28
        p1.height = 180
        fmt.Ptintf("p1=%V
    ", p1)
        fmt.Ptintf("p1=%#V
    ", p1)
        
        // 方法2
        p2 := person{"sss", 18, 149}
        fmt.Ptintf("p1=%V
    ", p1)
        
        // 方法3
        p3:= Person{}
        p3.name = 'yyy'
        p3.age = 18
        p3.height = 149
        fmt.Ptintf("p1=%V
    ", p3)
        fmt.Ptintf("p1=%#V
    ", p3)
        
        // 方法4
        p4 := person{name="sss", age=18, height=149}
        fmt.Ptintf("p1=%V
    ", p4)
    }
    

    9.6 结构体指针

    结构体是值传递,将一个实例赋值给另一个实例,发生的是深拷贝

    func main() {  
        p1 := Person{
            name: "qqq",
            age: 18,
            height: 180,
        }
        
        p2 := p1
        fmt.Printf("%p
    ", &p1)	//二者内存地址不一样
        fmt.Printf("%p
    ", &p2)
        
        p2.name = "www"
        fmt.Println(p1)	// {qqq 18 180}
        fmt.Println(p2)	// {www 18 180}   
    }
    
    type Person struct {
        name string,
        age int,
        height int,
    }
    

    如果要创建引用类型的实例,那就要使用结构体指针实例化

    func main() {    
        p1 := Person{
            name: "qqq",
            age: 18,
            height: 180,
        }
        
        var p2 *Person
        p2 = p1    
        p2.name = "www"
        fmt.Println(p1)	// {www 18 180}
        fmt.Println(p2)	// &{www 18 180}   
    }
    
    type Person struct {
        name string,
        age int,
        height int,
    }
    

    9.7 匿名结构体与结构体的匿名字段

    • 匿名结构体
    // 没有名字的结构体
    func main() {
        p1 := struct{
            name string
            age int
            height int
        }{
            name: "qqq",
            age: 18,
            height: 185,
        }
        
        fmt.Println(p1)	// {qqq 18 158}
    }
    
    • 结构体的匿名字段
    func main() {
        p1 := Person{"qqq", 18}
        fmt.Println(p1.string)
        fmt.Println(p1.int)  
    }
    
    type Person struct {
        //name string
        string		// 匿名字段,可通过实例.string获取
        //age int
        int		//匿名字段,可通过实例.int获取
    }
    
    /*
    总结:
    	结构体的匿名字段就是不写字段名,只写字段类型
    	可通过 实例.字段类型 来获取匿名字段的值
    	一个结构体中一种类型的匿名字段最多只能有一个
    */
    

    9.8 结构体的嵌套

    9.8.1 结构体的嵌套

    结构体内的字段类型为结构体,可通过 实例.嵌套结构体字段.字段 的方式访问和修改

    结构体的嵌套类似于面向对象中的聚合

    func main() {
    	addr := Address{
    		city: "上海市",
    		zone: "浦东新区",
    	}
    
    	p := Person{
    		name: "qqq",
    		age: 18,
    		address: addr,
    	}
    
    	fmt.Println(p)		// {qqq 18 {上海市 浦东新区}}
    	fmt.Println(p.address.city)		// 上海市
    
    	// 值传递,与嵌套的结构体互不影响
    	addr.zone = "杨浦区"
    	p.address.city = "北京市"
    	fmt.Println(p)		// {qqq 18 {北京市 浦东新区}}
    	fmt.Println(addr)	// {上海市 杨浦区}
    }
    
    type Person struct{
        name string
        age int
        address Address
    }
    
    type Address struct{
        city string
        zone string
    }
    

    9.8.2 Go语言中的OOP

    Golang不是面向对象的语言,所以没有面向对象的三大特征,但我们可以用代码模拟出面向对象的部分特征

    // Go语言实现面向对象的继承
    func main() {
    	// 实例化父类
    	p1 := Person{
    		name: "张三",
    		age: 25,
    	}
    	fmt.Println(p1)
    	fmt.Println(p1.name, p1.age)
    
    	// 实例化子类
    	//e1 :=  Employee{Person{name:"李四", age:35}, "bolome"}
    	e1 :=  Employee{
    		Person:Person{
    			name:"李四", 
    			age:35,
    		}, 
    		company:"bolome",
    	}
    	fmt.Println(e1)
    	//fmt.Println(e1.Employee.name, e1.Employee.age, e1.company)
    	//提升字段,如果继承字段为匿名字段,可以省略嵌套的字段,直接点
    	fmt.Println(e1.name, e1.age, e1.company)
    }
    
    
    // 定义父类
    type Person struct{
        name string
        age int
    }
    
    // 定义子类
    type Employee struct{
        Person		// 使用匿名字段,字段类型为结构体,模拟继承关系
        company string		// 子类新增属性
    }
    

    模拟多继承

    // 模拟面向对象多继承
    func main() {
        c := C{
            "ccc",
            20,
            A{"aaa", 18},
            B{"bbb", 28}
        }
        fmt.Println(c.name)		// 这种写法要么C中有name属性,要么父类中只有一个有name属性,否则抛错:ambiguous selector c.name
        fmt.Println(c.A.name)	// 如果要使用父类中重复的属性,必须声明是哪个父类
        fmt.Println(c.B.name)
        fmt.Println(c.age1)
        fmt.Println(c.age2)
        fmt.Println(c.age)
    }
    
    // 定义父类A
    type A struct {
        name string
        age1 string
    }
    
    // 定义父类B
    type B struct {
        name string
        age2 int
    }
    
    // 定义子类C
    type C struct {
        name string
        age int
        A
        B
    }
    
    • 总结:
      • 结构体嵌套多个结构体可以实现面向对象多继承的效果
      • 但没有继承顺序的说法,如果父类中有重复的字段,必须指明是哪个父类的哪个属性(c.A.name)

    10. 方法

    方法是与对象实例绑定的特殊函数。这个对象可以是命名类型或者结构体的一个值或一个指针。

    10.1 方法的定义

    func main() {
        p1 := Person{"qqq", 18}
        p1.talk()
        p1.run()
    }
    
    type Person struct{
        name string
        age int
    }
    
    /*
    定义方法
    func (t type) methodName() {
    	代码块
    }
    */
    func (p Person) talk() {
        fmt.Println("这是个talk方法")
    }
    
    func (p *Person) run() {
        //fmt.Println((*p).name)
    	fmt.Println(p.name)
        fmt.Println("这是个run方法")
    }
    

    10.2 方法的继承

    // Go语言实现面向对象的继承
    func main() {
    	// 实例化父类
    	p1 := Person{
    		name: "张三",
    		age: 25,
    	}
    	fmt.Println(p1)
    	fmt.Println(p1.name, p1.age)
    
    	// 实例化子类
    	//e1 :=  Employee{Person{name:"李四", age:35}, "bolome"}
    	e1 :=  Employee{
    		Person:Person{
    			name:"李四", 
    			age:35,
    		}, 
    		company:"bolome",
    	}
    	fmt.Println(e1)
    	//fmt.Println(e1.Employee.name, e1.Employee.age, e1.company)
    	//提升字段,如果继承字段为匿名字段,可以省略嵌套的字段,直接点
    	fmt.Println(e1.name, e1.age, e1.company)
        
        e1.talk()		// 子类没有从写,使用父类,重写后使用子类的
        e1.run()		// 子类从写方法
    }
    
    
    // 定义父类
    type Person struct{
        name string
        age int
    }
    
    // 定义子类
    type Employee struct{
        Person		// 使用匿名字段,字段类型为结构体,模拟继承关系
        company string		// 子类新增属性
    }
    
    // 定义父类方法
    func (p Person) talk() {
        fmt.Println("父类方法,talk")
    }
    
    // 子类新增方法
    func (e Empployee) run() {
        fmt.Println("子类新增方法,run")
    }
    
    //// 子类重写方法
    //func (e Employee) talk() {
    //    fmt.Println("子类重写方法,talk")
    //}
    
    • 总结:
      • 结构体嵌套多个结构体可以实现面向对象多继承的效果,包括属性和方法
      • 但没有继承顺序的说法,如果父类中有重复的属性或方法,必须指明是哪个父类的哪个属性或方法(c.A.test())
      • 子类可以新增和重写方法

    11. 接口

    11.1 什么是接口

    接口代表一种调用契约,是多个方法声明的集合。

    接口要实现的是做什么,而不关心怎么做,谁来做。

    Go中接口机制:只要目标方法集合包含接口声明的全部方法,就被视为实现了该接口,无需做显示声明(非入侵式)。

    目标类型可实现多个接口。

    接口通常以er作为后缀

    11.2 接口的定义

    type tester interface {
        test()
        string() string
    }
    
    type data struct{}
    
    func (*data) test() {}
    func (data) string() string { return "" }
    
    func main() {
        var d data
        
        // var t tester = d		// *data实现了接口中的两个方法,data只实现了一个,不能判断为实现了接口
        
        var t tester = &d
        t.test()
        fmt.Println(t.string())
    }
    
    // 一个比较好的借口实现案例
    func main()  {
    	m := Mouse{"鼠标"}
    	//m.startWork()
    	//m.stopWork()
    	testInterface(m)
    
    	k := KeyBoard{"键盘"}
    	//k.startWork()
    	//k.stopWork()
    	testInterface(k)
    
    	var m1 USB
    	m1 = m
    	//m1.name		// 接口对象不能访问实现类中的属性
    	m1.startWork()		// 接口对象可以访问实现类中的方法
    	m1.stopWork()
    }
    
    // 定义一个USB接口
    type USB interface {
    	startWork()		// USB设备开始工作
    	stopWork()		// USB设备结束工作
    }
    
    // 实现类
    type Mouse struct {
    	name string
    }
    
    type KeyBoard struct {
    	name string
    }
    
    // Mouse实现接口方法
    func (m Mouse) startWork() {
    	fmt.Println("Mouse开始工作")
    }
    
    func (m Mouse) stopWork()  {
    	fmt.Println("Mouse结束工作")
    }
    
    // KeyBoard实现接口方法k
    func (k KeyBoard) startWork() {
    	fmt.Println("KeyBoard开始工作")
    }
    
    func (k KeyBoard) stopWork()  {
    	fmt.Println("KeyBoard结束工作")
    }
    
    // 定义一个测试方法
    func testInterface(usb USB)  {	// 当需要接口类型对象时,可使用任意的实现类代替
    	usb.startWork()		// 如果传入Mouse类型,usb = m,传入KeyBoard类型,usb = k
    	usb.stopWork()
    }
    
    • 总结:
      • 当需要接口类型对象时,可使用任意的实现类代替,如果传入Mouse类型,usb = m,传入KeyBoard类型,usb = k
      • 接口对象不能访问实现类中的属性,可以访问实现类中的方法
      • 多个实现类实现一个接口,类似面向对象中的多态

    11.3 空接口

    内部没有任何方法的接口,可以看做任何对象都实现了空接口

    使用空接口可以向函数传入任意类型参数,定义可以接受任意类型参数的容器

    func main() {
        s := sct{"aaa"}
        var a0 A = s		// 空接口类型可以接受任意类型数值
        var a1 A = 1
        var a2 A = "aaa"
        var a3 A = func() {}
        var a4 A = 3.14
        fmt.Println(a0)
        fmt.Println(a1)
        fmt.Println(a2)
        fmt.Println(a3)
        fmt.Println(a4)
        
        // 使用空接口向函数传入任意类型参数
        func1(1)
        func1("abc")
        
        // 使用空接口定义可以接受任意类型参数的容器
        m := map[int] interface{}{
            1: 1,
            2: "qwerty",
            3: [3] int{1, 2, 3},
        }
        fmt.Println(m)
    }
    
    type A interface{}
    
    type sct struct{
        name string
    }
    
    func func1(a interface{}) {
        fmt.Println(a)
    }
    

    11.4 接口嵌套

    接口嵌套类似面向对象中多继承的效果

    func main() {
    	var s sct = sct{}
    	var a1 A = s
    	a1.test1()
    
    	var b1 B = s
    	b1.test2()
    
    	var c1 C = s
    	c1.test1()
    	c1.test2()
    	c1.test3()
    }
    
    type A interface{
    	test1()
    }
    
    type B interface{
    	test2()
    }
    
    type C interface{
    	A
    	B
    	test3()
    }
    
    type sct struct {
    }
    
    func (s sct) test1() {
    	fmt.Println("这是test1")
    }
    
    func (s sct) test2() {
    	fmt.Println("这是test2")
    }
    
    func (s sct) test3() {		// 如果要实现接口C,就要实现接口A和B
    	fmt.Println("这是test3")
    }
    

    11.5 接口断言

    要判断接口对象的实际类型,使用 instance := A.(type) 来断言

    //待整理
    

    12. 通道

    12.1 通道

    //待整理
    

    12.2 关闭通道

    //待整理
    

    12.3 缓冲通道

    //待整理
    

    12.4 定向通道

    //待整理
    

    12.5 select

    //待整理
    

    13. 并发

    //待整理
    

    14. 包结构

    14.1 main包

    //待整理
    

    14.2 package

    //待整理
    

    15. 反射

    //待整理
    
  • 相关阅读:
    pandas基础
    博客迁移公告!
    JavaScript: 认识 Object、原型、原型链与继承。
    微信小程序学习
    WEB安全
    webpack
    《JavaScript 高级程序设计》第四章:变量、作用域和内存问题
    NodeJS学习:搭建私有NPM
    《JavaScript 高级程序设计》第三章:基本概念
    Promise 基础学习
  • 原文地址:https://www.cnblogs.com/zj420255586/p/14292530.html
Copyright © 2020-2023  润新知