网站ui设计怎么做网站seo谷歌
1. 接口
1.1 基本介绍
接口中到底应该定义些什么?
在Go语言中,接口是声明函数的集合,但只有函数签名,没有具体的功能。
属于是面向对象中,行为的约束,面向对象中的类有自己的属性(可以当成数据成员或字段)、方法(可以当成函数,函数有自己的操作或行为),如下代码:
type 接口名称 interface {dog() // 方法1签名cat() // 方法2签名方法1 (参数列表1) 返回值列表1方法2 (参数列表2) 返回值列表2
}
上面这个接口A,要求实现者,必须实现方法1和方法2(就是命名函数时,只能用接口中的函数名命名)。
也就是说,如果有谁声称实现了某某接口,那就必须实现该接口中的所有方法。
1.2 使用接口注意事项
- 接口命名习惯在接口名后面加上er后缀。
- 参数列表、返回值列表参数名可以不写。
- 如果要在包外使用接口,接口名应该首字母大写,方法要在包外使用,方法名首字母也要大写。
- 接口中的方法应该设计合理,不要太多,甚至越少越好。
2. 接口实现
如果一个结构体实现了一个接口声明的所有方法,就说结构体实现了该接口。
一个结构体可以实现多个接口。
2.1 实现单个接口
package mainimport "fmt"// 定义一个"运动"接口
type Sport interface {// Go倾向小接口,只有一个方法都行run() // 跑步jump() // 跳跃
}// 该接口由"人"来实现
type Person struct {name stringage int
}// 实现接口
///实现接口方法1
func (*Person) run() {fmt.Println("跑步")
}///实现接口方法2
func (*Person) jump() {fmt.Println("跳跃")
}func main() {p1 := new(Person) // 创建一个新的指针实例,减少数据拷贝次数p1.jump()p1.run()
}
==============调试结果==============
跳跃
跑步
再来看一个特殊示例
package mainimport "fmt"type Sport interface {run() // 跑步jump() // 跳跃
}type Person struct {name stringage int
}func (*Person) run() {fmt.Println("跑步")
}func (*Person) jump() {fmt.Println("跳跃")
}// 我这里新加了一个成员方法,请问Person结构体实现了Sport接口吗?
// 当然实现了,上面两个成员方法已经实现了Sport接口,我当然可以继续定义Person结构体的其他方法。
func (t *Person) swim() {fmt.Println("游泳")
}func main() {p1 := new(Person)p1.jump()p1.run()p1.swim()
}
==============调试结果==============
跳跃
跑步
游泳
2.1.1 把接口类型赋值给变量
package mainimport "fmt"type Sport interface {run()jump()
}type Person struct {name stringage int
}func (*Person) run() {fmt.Println("跑步")
}func (*Person) jump() {fmt.Println("跳跃")
}func (t *Person) swim() {fmt.Println("游泳")
}func main() {p1 := new(Person)p1.jump()p1.run()p1.swim()fmt.Println("-------------------")var a Sport = p1 // 把接口类型赋值给变量,该变量就是个接口类型的变量了fmt.Println(a)a.jump() // 可以直接调用接口中的方法a.run()
}
==============调试结果==============
跳跃
跑步
游泳
-------------------
&{ 0}
跳跃
跑步
3. 接口嵌入(组合)
除了结构体可以嵌套,接口也可以。接口嵌套组合成了新接口。
type Reader interface {Read(p []byte) (n int, err error)
}
type Closer interface {Close() error
}
type ReadCloser interface {ReaderCloser
}
这样写的好处,就是如果需要同时调用Reader和Closer,直接调用ReadCloser就行了。
4. 空接口
4.1 什么是空接口
在 Go 语言中,空接口(interface{})是一个没有方法的接口。这意味着任何类型都可以实现空接口(或者说空接口可以匹配任意类型的数据),因为它们不需要满足任何方法的要求。
空接口可以存储任何类型的值,这使得它在需要存储未知类型数据时非常有用。
4.2 定义空接口
4.2.1 基本定义
func main() {var b interface{} // 空接口类型,注意是空接口类型,这部分interface{}表示数据类型var c any // 这样也行,any是interface{}的别名(鼠标移到any上能看到)
}
4.2.2 空接口和任意数据类型组合
func main() {var a = 500var b interface{} // 空接口,b的类型是接口类型,但是个空接口// var b any // 或者这样声明,更好理解,any表示任意数据类型// 为什么b能等于a?因为interface{}是空接口,里面没有任何方法。// 换句话说,不管什么类型,都能实现空接口。b = afmt.Printf("b的类型:%T\nb的值:%+[1]v\n", b)fmt.Println("-----------------------------------")var c = "abc"b = cfmt.Printf("b的类型:%T\nb的值:%+[1]v\n", b)
}
================调试结果================
b的类型:int
b的值:500
-----------------------------------
b的类型:string
b的值:abc
4.3 为什么要用空接口
比如我有这样一个需求:我需要一个容器,该容器能存储任意类型的数据,并且能够混装,注意是能混装!
这咋整呢?用结构体吗?不行,结构体虽然能混装不同的数据类型,但它有个前提,必须要有明确的字段。
所以这种情况下只有空接口interface{}是最合适的。
var d = []any{1, 2.1, true, "abc"}
// []interface{},表示数据类型,后面的{}表示元素字面量定义
var e = []interface{}{1, 2.1, true, "abc"}
fmt.Printf("d的类型:%T|d的值:%+[1]v\n", d)
fmt.Printf("e的类型:%T|e的值:%+[1]v\n", e)
================调试结果================
d的类型:[]interface {}|d的值:[1 2.1 true abc] // 返回值为任意数据类型的切片
e的类型:[]interface {}|e的值:[1 2.1 true abc]
5. 接口类型断言
5.1 基本介绍
在 Go 语言中,当你有一个接口变量时,你可以通过 类型断言 来检查它实际存储的类型,注意只能配合接口使用。
类型断言的基本语法是 变量.(数据类型)。
5.2 示例
5.2.1 基本使用
var a = 500
var b interface{} = a// v, ok := b.(int) 含义:把b接口中存储的数据当成int,可以吗?
// 可以:ok返回true,v返回对应的值
// 不可以:ok返回false,v返回对应数据类型的默认值(int:0,str: 空串)
v, ok := b.(int)
fmt.Printf("v=%v ok=%v\n", v, ok)v1, ok1 := b.(string)
fmt.Printf("v=%v ok=%v\n", v1, ok1)
================调试结果================
v=500 ok=true
v= ok=false // 注意这里的v是空串
5.2.2 结合if判断
string类型,断言失败会返回空串,但万一默认就是空串呢?所以这里最好结合if一起使用,判断ok的值来判断类型断言是否成功,如下代码:
var a = 500
var b interface{} = aif v, ok := b.(string); ok {fmt.Printf("断言成功,v的类型为:%T 值为:%[1]v", v)
} else {fmt.Printf("断言失败,v的类型为:%T 值为:%[1]v", v)
}
================调试结果================
断言失败,v的类型为:string 值为:
5.3 type-switch
当需要判断多个原始数据类型时,写if的话,需要写很长的代码,这里可以使用type-switch来替代if,更加简洁。
注意:type-switch只能配合接口类型断言使用。
var a = 500
var b interface{} = a// b.(type)的意思,是取b接口中数据的类型
switch b.(type) { // 这个b.(type)可以用变量接住,值会赋值给该变量
case int:fmt.Println("int,值=",b)
case string:fmt.Println("string")
case bool:fmt.Println("bool")
case nil: // 在判断接口或指针类型时,最好加上nil判断,防止出现空接口或空指针fmt.Println("nil 空接口")
default:fmt.Println("其他")
}
================调试结果================
int,值= 500
6. 接口输出格式化
6.1 普通方式格式化
先看一段代码:
package mainimport ("fmt"
)type Sport interface {run()jump()
}type Person struct {name stringage int
}func (*Person) run() {fmt.Println("跑步")
}func (*Person) jump() {fmt.Println("跳跃")
}func (t *Person) swim() {fmt.Println("游泳")
}func main() {var p = new(Person)fmt.Printf("%+v\n", p)
}
================调试结果================
&{name: age:0}
可以看到上述代码默认输出格式,看起来不太美观,可以如下优化一下:
package mainimport ("fmt"
)type Sport interface {run()jump()
}type Person struct {name stringage int
}func (*Person) run() {fmt.Println("跑步")
}func (*Person) jump() {fmt.Println("跳跃")
}func (t *Person) swim() {fmt.Println("游泳")
}func Print(p *Person) string {return fmt.Sprintf("我叫%s,我的年龄是%d", p.name, p.age)
}func NewPerson(name string, age int) *Person {return &Person{name, age}
}func main() {var p = NewPerson("Tom", 21)fmt.Println(Print(p))
}
================调试结果================
我叫Tom,我的年龄是21
6.2 方法格式化
package mainimport ("fmt"
)type Sport interface {run()jump()
}type Person struct {name stringage int
}func (*Person) run() {fmt.Println("跑步")
}func (*Person) jump() {fmt.Println("跳跃")
}func (t *Person) swim() {fmt.Println("游泳")
}func (p *Person) String() string {return fmt.Sprintf("我叫%s,我的年龄是%d", p.name, p.age)
}func NewPerson(name string, age int) *Person {return &Person{name, age}
}func main() {var p = NewPerson("Tom", 21)fmt.Printf("%+v\n", p)fmt.Printf("%v\n", p)fmt.Println(p)fmt.Print(p)
}
================调试结果================
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21
我叫Tom,我的年龄是21