Go结构体接口指针 | 青训营笔记

118 阅读7分钟

Go语言中没有像C++ Java等 “类”的概念,也不支持“类”的继承,多态等面向对象的特征(不是纯oop的语言)。但是Go语言可以通过结构体和接口的使用实现比向对象具有更高的扩展性和灵活性的功能。

类型别名和自定义类型

自定义类型

除了Go语言基本的数据类型string、整型、浮点型、布尔等数据类型, 我们可以通过type关键词 自定义类型。

type example int

通过type定义的example就是一种新的类型,它只是具有int的特性,但不是int类型

类型别名

类型别名是一个类型的别称,本质上还是同一个类型。

type 原神=int

常见的rune,byte,any就是类型别名

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
​
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
​
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

区别

其实最主要的区别是 自定义类型是新类型,类型别名依然是旧类型,只不过有了一个别名新的名称 。

package main
​
import (
    "fmt"
    "reflect"
)
​
type 袁神 inttype 原神 = intfunc main() {
    var a 袁神
​
    var b 原神
​
    fmt.Printf("type of a:%T\n", a)
    fmt.Printf("type of b:%T\n", b)
​
    rfTypeOf(a)
    rfTypeOf(b)
​
    TypeOf(a)
    TypeOf(b)
}
​
func rfTypeOf(data interface{}) {
    of := reflect.TypeOf(data)
    fmt.Println(of)
}
​
func TypeOf(data interface{}) {
    switch data.(type) {
    case 袁神:
        fmt.Println("Type is int")
    case nil:
        fmt.Println("Type is nil")
    default:
        fmt.Println("Type Not Found")
    }
}

结构体

结构体的定义

使用typestruct关键字来定义结构体:

    type 类型名 struct {
        字段名 字段类型
        字段名 字段类型
    }

结构体实例

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。 结合前面所说 结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型 或者是 **:=**语法糖(匿名结构体)。

//var
var 袁神 struct{Name string; Age int} ; 袁神.Name = "YXH" ; 袁神.Age = 16//推荐
袁神 := struct {
        Name string
        Age int
    }{
        "YXH",
        16,
    }
package main
​
import (
    "fmt"
)
​
type 袁神 struct {
    Name string
    Age  int
}
​
func main() {
​
    //最常见的方式
    a := 袁神{
        Name: "yuan1",
        Age:  18,
    }
​
    var b 袁神
    b.Name = "yuan2"
    b.Age = 18
​
    c := struct {
        Name string
        Age  int
    }{
        "yuan3",
        18,
    }
    
    d:=NewYuanShen("袁神 tql",18)
    
    
    fmt.Printf("%#v\n", a)
    fmt.Printf("%#v\n", b)
    fmt.Printf("%#v\n", c)
    fmt.Printf("%#v\n", d)
​
}
​
//构造函数
func NewYuanShen(name string, age int) *袁神 {
    return &袁神{
        Name: name,
        Age:  age,
    }
}

结构体的访问

我们通过上述 代码可以轻而易举的知道 给以直接拿到整个结构体变量 但是我们应该怎么获取结构体的成员呢?

我们可以通过 . 操作来进行访问。

  a := struct {
        Name string
        Age int
    }{
        "yuan1",
        18,
    }
​
fmt.Println(a)
fmt.Println(a.Name)
fmt.Println(a.Age)

但并不是所有的成员函数都可以访问

在Go中 没有public、protected、private等访问控制修饰符,它是通过字母大小写来控制可见性的,如果定义的常量、变量、类型、接口、结构、函数等的名称是大写字母开头表示能被其它包访问或调用(相当于public),非大写开头就只能在包内使用(相当于private,变量或常量也可以下划线开头)

嵌套结构体

我们把一个结构体中可以嵌套包含另一个结构体或结构体指针 叫做 嵌套结构体或者是结构体内嵌

type People struct {
    Name string
    Age  int
}
​
type Banked struct {
    Name string
    member People 
}

方法和接收者

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

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

举一个🌰

type People struct {
    Name  string
    Age   int
    Books []Book
}
​
type Book struct {
    Name string
}
​
func (w People) PrintName() {
    fmt.Println(w.Name)
}
func (w People) PrintAge() {
    fmt.Println(w.Age)
}
​
func (w People) PrintBook() {
    fmt.Println(w.Books)
}
​
func (b Book) PrintBookName() {
    fmt.Println(b.Name)
}

🧷指针

区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针

要搞明白Go语言中的指针需要先知道3个概念:指针地址指针类型指针取值

Go语言中的指针

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

具体可以 看看go语言中的函数传参都是值传递

指针地址和指针类型

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string

我们可以通过 & 来获取 地址

 ptr := &v    // v的类型为

举个🌰

func main() {
    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
}

🕳空指针

  • 当一个指针被定义后没有分配到任何变量时,它的值为 nil
  • 空指针的判断
package main
​
import "fmt"func main() {
   var p *string
   fmt.Println(p)
   fmt.Printf("p的值是%v\n", p)
   if p != nil {
       fmt.Println("非空")
   } else {
       fmt.Println("空值")
   }
}

🌐new和make

在这之前 我们先来看一个 🌰 大家 不忙猜测 一下 运行程序会出现 什么问题

func main() {
    var a *string
    *a = "NB"
    fmt.Println(*a)
​
    var b map[string]string
    b["NB"] = "YYDS"
    fmt.Println(b)
}

🆕new

new是一个内置的函数,它的函数签名如下:

func new(Type) *Type

对于之前的🌰

  var a *string
  a=new(string)
    *a = "NB"
    fmt.Println(*a)

🤖make

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

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

对于之前的🌰

    var b map[string]string
  b=make(map[string]string)
    b["NB"] = "YYDS"
    fmt.Println(b)

🎉接口

接口(interface) 可以定义一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节

接口类型

在Go语言中接口(interface)是一种类型,一种抽象的类型。

interface是方法(1.18以后 加入了泛型 准确说 是类型 因为 方法也是一种类型)的集合

为什么要用接口

在讲解之前我们来 看一下以下🌰

type Cat struct{}
​
func (c Cat) Say() string { return "喵喵喵" }
​
type Dog struct{}
​
func (d Dog) Say() string { return "汪汪汪" }
​
func main() {
    c := Cat{}
    fmt.Println("猫:", c.Say())
    d := Dog{}
    fmt.Println("狗:", d.Say())
}

上面的代码中定义了🐶and🐱,然后它们都会叫,你会发现有重复的代码,如果我们后续再加上其他动物的话,我们的代码还会一直重复下去,为了代码 的扩展性,那我们能不能把这动物 都 归为 "会叫的动物"

除了 上面 这些还有很多🌰

比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们可以把他当成 “支付方式” 来一起处理。

接口的定义

type 接口类型名 interface{
        方法名1( 参数列表1 ) 返回值列表1
        方法名2( 参数列表2 ) 返回值列表2
    }

🔍接口的实现

我们 可以 根据上面的🌰 实现一个 sayer 接口

type Sayer interface {
    Say()
}
​
type dog struct {
}
​
type cat struct {
}
​
func (d dog) Say() {
    fmt.Println("汪汪汪")
}
​
func (c cat) Say() {
    fmt.Println("喵喵喵")
}
func main() {
    var x Sayer
    a := cat{}
    b := dog{}
    x = a
    x.Say()
    x = b
    x.Say()
}

🕳空接口

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。空接口类型的变量可以存储任意类型的变量

使用空接口实现可以接收任意类型的函数参数。

   var x interface{}
    s := "YuanShen"
    x = s
​
func NilInterface(x  interface{}){
}

类型断言

上面我们提到了 空接口可以存储任意类型,但是我们怎么才能知道他的类型呢?

通过.(type)返回value和ok,若断言失败则ok为false,value为空值,若成功则返回对应类型的value,ok为true

func main() {
    var x interface{}
    s := "YuanShen"
    x = s
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("类型断言失败")
    }
}