# 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.Writer`接口类型的变量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("启动了")
}

上次更新: 1/10/2023, 12:41:52 PM