DataWhale & Golang(八、结构体、方法、接口)
学习大纲:
目录
DataWhale & Golang(八、结构体、方法、接口)
补充:
Go 语言结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:
- Title :标题
- Author : 作者
- Subject:学科
- ID:书籍ID
访问结构体成员
如果要访问结构体成员,需要使用点号 . 操作符,格式为:
结构体.成员名"
结构体指针
- 你可以定义指向结构体的指针类似于其他指针变量,格式如下:
var struct_pointer *Books
- 以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:
struct_pointer = &Book1
- 使用结构体指针访问结构体成员,使用 "." 操作符:
struct_pointer.title
8.结构体、方法、接口
8.1.结构体
- Go 语言中没有“类”的概念,也不支持像继承这种面向对象的概念。
- 但是Go 语言的结构体与“类”都是复合结构体,而且Go 语言中结构体的组合方式比面向对象具有更高的扩展性和灵活性。
8.1.1 结构体定义
结构体一般定义如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
package main
import "fmt"
type Books struct {
title string
author string
subject string
}
func main() {
// 创建一个新的结构体
fmt.Println(Books{"xhc", "学", "Golang"})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Golang", author: "xhc", subject: "学习"})
// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Golang", author: "xhc"})
}
8.1.2 操作结构体
声明完结构体之后我们需要创建结构体的实例,可以使用如下几种方法创建,仍然以上面的Student结构体为例。
s1 := new(Student) //第一种方式
s2 := Student{"james", 35} //第二种方式
s3 := &Student { //第三种方式
Name: "LeBron",
Age: 36,
}
- - 使用new函数会创建一个指向结构体类型的指针,创建过程中会自动为结构体分配内存,结构体中每个变量被赋予对应的零值。
- - 也可以使用第二种方式生命结构类型,需要注意的是此时给结构体赋值的顺序需要与结构体字段声明的顺序一致。
- - 第三种方式更为常用,我们创建结构体的同时显示的为结构体中每个字段进行赋值。
- 声明完结构体之后可以直接按如下方式操作结构体。
s1.Name = "james"
s1.Age = 35
注意:
- 结构体也仍然遵循可见性规则,要是定义结构体的字段时首字母为小写在其他包是不能直接访问该字段的。
- 我们直接通过p.int的方式来访问结构体中的匿名字段对其赋值,对于一个结构体来说,每一种数据类型只能有一个匿名字段。
8.1.3 标签
在go语言中结构体除了字段的名称和类型外还有一个可选的标签tag,标记的tag只有reflect包可以访问到,一般用于orm或者json的数据传递
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
我们可以使用go自带的json包将声明的结构体变量转变为json字符串。
func ToJson(s *Student) (string, error) {
bytes, err := json.Marshal(s)
if err != nil {
return "", nil
}
return string(bytes), nil
}
需要注意的是,内嵌结构体和声明一个结构体类型的字段是不同的,例如下面的结构体B的定义方式与上面是完全不同的。
type B struct {
a A
Name string
}
8.2 方法
8.2.1 方法定义
方法与函数类似,只不过在方法定义时会在func和方法名之间增加一个参数
func (r Receiver)func_name(){
// body
}
其中r被称为方法的接收者,例如我们下面这个例子:
type Person struct {
name string
}
func (p Person) GetName() string {
return p.name
}
8.2.2 方法接收者
- 对于一个方法来说接收者分为两种类型:值接收者和指针接收者。上面的GetName的接收者就是值接收者。我们再为Person结构体定义一个指针接收者。
func (p *Person)SetName(name string){
p.name = name
}
- 使用值接收者定义的方法,在调用的时使用的其实是值接收者的一个拷贝,所以对该值的任何操作,都不会影响原来的类型变量。
- 但是如果使用指针接收者的话,在方法体内的修改就会影响原来的变量,因为指针传递的也是地址,但是是指针本身的地址,此时拷贝得到的指针还是指向原值的,所以对指针接收者操作的同时也会影响原来类型变量的值。而且在go语言中还有一点比较特殊,我们使用值接收者定义的方法使用指针来调用也是可以的,反过来也是如此
func main() {
p := &Person{
name: "james",
}
fmt.Println(p.GetName())
p1 := Person{
name: "james",
}
p1.SetName("kobe")
fmt.Println(p1.GetName())
}
8.3 接口
8.3.1 接口定义
接口相当于一种规范,它需要做的是谁想要实现我这个接口要做哪些内容,而不是怎么做
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
8.3.2 实现接口
在go语言中不需要显示的去实现接口,只要一个类型实现了该接口中定义的所有方法就是默认实现了该接口,而且允许多个类型都实现该接口,也允许一个类型实现多个接口。
type Animal interface {
Eat()
}
type Bird struct {
Name string
}
func (b Bird) Eat() {
fmt.Println(b.Name + "吃肉啊")
}
type Dog struct {
Name string
}
func (d Dog) Eat() {
fmt.Println(d.Name + "吃肉")
}
func EatWhat(a Animal) {
a.Eat()
}
func main() {
b := Bird{"Bird"}
d := Dog{"Dog"}
EatWhat(b)
EatWhat(d)
}
在EatWaht函数中是传递一个Animal接口类型,上面的Bird和Dog结构体都实现了Animal接口,所以都可以传递到函数中去来实现多态特性。
8.3.3 类型断言
有些时候方法传递进来的参数可能是一个接口类型,但是我们要继续判断是哪个具体的类型才能进行下一步操作,这时就用到了类型断言,下面我们通过一个例子来进行讲解:
func IsDog(a Animal) bool {
if v, ok := a.(Dog); ok {
fmt.Println(v)
return true
}
return false
}
上面的方法对传递进来的参数进行判断,判断其是否为Dog类型,如果是Dog类型的话就会将其进行转换为v,ok用来表示是否断言成功。
8.3.4 空接口
空接口是一个比较特殊的类型,因为其内部没有定义任何方法所以空接口可以表示任何一个类型,比如可以进行下面的操作:
var any interface{}
any = 1
fmt.Println(any)
any = "hello"
fmt.Println(any)
any = false
fmt.Println(any)