# Day05 标准库fmt与接口
# 本节关键词
接口是一种类型,一种抽象的类型,方法的集合,关注能做什么
接口类型的实例变量,可以接收实现了该接口的结构体类型变量
接口可以互相嵌套,甚至可以嵌套到结构体内
空接口 var x interface{}
# 控制台 fmt.Print
Print
系列函数会将内容输出到系统的标准输出
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
# 文件 fmt.Fprint
Fprint
系列函数会将内容输出到一个io.Write
r`接口类型的变量w中,我们通常用这个函数往文件中写入内容
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
- 举个例子
func main() {
fmt.Fprint(os.Stdout, "向标准输出写入内容\n")
fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("打开文件出错,err:", err)
return
}
defer fileObj.Close() // defer 用法
name := "落魄山小龙王"
// 向打开的文件句柄中写入内容
fmt.Fprintf(fileObj, "往文件中写如信息:%s", name)
}
# 格式化 fmt.Sprint
Sprint
系列函数会把传入的数据生成并返回一个字符串
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string
# 错误 fmt.Errorf
e := errors.New("原始错误e")
w := fmt.Errorf("Wrap了一个错误%w", e)
# 输入 fmt.Scan
func Scan(a ...interface{}) (n int, err error)
- 举个例子
func main() {
var (
name string
age int8
married bool
)
fmt.Scan(&name, &age, &married)
fmt.Printf("扫描结果 name:%s age:%d married:%t \n", name, age, married)
}
# 接口 interface
- Go语言中接口
interface
是一种类型,一种抽象的类型。 - 相比于字符串、切片、结构体等关注"我是谁",接口类型更注重"我能做什么"
一个接口类型就是一组方法的集合,它规定了需要实现的所有方法
// 每个接口类型由任意个方法签名组成
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
// 举个例子,定义一个包含Write方法的Writer接口
type Writer interface{
Write([]byte) error
}
# 实现接口的条件
接口就是规定了一个需要实现的方法列表,Go语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口
// Singer 接口
type Singer interface{
Sing()
}
// Bird 结构体
type Bird struct{
}
// 实现接口 Singer 中的方法 Sing()
func (b Bird) Sing () {
}
# 接口类型变量
一个接口类型的变量能够存储所有实现了该接口的类型变量
var s Singer
b := Bird{}
s = b // s 可以存储 b
# 值接收者和指针接收者
// Mover 定义一个接口类型,实现Move可以是值接收者也可以是值接收者
type Mover interface {
Move()
}
type Dog struct{}
// 值接收者实现接口
func (d Dog) Move(){
fmt.Println("狗会动~")
}
var m Mover // 声明一个Mover类型的变量 m
var d1 = Dog{} // d1 是Dog类型
m = d1 // 可以将 d1 赋值给变量 m
m.Move()
var d2 = &Dog{} // d2 是Dog指针类型
m = d2 // 可以将 d2 赋值给变量 m
m.Move()
type Cat struct{}
// 指针接收者实现接口
func (c *Cat) Move(){
fmt.Println("猫会动~")
}
var m Mover
var c1 = &Cat{} // c1是*Cat类型
m = c1 // 可以将 c1 当成Mover类型
m.Move()
// m = c2 通不过编译
var c2 = Cat{}
m = c2
# 类型与接口的关系
- 一个类型实现多个接口
- 多种类型实现同一接口
# 接口组合
- 接口与接口之间可以通过互相嵌套形成新的接口类型
// src/io/io.go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter 是组合Reader接口和Writer接口形成的新接口类型
type ReadWriter interface {
Reader
Writer
}
# 空接口
- 空接口是指没有定义任何方法的接口类型,因此任何类型都可以视为实现了空接口
- 空接口类型的变量可以存储任意类型的值
type Any interface{}
- 空接口作为函数的参数,使用空接口实现可以接收任意类型的函数参数
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
- 空接口作为map的值,使用空接口实现可以保存任意值的字典
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "linda"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
# 接口值
由于接口类型的值可以是任意一个实现了该接口的类型值,所以接口值除了需要记录具体值之外,还需要记录这个值属于的类型。也就是说接口值由"类型"和"值"组成,鉴于这两部分会根据存入值的不同而发生变化,我们称之为接口的动态类型和动态值
- 使用
m == nil
来判断此时的接口值是否为空
# 类型断言
接口变量能存储任意类型的前提条件是,接口分为动态值和动态类型两部分
想要获取到原始的类型和值,可以使用类型断言
类型断言两种写法:
接口变量.(T)
var x interface{} // 只有接口类型才能使用断言,var x int = 100 就不行
x = 10
v, ok := x.(string) // ok=false v=""
x = "linda"
v, ok := x.(string) // ok=true v="linda"
switch x.(type)
var x interface{}
x = 100
switch v := x.(type){
case int:
// v = 100
case string:
// ....
}
# 课后作业
- 使用接口实现一个简单的日志库
- 可以往终端和文件输出日志
package main
import (
"fmt"
"io"
"os"
)
// 使用接口实现一个简单的日志库
// 可以往终端和文件输出日志
// log.Error()
// log.Warning()
// log.Info()
type Log struct {
Output io.Writer
}
func NewLog(output io.Writer) *Log {
return &Log{
Output: output,
}
}
func (l *Log) Error(s string) {
fmt.Fprintf(l.Output, "Error: %s\n", s)
}
func (l *Log) Warning(s string) {
fmt.Fprintf(l.Output, "Warning: %s\n", s)
}
func (l *Log) Info(s string) {
formatString := fmt.Sprintf("Info: %s", s)
fmt.Fprintln(l.Output, formatString)
}
func main() {
// logger := NewLog(os.Stdout)
f, err := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("文件打开错误")
return
}
logger := NewLog(f)
logger.Error("出错了")
logger.Warning("有告警")
logger.Info("启动了")
}