# day05 输入输出fmt、接口interface

# 内容回顾

自定义类型

type Myint int

type f func()int

函数签名:函数需要的参数和返回值

类型别名

  • byte和rune 都是自带的类型别名
  • type NewInt = int32

自定义类型和类型别名的区别?

  • 类型别名编译后就会被替换成原来的类型,只是为了在开发阶段形象一点。

结构体

  • 定义方式

    type Student struct{
      ID int 
      Name string
    }
    
  • 结构体初始化

  • 结构体的内存布局

    • 结构体占用的内存是连续的。
    • 结构体的大小是由结构体的字段决定的
    • 结构体的内存对齐
  • 空结构体

    • 空结构体不占空间
  • 结构体匿名字段

  • 结构体的嵌套

  • 匿名结构体

方法

  • 定义方式

    func (s Student) Dream()string{
      // ...
    }
    
  • 值接收者和指针接收者

结构体tag

json序列化

  • json.Marshal
  • json.Unmarshal

# 课后练习

  1. 使用“面向对象”的思维方式编写一个学生信息管理系统。
    1. 学生有id、姓名、年龄、分数等信息
    2. 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能

完整代码在课上代码里。

  1. 下面代码的执行结果是啥?

    type student struct {
    	name string
    	age  int
    }
    
    func main() {
    	m := make(map[string]*student)
    	stus := []student{
    		{name: "小王子", age: 18},
    		{name: "linda", age: 23},
    		{name: "大王八", age: 9000},
    	}
    
    	for _, stu := range stus {
    		m[stu.name] = &stu
    	}
    	for k, v := range m {
    		fmt.Println(k, "=>", v.name)
    	}
    }
    

# 控制台 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:
	// ....
}
上次更新: 9/10/2022, 5:57:39 PM