Golang——7、包与接口详解

1、Golang包详解

1.1、Golang中包的定义和介绍

包(package)是多个 Go 源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmt、strconv、strings、sort、errors、time、encoding/json、os、io等。
Golang 中的包可以分为三种:1、系统内置包。2、自定义包。3、第三方包。
系统内置包: Golang语言给我们提供的内置包,引入后可以直接使用,如 fmt、 strconv、 strings、sort、errors、time、encoding/json、os、io等。
自定义包:开发者自己写的包。
第三方包:属于自定义包的一种,需要下载安装到本地后才可以使用,如前面给大家介绍的。
"github.com/shopspring/decimal"包解决float精度丢失问题。

1.2、Golang包管理工具go mod

在Golang1.11版本之前如果我们要自定义包的话必须把项目放在GOPATH 目录。Go1.11版本之后无需手动配置环境变量,使用 go mod 管理项目,也不需要非得把项目放到 GOPATH指定目录下,你可以在你磁盘的任何位置新建一个项目,Go1.13 以后可以彻底不要GOPATH了。

首先在项目路径下使用go mod init 文件名,来初始化项目,生成一个go.mod文件。
在这里插入图片描述
注意,这个文件名必须是当前所在目录下的文件名,比如当前我在demo60目录下,因此文件名就是demo60。

在这里插入图片描述
另外,使用命令go mod可以查看其他选项,在这些选项中我们最常用的就是init和tidy。
其中go mod tidy表示增加丢失的module,去掉未用的module,当我们执行这个指令后,就会自动将我们需要包下载下来,将不需要的包去除。


1.3、Golang中自定义包

包(package)是多个Go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包。
在这里插入图片描述
如图:我们在demo60目录下创建一个clac目录,并创建一个clac。

package main

import (
	"demo60/clac"
	"fmt"
)

func main() {
	res1 := clac.Add(5, 10)
	res2 := clac.Sub(5, 10)
	fmt.Println(res1, res2)
}

接着我们在main.go中引入我们自定义的clac包,并调用里面的Add和Sub方法。
在这里插入图片描述

同时我们需要注意,在clac这个包中,可以定义变量和函数,但是必须是大写的,大写的表示公有属性,可以被其他包访问,小写表示私有属性,不能被其他包使用。


当然我们也可以引入多个包,例如我们在demo60目录下创建一个tools文件夹,里面再定义几个.go文件,但他们都属于同一个包tools。
在这里插入图片描述

package tools

import "fmt"

func PrintInfo() {
	fmt.Println("hello world")
}
package tools

func Mul(x, y int) int {
	return x * y
}

然后我们在main.go中调用这两个函数。

package main

import (
	"demo60/clac"
	"demo60/tools"
	"fmt"
)

func main() {
	res1 := clac.Add(5, 10)
	res2 := clac.Sub(5, 10)
	fmt.Println(res1, res2)
	fmt.Println(clac.AA)

	fmt.Println(tools.Mul(2, 3))
	tools.PrintInfo()
}

在这里插入图片描述
上面就演示了我们自定义了两个包clac和tools,并通过import引入使用。


另外,我们也可以匿名导入一个包,不过在现在vscode的go语法已经非常智能了,所以基本上就用不到了。
例如下面这种情况,我们导入了一个fmt包,但是并没有使用,我们可以在前面加上_

package main

import _ "fmt"

func main() {

}

我们也可以给导入的包起别名,比如我们导入的一个包名字过长,我们就可以给他起个别名。

package main

import f "fmt"

func main() {
	f.Println("hello world")
}

在这里插入图片描述


1.4、Golang中使用第三包

之前我们说过,Golang中的浮点数类型是有精度丢失的,所以我们可以使用第三方包decimal,下面演示如何使用第三方包decimal。
首先在项目目录下执行go mod init初始化项目,然后实现main.go文件。

package main

import (
	"fmt"

	"github.com/shopspring/decimal"
)

func main() {
	num1 := 8.2
	num2 := 3.8
	d1 := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2))
	fmt.Println(d1)
}

当我们这样写好代码保存后是无法运行的,因为我们并没有第三方包decimal。
所以这里就需要使用之前的go mod tidy。它会自动根据我们的文件查找是否有缺少的包,或者不需要使用的包。

在这里插入图片描述


1.5、init函数

在Go语言程序执行时导入包语句会自动触发包内部init()函数的调用。需要注意的是:init()函数没有参数也没有返回值。init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。
在这里插入图片描述

Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入了其他的包。Go 编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。
在运行时,被最后导入的包会最先初始化并调用其init()函数,如下图示:

在这里插入图片描述
有点类似DFS的感觉。


2、接口详解

Golang中的接口是一种抽象数据类型,Golang中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现。
通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范。

2.1、接口的定义

在Golang中接口(interface)是一种类型,函数method的集合,Golang中的接口不能包含任何变量。一种抽象的类型。接口(interface)是一组在Golang中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想Golang中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。
在这里插入图片描述
其中:
• 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。 接口名最好要能突出该接口的类型含义。
• 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
• 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

实现一个Usber接口规范,接着让手机和相机实现这个接口。

package main

import "fmt"

type Usber interface {
	start()
	stop()
}

type Phone struct {
	Name string
}

func (p Phone) start() {
	fmt.Printf("%v启动\n", p.Name)
}

func (p Phone) stop() {
	fmt.Printf("%v关闭\n", p.Name)
}

type Camera struct {
	Name string
}

func (c Camera) start() {
	fmt.Printf("%v启动\n", c.Name)
}

func (c Camera) stop() {
	fmt.Printf("%v关闭\n", c.Name)
}

func main() {
	p := Phone{
		"小米手机",
	}
	c := Camera{
		"哈苏相机",
	}
	var u1, u2 Usber // Golang中接口就是一个数据类型
	u1 = p           // 表示Phone实现接口Usber
	u2 = c           // 表示Camera实现接口Usber
	u1.start()
	u1.stop()
	u2.start()
	u2.stop()
}

在这里插入图片描述

下面我们可以再实现一个Computer结构体,然后实现它的一个方法work,该方法必须传入一个Usber接口。

package main

import "fmt"

type Usber interface {
	start()
	stop()
}

type Phone struct {
	Name string
}

func (p Phone) start() {
	fmt.Printf("%v启动\n", p.Name)
}

func (p Phone) stop() {
	fmt.Printf("%v关闭\n", p.Name)
}

type Camera struct {
	Name string
}

func (c Camera) start() {
	fmt.Printf("%v启动\n", c.Name)
}

func (c Camera) stop() {
	fmt.Printf("%v关闭\n", c.Name)
}

type Computer struct {
}

func (c Computer) work(u Usber) {
	u.start()
	u.stop()
}

func main() {
	p := Phone{
		"小米手机",
	}
	c := Camera{
		"哈苏相机",
	}
	computer := Computer{}
	computer.work(p)
	computer.work(c)
}

2.2、空接口

Golang中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

1、定义空接口
空接口表示没有任何约束,任何类型都可以实现空接口。

package main

import "fmt"

type A interface{}

func main() {
	var a A
	var str = "你好golang"
	a = str
	fmt.Printf("值: %v, 类型: %T\n", a, a)

	var num = 10
	a = num
	fmt.Printf("值: %v, 类型: %T\n", a, a)

	
	var flag = true
	a = flag
	fmt.Printf("值: %v, 类型: %T\n", a, a)
}

在这里插入图片描述

2、空接口作为函数参数

package main

import "fmt"

func show(a interface{}) {
	fmt.Printf("值: %v, 类型: %T\n", a, a)
}

func main() {
	show(20)
	show("你好golang")
	show([]int{1, 2, 3, 4, 5})
}

在这里插入图片描述

3、map的值实现空接口,使用空接口实现可以保存任意值的字典。

package main

import "fmt"

func main() {
	var m = make(map[string]interface{})
	m["username"] = "张三"
	m["age"] = 18
	m["hobby"] = []string{"跑步", "爬山", "写代码"}
	fmt.Printf("值: %v, 类型: %T\n", m, m)
}

在这里插入图片描述

4、切片实现空接口。

var slice = []interface{}{"张三", 18, true, 33.22}
fmt.Printf("值: %v, 类型: %T\n", slice, slice)

在这里插入图片描述


2.3、类型断言

一个接口的值 (简称接口值) 是由一个具体类型和具体类型的值两部分组成的。 这两部分分别称为接口的动态类型和动态值。
如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)

其中:
• x :表示类型为interface{}的变量
• T:表示断言x可能是的类型。

该语法返回两个参数,第一个参数是x转化为 T 类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为 alse则表示断言失败。

1、基本使用。

package main

import "fmt"

func main() {
	var x interface{}
	x = "hello golang"
	v, ok := x.(string)
	if ok {
		fmt.Printf("类型为string, 值: %v\n", v)
	} else {
		fmt.Println("类型断言失败")
	}
}

在这里插入图片描述

2、定义一个方法,可以传入任意类型,然后根据不同的类型实现不同的功能。
下面实现两种方式:if-else和switch-case。

package main

import "fmt"

func MyPrint1(x interface{}) {
	if _, ok := x.(string); ok {
		fmt.Println("string类型")
	} else if _, ok := x.(int); ok {
		fmt.Println("int类型")
	} else if _, ok := x.(bool); ok {
		fmt.Println("bool类型")
	}
}

func MyPrint2(x interface{}) {
	switch x.(type) {
	case string:
		fmt.Println("string类型")
	case int:
		fmt.Println("int类型")
	case bool:
		fmt.Println("bool类型")
	default:
		fmt.Println("传入错误...")
	}
}

func main() {
	MyPrint1("你好golang")
	MyPrint2(12)
}

在这里插入图片描述
注意:x.(type)只能结合switch语句使用。


针对前面的实现的Computer,我们判断传入的类型然后实现对应的方法。有点类似于C++中的多肽。

package main

import "fmt"

type Usber interface {
	start()
	stop()
}

type Phone struct {
	Name string
}

func (p Phone) start() {
	fmt.Printf("%v启动\n", p.Name)
}

func (p Phone) stop() {
	fmt.Printf("%v关闭\n", p.Name)
}

type Camera struct {
	Name string
}

func (c Camera) start() {
	fmt.Printf("%v启动\n", c.Name)
}

func (c Camera) stop() {
	fmt.Printf("%v关闭\n", c.Name)
}

type Computer struct {
}

func (c Computer) work(u Usber) {
	if _, ok := u.(Phone); ok {
		u.start()
	} else if _, ok := u.(Camera); ok {
		u.stop()
	}
}
func main() {
	p := Phone{
		"小米手机",
	}
	c := Camera{
		"哈苏x2d",
	}
	com := Computer{}
	com.work(p)
	com.work(c)
}

在work函数中我们实现类型断言,如果是Phone类型就执行start函数,如果是Camera类型就执行stop函数。


2.4、结构体值接收者和指针接收者实现接口的区别

package main

import "fmt"

type Usber interface {
	start()
	stop()
}

type Phone struct {
	Name string
}

func (p Phone) start() {
	fmt.Printf("%v启动!\n", p.Name)
}

func (p Phone) stop() {
	fmt.Printf("%v关闭!\n", p.Name)
}

func main() {
	var u Usber
	var p1 = Phone{
		"小米手机",
	}
	u = p1
	u.start()
	u.stop()

	var p2 = &Phone{
		"苹果手机",
	}
	u = p2
	u.start()
	u.stop()
}

在这里插入图片描述
根据现象,如果结构体中的方法是值接受者,那么实例化后的结构体值类型和指针类型都可以赋值给接口变量。

将start和stop方法改为指针接受者类型*Phone,那么结构体值类型就无法复制给接口变量了,此时只有指针类型可以赋值给接口变量。

另外如果要修改结构体内的成员变量,必须采用指针接受者的方式,如下:

package main

import "fmt"

type Animaler interface {
	setName(name string)
	getName() string
}

type Dog struct {
	Name string
}

func (d *Dog) setName(name string) {
	d.Name = name
}

func (d Dog) getName() string {
	return d.Name
}

func main() {
	d := &Dog{
		Name: "小柴",
	}
	var a Animaler
	a = d
	fmt.Println(a.getName())
	a.setName("小奇")
	fmt.Println(a.getName())
}

在这里插入图片描述


2.5、一个结构体实现多个接口

package main

import "fmt"

type Animaler1 interface {
	getName() string
}

type Animaler2 interface {
	setName(name string)
}

type Dog struct {
	Name string
}

func (d Dog) getName() string {
	return d.Name
}

func (d *Dog) setName(name string) {
	d.Name = name
}

func main() {
	d := &Dog{
		Name: "小柴",
	}
	var a1 Animaler1
	var a2 Animaler2
	a1 = d
	a2 = d
	fmt.Println(a1.getName())
	a2.setName("小奇")
	fmt.Println(a1.getName())
}

在这里插入图片描述


2.6、接口嵌套

package main

import "fmt"

type Ainterface interface {
	SetName(name string)
}

type Binterface interface {
	GetName() string
}

type Animaler interface {
	Ainterface
	Binterface
}

type Dog struct {
	Name string
}

func (d Dog) GetName() string {
	return d.Name
}

func (d *Dog) SetName(name string) {
	d.Name = name
}

func main() {
	d := &Dog{
		Name: "小柴",
	}
	var a Animaler
	a = d
	fmt.Println(a.GetName())
	a.SetName("小奇")
	fmt.Println(a.GetName())
}

在这里插入图片描述


2.7、空接口和类型断言使用细节

下面的代码有没有问题?

package main

import "fmt"

type Address struct {
	Name  string
	Phone string
}

func main() {
	var m = make(map[string]interface{})
	m["username"] = "张三"
	m["age"] = 20
	m["hobby"] = []string{"跑步", "爬山", "写代码"}
	fmt.Println(m["username"])
	fmt.Println(m["age"])
	fmt.Println(m["hobby"])
	fmt.Println(m["hobby"][0])
}

在这里插入图片描述
可以看到报错了,不支持这么访问。

再来看另一种情况:

m["address"] = Address{
	Name:  "李四",
	Phone: "13912344321",
}
fmt.Println(m["address"])
fmt.Println(m["address"].Name)

在这里插入图片描述
这样也是不支持的。

解决办法,实现类型断言接受后再访问:

package main

import "fmt"

type Address struct {
	Name  string
	Phone string
}

func main() {
	var m = make(map[string]interface{})
	m["username"] = "张三"
	m["age"] = 20
	m["hobby"] = []string{"跑步", "爬山", "写代码"}
	fmt.Println(m["username"])
	fmt.Println(m["age"])
	fmt.Println(m["hobby"])
	hobby, _ := m["hobby"].([]string)
	fmt.Println(hobby[0], hobby[1], hobby[2])
	m["address"] = Address{
		Name:  "李四",
		Phone: "13912344321",
	}
	fmt.Println(m["address"])
	address, _ := m["address"].(Address)
	fmt.Println(address.Name, address.Phone)
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值