Golang 中的包与接口
1.包的理论部分
Go 语言中支持模块化的开发理念,在 Go 语言中使用包(package)来支持代码模块化和代码复用。一个包是由一个或多个 Go 源码文件(.go 结尾的文件)组成,是一种高级的代码复用方案,Go 语言为我们提供了很多内置包,如 fmt、os、io 等。
2.包的基础使用与导入
如果想让一个包中的标识符(如变量、常量、类型、函数等)能被外部的包使用,那么标识符必须是对外可见的(public)。
在 Go 语言中是通过标识符的首字母大/小写来控制标识符的对外可见(public)/不可见(private)的。在一个包内部只有首字母大写的标识符才是对外可见的。
例如我们定义一个名为 demo 的包,在其中定义了若干标识符。在另外一个包中并不是所有的标识符都能通过 demo.前缀访问到,因为只有那些首字母是大写的标识符才是对外可见的。
package packagename//声明包文件(在文件顶部)
要在当前包中使用另外一个包的内容就需要使用 import 关键字引入这个包,并且 import 语句通常放在文件的开头,package 声明语句的下方。
当引入的多个包中存在相同的包名或者想自行为某个引入的包设置一个新包名时,都需要通过 importname 指定一个在当前文件中使用的新包名。例如,在引入 fmt 包时为其指定一个新包名 f。
如果引入一个包的时候为其设置了一个特殊_作为包名,那么这个包的引入方式就称为匿名引入。一个包被匿名引入的目的主要是为了加载这个包,从而使得这个包中的资源得以初始化。 被匿名引入的包中的 init 函数将被执行并且仅执行一遍。
import (
"fmt"
"net/http"
"os"
)//引入多个包
import f "fmt"
f.Println("Hello world!")
//给fmt包赋予一个别名f
import _ "github.com/go-sql-driver/mysql"//导入匿名包
3.包的 init 函数
- 在每一个 Go 源文件中,都可以定义任意个 init 初始化函数
- 这种特殊的函数不接收任何参数也没有任何返回值,我们也不能在代码中主动调用它。当程序启动的时候,init 函数会按照它们声明的顺序自动执行。
- 一个包的初始化过程是按照代码中引入的顺序来进行的,所有在该包中声明的 init 函数都将被串行调用并且仅调用执行一次。每一个包初始化的时候都是先执行依赖的包中声明的 init 函数再执行当前包中声明的 init 函数。确保在程序的 main 函数开始执行时所有的依赖包都已初始化完成。
- 每一个包的初始化是先从初始化包级别变量开始的。例如从下面的示例中我们就可以看出包级别变量的初始化会先于 init 初始化函数。
func init(){
fmt.Println("student模块包初始化函数执行")
}
//注意:初始化init函数是在包级别变量创建完之后再执行的
4.接口的快速了解
总的来说接口就在于是结构体方法的抽象集合,下面一个小案例:
type Cat struct{}
func (c Cat) Say() {
fmt.Println("喵喵喵~")
}
type Dog struct{}
func (d Dog) Say() {
fmt.Println("汪汪汪~")
}
type Sheep struct{}
func (s Sheep) Say() {
fmt.Println("咩咩叫~")
}
type Sayer interface {
Say()//只要具备say方法的结构体都是属于Sayer的抽象部分,可以直接算是属于sayer类型
}
func Bark(s Sayer) {
s.Say()//可以基于他们的sayer集合,写一个他们共同具有的bark函数,直接复用
}
var c cat
Bark(c)
var d dog
Bark(d)
//直接进行调用即可
5.接口的基本使用与声明
//接口的完整调用格式图下面是所示:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
正式在于接口的相关特性,接口具有实现条件:
接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口。
6.接口的接受者问题
//值接受类型下:
// Dog 狗结构体类型
type Dog struct{}
// Move 使用值接收者定义Move方法实现Mover接口
func (d Dog) Move() {
fmt.Println("狗会动")
}
var x Mover // 声明一个Mover类型的变量x
var d1 = Dog{} // d1是Dog类型
x = d1 // 可以将d1赋值给变量x
x.Move()
var d2 = &Dog{} // d2是Dog指针类型
x = d2 // 也可以将d2赋值给变量x
x.Move()
// Cat 猫结构体类型
type Cat struct{}
// Move 使用指针接收者定义Move方法实现Mover接口
func (c *Cat) Move() {
fmt.Println("猫会动")
}
var c1 = &Cat{} // c1是*Cat类型
x = c1 // 可以将c1当成Mover类型
x.Move()
var c1 = &Cat{} // c1是*Cat类型
x = c1 // 可以将c1当成Mover类型
x.Move()
//正确的,没有问题
var c2 = Cat{} // c2是Cat类型
x = c2 // 不能将c2当成Mover类型
// 上面的代码无法通过编译
7.空接口
空接口是指没有定义任何方法的接口类型。因此任何类型都可以视为实现了空接口。也正是因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值。
// 空接口
// Any 不包含任何方法的空接口类型
type Any interface{}
// Dog 狗结构体
type Dog struct{}
func main() {
var x Any
x = "你好" // 字符串型
fmt.Printf("type:%T value:%v\n", x, x)
x = 100 // int型
fmt.Printf("type:%T value:%v\n", x, x)
x = true // 布尔型
fmt.Printf("type:%T value:%v\n", x, x)
x = Dog{} // 结构体类型
fmt.Printf("type:%T value:%v\n", x, x)
}
//通常我们在使用空接口类型时不必使用type关键字声明,可以像下面的代码一样直接使用interface{}。
var x interface{} // 声明一个空接口类型变量x
8.空接口在实际开发中的应用
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
使用空接口实现可以保存任意值的字典。
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
9.类型断言
使用 x.(T)的方法即可获取其类型, 该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言成功,为 false 则表示断言失败。
var n Mover = &Dog{Name: "旺财"}
v, ok := n.(*Dog)
if ok {
fmt.Println("类型断言成功")
v.Name = "富贵" // 变量v是*Dog类型
} else {
fmt.Println("类型断言失败")
}
// justifyType 对传入的空接口类型变量x进行类型断言
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!")
}
}