GO-接口
1. 接口
在Go语言中接口(interface)是一种类型,一种抽象的类型。
interface是一组method的集合,接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
接口(interface)是一种类型
接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。
接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。
1.2 接口定义
Go语言提倡面向接口编程。
每个接口类型由数个方法组成。接口的形式代码如下:
type 接口类型名 interface{ 方法名1( 参数列表1 ) 返回值列表1 方法名2( 参数列表2 ) 返回值列表2 … }
对各个部分的说明:
- 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
- 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略
type Writer interface { //接口名和方法首字母大写,意味着可以被其他包访问 Write([]byte)string }
1.3 接口实现条件
如果一个任意类型 T 的方法集为一个接口类型的方法集的超集,则我们说类型 T 实现了此接口类型。
T 可以是一个非接口类型,也可以是一个接口类型。
实现关系在Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 Go编译器将自动在需要的时候检查两个类型之间的实现关系。
接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。
接口的实现需要遵循两条规则才能让接口可用:
- 接口的方法与实现接口的类型方法格式一致在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。
// 定义一个数据写入器 type DataWriter interface { Write(interface{}) error } // 定义文件结构,用于实现DataWriter type file struct { } // 实现DataWriter接口的WriteData方法 func (f *file) Write(b interface{}) error { return fmt.Sprintf("writer:", b) } func main() { // 实例化file f := new(file) // 声明一个DataWriter的接口 var write DataWriter // 将接口赋值f,也就是*file类型 write = f // 使用DataWriter接口进行数据写入 write.Write("hhhhhhhh") }
- 当类型无法实现接口时,编译器会报错:
-
- 函数名不一致导致的报错
- 实现接口的方法签名不一致导致的报错
- 接口中所有方法均被实现当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。
// 定义一个数据写入器 type DataWriter interface { Write(interface{}) error //上述代码中新增一个方法 Content() bool }
运行结果
.\main.go:28:10: cannot use f (variable of type *file) as DataWriter value in assignment: *file does not implement DataWriter (missing method Content)
Go语言的接口实现是隐式的,无须让实现接口的类型写出实现了哪些接口。
这个设计被称为非侵入式设计。
1.4 类型与接口的关系
在Go语言中类型和接口之间有一对多和多对一的关系
一个类型可以实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
例如,狗可以叫,也可以动。
我们就分别定义Sayer接口和Mover接口,如下:
type Sayer interface { Say() } type Mover interface { Move() } type Dog struct { name string } // dog实现say和move接口 func (d Dog) Say() { fmt.Println(d.name, " saying......") } func (d Dog) Move() { fmt.Println(d.name, "moving ......") } func main() { var x Sayer var y Mover var dog = Dog{"wangwang"} x = dog y = dog x.Say() //wangwang saying...... y.Move() //wangwang moving ...... }
多个类型实现同一接口
type Mover interface { Move() } type Dog struct { name string } type Car struct { name string } //dog 和 car都实现mover接口 func (d Dog) Move() { fmt.Println(d.name, "moving,....") } func (c Car) Move() { fmt.Println(c.name, "moving .....") } func main() { var d = Dog{"旺财"} var c = Car{"小米"} var move Mover move = d move.Move() //旺财 moving,.... move = c move.Move() //小米 moving ..... }
接口嵌套
接口与接口间可以通过嵌套创造出新的接口
// Sayer 接口 type Sayer interface { say() } // Mover 接口 type Mover interface { move() } // 接口嵌套 type animal interface { Sayer Mover }
嵌套得到的接口的使用与普通接口一样,这里我们让cat实现animal接口:
type cat struct { name string } func (c cat) say() { fmt.Println("喵喵喵") } func (c cat) move() { fmt.Println("猫会动") } func main() { var x animal x = cat{name: "花花"} x.move() x.say() }
1.5 空接口
空接口是指没有定义任何方法的接口。
因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() { var x interface{} var i = 100 x = i fmt.Println(x) //100 var name = "hhhhh" x = name fmt.Println(x) //hhhhh }
1.5.1 空接口的应用
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
func show(a interface{}) { fmt.Println(a) } func main() { //空接口作为函数参数 show("空接口传参") //空接口传参 }
空接口作为map的值
使用空接口实现可以保存任意值的字典。
func main() { var student = make(map[string]interface{}, 3) student["小明"] = 100 student["小红"] = "hahah" student["小高"] = false fmt.Printf("%+v", student) //map[小明:100 小红:hahah 小高:false] }
1.5.2 类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
接口值
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。
这两部分分别称为接口的动态类型和动态值。
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
1
其中:
- x:表示类型为interface{}的变量
- T:表示断言x可能是的类型。
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
func main() { var student = make(map[string]interface{}, 3) student["小明"] = 100 student["小红"] = "hahah" student["小高"] = false fmt.Printf("%+v\n", student) //map[小明:100 小红:hahah 小高:false] _, ok := student["小明"].(bool) if ok != true { fmt.Println("student[\"小明\"]不是bool") } }
2. I/O操作
I/O操作也叫输入输出操作。其中I是指Input,O是指Output,用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。
Golang 标准库对 IO 的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。
io包中提供I/O原始操作的一系列接口。
它主要包装了一些已有的实现,如 os 包中的那些,并将这些抽象成为实用性的功能和一些其他相关的接口。
由于这些接口和原始的操作以不同的实现包装了低级操作,客户不应假定它们对于并行执行是安全的。
io库比较常用的接口有三个,分别是Reader,Writer和Closer。
2.1 Reader
Reader接口的定义,Read()方法用于读取数据。
type Reader interface { Read(p []byte) (n int, err error) }
io.Reader 表示一个读取器,它将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。
- 对于要用作读取器的类型,它必须实现 io.Reader 接口的唯一一个方法 Read(p []byte)。
- 换句话说,只要实现了 Read(p []byte) ,那它就是一个读取器。
- Read() 方法有两个返回值,一个是读取到的字节数,一个是发生错误时的错误。
通过 string.NewReader(string) 创建一个字符串读取器,然后流式地按字节读取:
func main() { reader := strings.NewReader("this is a reader") // 每次读取4个字节 p := make([]byte, 4) for { n, err := reader.Read(p) if err != nil { if err == io.EOF { log.Println("读完了") break } log.Fatalln("read error", err) os.Exit(2) } log.Println("读取到的字节数:", n) } }
- 最后一次返回的 n 值有可能小于缓冲区大小。
- io.EOF 来表示输入流已经读取到头
2.1.1 文件操作相关API
-
func Create(name string) (file *File, err Error)
1
-
- 根据提供的文件名创建新的文件,返回一个文件对象,默认权限是0666
-
func NewFile(fd uintptr, name string) *File
1
-
- 根据文件描述符创建相应的文件,返回一个文件对象
-
func Open(name string) (file *File, err Error)
1
-
- 只读方式打开一个名称为name的文件
-
func OpenFile(name string, flag int, perm uint32) (file *File, err Error)
1
-
- 打开名称为name的文件,flag是打开的方式,只读、读写等,perm是权限
-
func (file *File) Write(b []byte) (n int, err Error)
1
-
- 写入byte类型的信息到文件
-
func (file *File) WriteAt(b []byte, off int64) (n int, err Error)
1
-
- 在指定位置开始写入byte类型的信息
-
func (file *File) WriteString(s string) (ret int, err Error)
1
-
- 写入string信息到文件
-
func (file *File) Read(b []byte) (n int, err Error)
1
-
- 读取数据到b中
-
func (file *File) ReadAt(b []byte, off int64) (n int, err Error)
1
-
- 从off开始读取数据到b中
-
func Remove(name string) Error
1
-
- 删除文件名为name的文件
2.1.2 读文件
type Closer interface { Close() error }
os.Open()函数能够打开一个文件,返回一个*File和一个err。对得到的文件实例调用Close()方法能够关闭文件。
文件读取可以用file.Read(),读到文件末尾会返回io.EOF的错误
func main() { // 打开文件 file, err := os.Open("C:\\Users\\Administrator\\Desktop\\新建 文本文档 (2).txt") if err != nil { log.Println("打开失败") } defer file.Close() // 定义接收文件读取的字节数组 buff := make([]byte, 128) var content []byte for { _, err := file.Read(buff) if err == io.EOF { log.Println("读完了") break } if err != nil { log.Println("读取失败:", err) return } } content = append(content, buff...) fmt.Sprintln(content) }
Writer
type Writer interface { //Write() 方法有两个返回值,一个是写入到目标资源的字节数,一个是发生错误时的错误。 Write(p []byte) (n int, err error) }
- io.Writer 表示一个写入器,它从缓冲区读取数据,并将数据写入目标资源。
- 对于要用作编写器的类型,必须实现 io.Writer 接口的唯一一个方法 Write(p []byte)
- 同样,只要实现了 Write(p []byte) ,那它就是一个编写器。
func main() { // 打开文件 file, err := os.Open("C:\\Users\\Administrator\\Desktop\\新建 文本文档 (2).txt") if err != nil { log.Println("打开失败") } defer file.Close() // 定义接收文件读取的字节数组 buff := make([]byte, 128) var content []byte for { _, err := file.Read(buff[:]) if err == io.EOF { log.Println("读完了") break } if err != nil { log.Println("读取失败:", err) return } } content = append(content, buff...) fmt.Println(string(content)) }
2.2 Writer
type Writer interface { //Write() 方法有两个返回值,一个是写入到目标资源的字节数,一个是发生错误时的错误。 Write(p []byte) (n int, err error) }
- io.Writer 表示一个写入器,它从缓冲区读取数据,并将数据写入目标资源。
- 对于要用作编写器的类型,必须实现 io.Writer 接口的唯一一个方法 Write(p []byte)
- 同样,只要实现了 Write(p []byte) ,那它就是一个编写器。
写文件:
func main() { file, err := os.Create("test.txt") if err != nil { log.Println("create error") } defer file.Close() b := make([]byte, 0) for i := 0; i
2.3 bufio
- bufio包实现了带缓冲区的读写,是对文件读写的封装
- bufio缓冲写数据
模式
含义
os.O_WRONLY
只写
os.O_CREATE
创建文件
os.O_RDONLY
只读
os.O_RDWR
读写
os.O_TRUNC
清空
os.O_APPEND
追加
bufio读写数据
func wr() { // 参数2:打开模式,所有模式d都在上面 // 参数3是权限控制 // w写 r读 x执行 w 2 r 4 x 1 //特殊权限位,拥有者位,同组用户位,其余用户位 file, err := os.OpenFile("./xxx.txt", os.O_CREATE|os.O_WRONLY, 0666) if err != nil { return } defer file.Close() // 获取writer对象 writer := bufio.NewWriter(file) for i := 0; i
2.5 实现一个cat命令
使用文件操作相关知识,模拟实现linux平台cat命令的功能。
-
-
-
-
-
-
-
-
-
-
-