结构体、接口、类
目录:
1:结构体
2:接口
3:类
一、结构体
使用关键字 type 可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。
结构体的定义格式如下:
type 类型名 struct { 字段1 字段1类型 字段2 字段2类型 … }
-
-
struct{}:表示结构体类型,
type 类型名 struct{}
可以理解为将 struct{} 结构体定义为类型名的类型。 -
字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
-
type 关键字我们其实是使用过了。之前就是定义别名
结构体后面的struct 一起当成一个类型就可以了
颜色的红、绿、蓝 3 个分量可以使用 byte 类型:
type Color struct { R, G, B byte }
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存
二、接口
在Go语言中接口(interface)是一种类型,一种抽象的类型。
interface是一组method的集合,接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
接口(interface)是一种类型
接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。
接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。
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()) }
上面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码,如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢?
像类似的例子在我们编程过程中会经常遇到:
比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理呢?
比如三角形,四边形,圆形都能计算周长和面积,我们能不能把它们当成“图形”来处理呢?
比如销售、行政、程序员都能计算月薪,我们能不能把他们当成“员工”来处理呢?
Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一种抽象的类型。
当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。
2.2:定义接口
type 接口类型名 interface{ 方法名1( 参数列表1 ) 返回值列表1 方法名2( 参数列表2 ) 返回值列表2 … }
对各个部分的说明:
-
接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
-
方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
-
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略
type Writer interface{ //大写字母开头 意味着别的包 也可以访问 Write([]byte) error }
package main import ( "fmt" ) // 定义一个数据写入器 type DataWriter interface { WriteData(data interface{}) error } // 定义文件结构,用于实现DataWriter type file struct { } // 实现DataWriter接口的WriteData方法 func (d *file) WriteData(data interface{}) error { // 模拟写入数据 fmt.Println("WriteData:", data) return nil } func main() { // 实例化file f := new(file) // 声明一个DataWriter的接口 var writer DataWriter // 将接口赋值f,也就是*file类型 writer = f // 使用DataWriter接口进行数据写入 writer.WriteData("data") }
2.4:空接口
空接口是指没有定义任何方法的接口。
因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() { // 定义一个空接口x var x interface{} s := "码神之路" x = s fmt.Printf("type:%T value:%v\n", x, x) i := 100 x = i fmt.Printf("type:%T value:%v\n", x, x) b := true x = b fmt.Printf("type:%T value:%v\n", x, x) }
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数 func show(a interface{}) { fmt.Printf("type:%T value:%v\n", a, a) }
空接口作为map的值
使用空接口实现可以保存任意值的字典。
// 空接口作为map值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "李白" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo)
1.5.2 类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
接口值
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。
这两部分分别称为接口的动态类型
和动态值
。
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
-
x:表示类型为interface{}的变量
-
T:表示断言x可能是的类型。
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
func main() { var x interface{} x = "码神之路" v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("类型断言失败") } }
上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:
func justifyType(x interface{}) { switch v := x.(type) { case string: fmt.Printf("x is a string,value is %v\n", v) case int: fmt.Printf("x is a int is %v\n", v) case bool: fmt.Printf("x is a bool is %v\n", v) default: fmt.Println("unsupport type!") } }
因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
三、类
一个类型加上它的方法等价于面向对象中的一个类
在Go语言中,类型的代码
和绑定在它上面的方法
的代码可以不放置在一起
,它们可以存在不同的源文件中,唯一的要求是它们必须是同一个包的
在面向对象的语言中,类拥有的方法一般被理解为类可以做的事情。在Go语言中“方法”的概念与其他语言一致,
只是Go语言建立的“接收器”强调方法的作用对象是接收器,也就是类实例,而函数没有作用对象。
为结构体添加方法:
需求:将物品放入背包
面向对象的写法:
将背包做为一个对象,将物品放入背包的过程作为“方法”
2.2:继承
2.3:多态