【9】Go

Viedo Date:2023/10/14
Made By:BIRKHOFF 
Date:2024-06-24

【9-1-结构体及成员】

【9-1-结构体及成员】
=====================================================================
Go语言的结构体有点像面向对象语言中的“类”,但不完全是,Go语言也没打算真正实现面向对象范式。

结构体可以扩展方法,因为结构体是类型
	方法method,本质上就是function
	User.getName //类型的调用 人类的名字 不合适
	u1.getName //User具体
	
	
		
Go中,可以为新的类型扩展方法
	新的,可以基于原来的类型生成新的类型,最好不要在原来类型上直接修改

[定义]
使用type定义结构体,可以把结构体看做类型使用。必须指定结构体的字段(属性)名称和类型
type User struct {
	id int
	name,addr string //多个字段类型相同可以合并写
	score float32
}

User不过是个标识符,是一个指代罢了
真正的类型定义是struct{}的部分

[初始化]
package main

import (
	"fmt"
)

type User struct {
	id         int
	name, addr string //多个字段类型相同可以合并写
	score      float32
}

func main() {
	// 初始化
	//1 零值 很常用,经常用
	var u1 User // u1是User的实例
	//%v打印结构体
	fmt.Printf("%T %[1]v", u1) //main.User {0   0}

	u1.id = 1
	fmt.Printf("%T %[1]v", u1) //main.User {1   0}
	fmt.Println("------------------------------------\n")
	fmt.Println(u1)         //{1   0}
	fmt.Printf("%v\n", u1)  //{1   0}                                    默认缺省格式打印
	fmt.Printf("%+v\n", u1) //{id:1 name: addr: score:0}                 增强打印
	fmt.Printf("%#v\n", u1) //main.User{id:1, name:"", addr:"", score:0} 详细打印

	//2 字面量初始化,推荐
	u2 := User{}            //字段为零值
	fmt.Printf("%#v\n", u2) //main.User{id:0, name:"", addr:"", score:0}

	//3 字面量初始化,field:value为字段赋值
	u3 := User{id: 100}
	fmt.Printf("%#v\n", u3) //main.User{id:100, name:"", addr:"", score:0}

	u4 := User{
		id:    319,
		score: 99,
		addr:  "Shanghai",
		name:  "BIRKHOFF",
	} //名称对应无所谓顺序
	fmt.Printf("%#v\n", u4) //main.User{id:319, name:"BIRKHOFF", addr:"Shanghai", score:99}

	u5 := User{1220, "HEYE", "SHANGHAI", 59} //无字段名称必须按照顺序给出全部字段值
	fmt.Printf("%#v\n", u5)                  //main.User{id:1220, name:"HEYE", addr:"SHANGHAI", score:59}
}

[可见性]
	Go包的顶层代码中,首字母大写的标识符,跨package包可见(导出),否则只能本包内可见
	导出的结构体,package内外皆可见,同时,导出的结构体中的成员(属性、方法)要在包外也可见,则也需首字母大写

[访问]
	u5 := User{1220, "HEYE", "SHANGHAI", 59} //无字段名称必须按照顺序给出全部字段值
	fmt.Printf("%#v\n", u5)                  //main.User{id:1220, name:"HEYE", addr:"SHANGHAI", score:59}
	fmt.Println(u5.id, u5.name, u5.addr)     //1220 HEYE SHANGHAI

[修改]
	u5 := User{1220, "HEYE", "SHANGHAI", 59} //无字段名称必须按照顺序给出全部字段值
	u5.name = "HY"
	u5.score = 150
	fmt.Printf("%#v\n", u5) //main.User{id:1220, name:"HY", addr:"SHANGHAI", score:150}

[成员方法]
package main

import (
	"fmt"
)

type User struct {
	id         int
	name, addr string //多个字段类型相同可以合并写
	score      float32
}

// 定义一个普通的函数
func getName(u User) string {
	return fmt.Sprintf("%d : %s", u.id, u.name)
}

// 定义方法
// User 表示和User类型有关
// u称为receiver 表示和User的具体某个实例有关
func (u User) getName() string {
	return fmt.Sprintf("%d : %s", u.id, u.name)
}

func main() {
	u5 := User{1220, "HEYE", "SHANGHAI", 59}
	//调用普通函数方法
	fmt.Println(getName(u5)) //1220 : HEYE
	//调用方法
	fmt.Println(u5.getName()) //1220 : HEYE
}

-------写一个方法 自定义MyInt 自增1---------
package main

import "fmt"

type MyInt int

func (i MyInt) inc() int {
	t := int(i)
	t++
	return t
}

func main() {
	var j MyInt = 99
	fmt.Printf("%T %[1]v\n", j) //main.MyInt 99

	value := j.inc()                //
	fmt.Printf("%T %[1]v\n", value) //int 100
}

【9-2-结构体指针和值类型】

=====================================================================
指针
	p1 类型是Point,指代Point的某一个具体的实例
	p2:=&p1 类型是*Point,p2记录一个大整数是地址,通过地址找到p1这个标识符指向的实例
	p1.x 通过实例访问属性
	p2.x 通过指针找到实例访问属性x,,在其他语言中p2 -> x

package main

import "fmt"

type Point struct {
	x, y int
}

func main() {
	p1 := Point{4, 5}
	fmt.Printf("%T %+[1]v,%p\n", p1, &p1) //main.Point {x:4 y:5},0xc0000180a0

	p2 := &p1                           //*Point 指向 p1这个实例的指针
	fmt.Printf("%T %+[1]v,%[1]p\n", p2) //*main.Point &{x:4 y:5},0xc0000180a0

	p3 := new(Point)                      //构建一个Point零值的实例,返回该实例的指针给p3
	fmt.Printf("%T %+[1]v,%p\n", p3, &p3) //*main.Point &{x:0 y:0},0xc0000ca020

	//指针用途
	p1.x = 100
	fmt.Printf("%T %+[1]v,%p\n", p1, &p1) //main.Point {x:100 y:5},0xc0000180a0 修改地址不变
	p2.x = 200
	fmt.Printf("%T %+[1]v,%p\n", p1, &p1) //main.Point {x:200 y:5},0xc0000a6070
	fmt.Printf("%T %+[1]v,%[1]p\n", p2)   //*main.Point &{x:200 y:5},0xc0000a6070
}

[多次值copy]
package main

import "fmt"

type Point struct {
	x, y int
}

func testCopy(t Point) Point {
	fmt.Printf("t %T %+[1]v,%p\n", t, &t) //*main.Point &{x:4 y:5},0xc0000180a0
	return t
}

func main() {
	p1 := Point{4, 5}
	fmt.Printf("%T %+[1]v,%p\n", p1, &p1) //main.Point {x:4 y:5},0xc0000180a0

	p2 := p1                              //完全副本
	fmt.Printf("%T %+[1]v,%p\n", p2, &p2) //main.Point {x:4 y:5},0xc0000180f0

	p3 := &p1
	fmt.Printf("%T %+[1]v,%p\n", p3, p3) //*main.Point &{x:4 y:5},0xc0000180a0
	fmt.Println("-----------------------------------------------")

	p4 := testCopy(p1)
	fmt.Printf("p1 %T %+[1]v,%p\n", p1, &p1) 
	fmt.Printf("p4 %T %+[1]v,%p\n", p4, &p4) 
	//会有多次copy
	// t main.Point {x:4 y:5},0xc000018150
	// p1 main.Point {x:4 y:5},0xc0000180a0
	// p4 main.Point {x:4 y:5},0xc000018140
}

[减少值copy 使用指针传参]
package main

import "fmt"

type Point struct {
	x, y int
}

func testCopy(t Point) *Point {
	fmt.Printf("t %T %+[1]v,%p\n", t, &t) //*main.Point &{x:4 y:5},0xc0000180a0
	return &t
}

func main() {
	p1 := Point{4, 5}
	fmt.Printf("%T %+[1]v,%p\n", p1, &p1) //main.Point {x:4 y:5},0xc0000180a0

	p4 := testCopy(p1)
	fmt.Printf("p1 %T %+[1]v,%p\n", p1, &p1)
	fmt.Printf("p4 %T %+[1]v,%p\n", p4, p4)
	//所以返回指针减少copy 返回的值和p4是一个地址
	// t main.Point {x:4 y:5},0xc0000180f0
	// p1 main.Point {x:4 y:5},0xc0000180a0
	// p4 *main.Point &{x:4 y:5},0xc0000180f0
}

【9-3-构造器】

=====================================================================
Go语言并没有从语言层面为结构体提供什么构造器,但是有时候可以通过一个函数为结构体初始化提供属性值,从而方便得到以恶结构体实例。习惯上,函数命名为NewXxx的形式

	严格来说,Go语言结构体并没有构造函数这样的语法不成文的约定
	可以没有,不限制
	因为某些结构体数据成员很多,而且初始化数据成员很麻烦,甚至初始化初使用者不知道z的方法给使用者提供方便
	Newxxx成为了结构体xxx的构造函数了
	函数,普通的函数,不是方法(因为没有receiver)
	Newxxx函数,没有实例而创造出新的实例

	重载overload
		Go没有重载函数同名
		形参个数、类型不一样支持重载
			func fl(x,y int) f1(4,5)
			func fl(x int)   f1(4)

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

func NewAnimal(name string, age int) Animal {
	a := Animal{name, age}
	fmt.Printf("NewAnimal : %+v,%p\n", a, &a) //NewAnimal : {name:BIRKHOFF age:30},0xc000008090
	return a
}

func main() {
	a := NewAnimal("BIRKHOFF", 30)
	//发现2个地址不一样 是副本 可以使用指针减少副本
	fmt.Printf("Main : %+v,%p\n", a, &a) //Main : {name:BIRKHOFF age:30},0xc000008078
}

#上例中,NewAnimal的返回值使用了值拷贝,增加了内存开销,习惯上返回值会采用指针类型,避免实例的拷贝。		
func NewAnimal(name string, age int) *Animal {
	a := Animal{name, age}
	fmt.Printf("NewAnimal : %+v,%p\n", a, &a) //NewAnimal : {name:BIRKHOFF age:30},0xc000118060
	return &a
}

【9-4-父子结构体】

=====================================================================
[匿名结构体]
type定义新类型 
	type 类型名 类型本体
	type 类型名	类型 基于类型定义新类型
	type 类型名=类型 类型的别名
	
var 定义新变量,变量有类型
	var 变量名 类型
	
	//得到是一个结构体类型的变量
	var pd struct {
		x, y int
	}
*匿名结构体,只是为了快速方便地得到一个结构体实例,而不是使用结构体创建N个实例。

package main

import "fmt"

//定义新类型
type Point struct {
	x, y int
}

func main() {
	//类型没有给出名称 这种定义叫做 匿名结构体
	//得到是一个结构体类型的变量
	var pd struct {
		x, y int
	}
	var i int
	var p1 Point
	// var p2 pd 错误 2个变量错误
	fmt.Printf("%T\n", p1) //main.Point
	fmt.Printf("%T\n", i)  //int
	fmt.Printf("%T\n", pd) //struct { x int; y int } 没有名字

	pd.x = 20
	pd.y = 30
	fmt.Printf("%#v\n", pd) //struct { x int; y int } 没有名字

	var pd2 = struct { //直接使用 要用 =
		x, y int
	}{8, 8}
	fmt.Printf("%#v\n", pd2) //struct { x int; y int }{x:8, y:8}
}


[匿名成员]
	成员属性,没有名字
	不推荐使用
	type Point struct {
		x int
		int //只有类型没有属性名的成员,匿名成员
		string
		bool
	}

package main

import "fmt"

//匿名成员
type Point struct {
	x   int
	int //只有类型没有属性名的成员,匿名成员
	string
	bool
}

func main() {
	p1 := new(Point)
	fmt.Printf("%T %+[1]v\n", p1)                 //*main.Point &{x:0 int:0 string: bool:false}
	fmt.Println(p1.x, p1.int, p1.string, p1.bool) //0 0  false
	p1.string = "xks"
	p1.bool = true
	fmt.Printf("%T %+[1]v\n", p1) //*main.Point &{x:0 int:0 string:xks bool:true}
}

[父子结构体]
	使用结构体嵌套实现类似面向对象父类子类继承(派生)的效果
	子结构体使用匿名成员能简化调用父结构体成员

EG:
动物类包括猫类,猫属于猫类,猫也属于动物类,某动物一定是动物类,但不能说某动物一定是猫类将上例中的Animal结构体
使用匿名成员的方式,嵌入到Cat结构体中,看看效果

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

type Cat struct { //子结构体 直接拥有了父结构体的属性
	//name string
	//age int
	Animal //匿名成员,父结构体,Go提供语法糖,结构体嵌套
	color  string
}

func main() {
	var c = new(Cat)             //c是Cat的实例
	fmt.Printf("%T %+[1]v\n", c) //*main.Cat &{Animal:{name: age:0} color:}
	c.name = "XKS"               //=c.Animal.name
	c.age = 30                   //=c.Animal.age
	c.color = "blue"             //=c.Animal.color
	fmt.Printf("%T %+[1]v\n", c) //*main.Cat &{Animal:{name:XKS age:30} color:blue}
}
*子结构体,为什么定义一个子类cat?父类并没有包含cat的所有特征,也没有包括Dog类所有特征

【9-5-receiver和深浅拷贝】

=====================================================================
[指针类型receiver]
Go语言中,可以为任意类型包括结构体增加方法,形式是 func Receiver 方法名 签名{函数体}
	这个receiver类似其他语言中的this或self.
	receiver必须是一个类型T实例或者类型T的指针,T不能是指针或接口。

[getter、setter]
package main

import "fmt"

type Point struct {
	x, y int
}

func NewPoint(x, y int) *Point { //构造函数不是方法 是一个普通函数
	return &Point{x, y}
}

/*getter*/
// 2种 receiver 实例或指针
//相当于实例.GetX() 指针.GetX() 调用该方法
func (p Point) GetX() int { //通过方法控制读或写 只是这里的receiver是实例
	//receiver是实例,通过实例调用有值拷贝,通过指针调用同样会值拷贝
	fmt.Printf("instance.GetX(), %p,%+v\n", &p, p)
	return p.x
}

func (p *Point) GetY() int { //通过方法控制读或写 只是这里的receiver是实例
	//receiver是指针,通过实例访问会取实例的指针,或通过指针访问
	fmt.Printf("pointer.GetY(), %p,%+v\n", p, p)
	return p.y
}

/*setter*/
// 2种 receiver 实例或指针
func (p Point) SetX(v int) {
	fmt.Printf("instance.SetX(), %p,%+v\n", &p, p)
	p.x = v
	fmt.Printf("instance.SetX(), %p,%+v\n", &p, p)

}

func (p *Point) SetY(v int) {
	fmt.Printf("pointer.SetY(), %p,%+v\n", p, p)
	p.y = v
	fmt.Printf("pointer.SetY(), %p,%+v\n", p, p)
}

func main() {
	// var p1 = NewPoint(4, 5) //p1 是指针
	var p1 = Point{4, 5}

	fmt.Printf("%T %+[1]v %p , %d %d\n", p1, &p1, p1.x, p1.y) //4
	fmt.Println("-------------------------")                  //4
	// fmt.Println(p1.GetX())                              //4
	fmt.Println("-------------------------") //4

	fmt.Println(p1.GetX(), (&p1).GetX())
	// instance.GetX(), 0xc0000180f0,{x:4 y:5} 2个值不同发生了值拷贝
	// instance.GetX(), 0xc000018120,{x:4 y:5}
	// 4 4
	fmt.Println("-------------------------") //4
	fmt.Println(p1.GetY(), (&p1).GetY())
	// pointer.GetY(), 0xc0000180a0,&{x:4 y:5} 2个地址相同,同一个地址
	// pointer.GetY(), 0xc0000180a0,&{x:4 y:5}
	// 5 5
	fmt.Println("-------------------------") //4

	//Setter
	p1.SetX(11)
	fmt.Printf("SetX() %T %+[1]v %p , %d %d\n", p1, &p1, p1.x, p1.y)
	(&p1).SetX(11)
	fmt.Printf("SetX() %T %+[1]v %p , %d %d\n", p1, &p1, p1.x, p1.y)
	//发现 通过receiver是实例 没有修改原来 因为修改了副本的数据 原本的数据没有修改
	// instance.SetX(), 0xc000018190,{x:4 y:5}
	// instance.SetX(), 0xc000018190,{x:11 y:5}
	// SetX() main.Point {x:4 y:5} 0xc0000180a0 , 4 5
	// instance.SetX(), 0xc000018200,{x:4 y:5}
	// instance.SetX(), 0xc000018200,{x:11 y:5}
	// SetX() main.Point {x:4 y:5} 0xc0000180a0 , 4 5
	fmt.Println("-------------------------") //4
	p1.SetY(22)
	fmt.Printf("SetY() %T %+[1]v %p , %d %d\n", p1, &p1, p1.x, p1.y)
	(&p1).SetY(222)
	fmt.Printf("SetY() %T %+[1]v %p , %d %d\n", p1, &p1, p1.x, p1.y)
	//receiver 作为指针返回 是同一个
	// pointer.SetY(), 0xc0000180a0,&{x:4 y:5}
	// pointer.SetY(), 0xc0000180a0,&{x:4 y:22}
	// SetY() main.Point {x:4 y:22} 0xc0000180a0 , 4 22

	// pointer.SetY(), 0xc0000180a0,&{x:4 y:22}
	// pointer.SetY(), 0xc0000180a0,&{x:4 y:222}
	// SetY() main.Point {x:4 y:222} 0xc0000180a0 , 4 222
}


[总结]
接收器receiver可以是类型T 也可以是 指针*T,定义的方法有什么区别?从上面可以看出 
	receiver是实例,方法内操作的是实例的副本,相当于实例的拷贝
		实例访问,方法内使用时该实例的副本
		指针访问,方法内使用的是指针指向的实例的副本
	reciver是指针,方法内操作的是实例的指针的副本,相当于实例的指针的拷贝
		实例访问,方法内使用的是该实例的指针
		指针访问,方法内使用的是该指针

**非常明显,如果是非指针接收器方法调用有值拷贝,操作的是副本,而指针接收器方法调用操作的是同一个内存的同一个实例。
	如果是操作大内存对象时,且操作同一个实例时,一定要采用指针接收器的方法。


[深浅拷贝]
shadow copy
	影子拷贝,也叫浅拷贝。遇到引用类型数据,仅仅复制一个引用而已
deep copy
	深拷贝,往往会递归复制一定深度

	注意,深浅拷贝说的是拷贝过程中是否发生递归拷贝,也就是说如果某个值是一个地址,是只复制这个地址,还是复制地址指向的内容。
	值拷贝是深拷贝,地址拷贝是浅拷贝,这种说法是错误的。因为地址拷贝只是拷贝了地址,因此本质上来讲也是值拷贝。

	Go语言中,引用类型实际上拷贝的是标头值,这也是值拷贝,并没有通过标头值中对底层数据结构的指针指向的内容进行复制,这就是浅拷贝。
	非引用类型的复制就是值拷贝,也就是再造一个副本,这也是浅拷贝。因为你不能说对一个整数值在内存中复制出一个副本,就是深的拷贝。
	像整数类型这样的基本类型就是一个单独的值,没法深入拷贝,根本没法去讲深入的事儿。
	简单讲,大家可以用拷贝文件是否对软链接跟进来理解。直接复制软链接就是浅拷贝,钻进软链接里面复制其内容就是深拷贝。
	*复杂数据结构,往往会有嵌套,有时嵌套很深,如果都采用深拷贝,那代价很高,
		所以,浅拷贝才是语言普遍采用的方案。

【9-6-接口概念】

=====================================================================
接口interface,和Java类似,是一组行为规范的集合,就是定义一组未实现的函数声明。谁使用接口就是参照接口的方法定义实现它们
type 接口名 interface{
		方法1 (参数列表1) 返回值列表1
		方法2 (参数列表2) 返回值列表2
		...
}
接口命名习惯在接口名后面加上er后缀
参数列表、返回值列表参数名可以不写
如果要在包外使用接口,接口名应该首字母大写,方法要在包外使用,方法名首字母也要大写
接口中的方法应该设计合理,不要太多
---
接口名,习惯以er结尾

一套方法的声明的组合,都不实现,谁用谁实现
	函数签名,形参,返回值都可以只有类型
	实现接口
		*** 对于某一个接口的方法全部实现,一个不落

可见性
Go语言种,建议,实现小接口
	接口的方法尽量的少
	用组合来实现大接口

约束方法
	方法是动作、操作、定义在结构体
	约束、结构体的方法成员
	结构体实现了某接口,遵守该接口的约定
		实现了该接口所有的方法
		
Go采用 非侵入设计 相对Java来说的
	Java
		class Xxx implements 接口1,接口2
	Go悄悄的实现

EG:
package main

import "fmt"

/*	如果一个结构体实现了一个接口声明的所有方法,就说结构体实现了该接口。
	一个结构体可以实现多个不同接口。
*/

//Person实现了Sporter接口,下面函数缺一不可
type Sporter interface {
	run()
	jump()
}

type Person struct {
	name string
	age  int
}

func (p *Person) run() {
	fmt.Println("run -----------")
}
func (p *Person) jump() {
	fmt.Println("jump -----------")
}
func (p *Person) swim() {
	fmt.Println("swimming ~~~~~~~~~")
}

/*Users 实现了Sporter接口*/
type User struct {
	id    int
	name  string
	score float32
}

func (p *User) run() {
	fmt.Println("User run -----------")
}
func (p *User) jump() {
	fmt.Println("User jump -----------")
}
func (p *User) walk() {
	fmt.Println("User swimming ~~~~~~~~~")
}

func main() {
	p1 := new(Person)
	p1.run()
	p1.jump()

	var s Sporter = p1 //var s Sporter = Sporter(p1)
	//p1 既是Person类型 也是 Sporter类型
	fmt.Printf("p1 = %T,s = %T\n", p1, s) //p1 = *main.Person,s = *main.Person
	s.jump()
	s.run()
	// s.swim() 不能调用这个方法 因为接口里没有这个方法
	// run -----------
	// jump -----------

	//s 既是User类型 也是 Sporter类型
	u := new(User)
	s = u                                //var s Sporter = Sporter(p1)
	fmt.Printf("p1 = %T,s = %T\n", s, u) //p1 = *main.User,s = *main.User
	s.jump()
	s.run()
	// User jump -----------
	// User run -----------
}

【9-7-接口嵌套】

=====================================================================
type Reader interface{
	Read(p []byte) (n int,err errot)
}

type Closer interface{
	Close() error
}

type ReadCloser interface{
	Reader
	Closer
}
ReadCloser接口是Reader、Closer接口组合而成,也就是说它拥有Read,Close方法声明

type runner interface{
	run()
}
type jumper interface{
	jump()
}
type Sporter interface{
	runner
	jumper
}

* 一般将接口分开定义 然后组合成一个大接口

【9-8-空接口和接口类型断言】

=====================================================================
[空接口]
	空接口,实际上是空接口类型,写作
		interface {}。
	为了方便使用,Go语言为它定义一个别名any类型
		type any =interface{}
	空接口,没有任何方法声明,因此,任何类型都无需显式实现空接口的方法,因为任何类型都满足这个空接口的要求。
	任何类型的值都可以看做是空接口类型。
、
EG:
package main

import "fmt"

func main() {
	var a = 500
	var b interface{}
	b = a
	fmt.Printf("%v,%[1]T ; %v,%[2]T\n", a, b) //500,int ; 500,int

	var c = "abcd"
	b = c
	fmt.Printf("%v,%[1]T ; %v,%[2]T\n", b, c) //abcd,string ; abcd,string

	//	b = []any{100, "xyz", [3]int{1, 2, 3}}
	//  b = []interface{}{100, "xyz", [3]int{1, 2, 3}}
	// type any = interface{}
	b = []any{100, "xyz", [3]int{1, 2, 3}}
	fmt.Printf("%v,%[1]T\n", b) //[100 xyz [1 2 3]],[]interface {}
}

[接口类型断言]
接口类型断言(Type Assertions)可以将接口转换成另外一种接口,也可以将接口转换成另外的类型。接口类型断言格式 
t := i.(T)
	i代表接口变量
	T表示转换目标类型
	t代表转换后的变量
		断言失败,也就是说i没有实现T接口的方法则panic
	t,ok:=i.(T),则断言失败不panic,通过ok是true或false判断i是否是T类型接口

EG:
package main

import "fmt"

func main() {
	var b interface{} = 500
	// fmt.Println(b.(string)) //panic转换失败
	//panic: interface conversion: interface {} is int, not string
	if s, ok := b.(string); ok {
		fmt.Println("断言成功,值是", s)
	} else {
		fmt.Println("断言失败") //断言失败
	}
}

[type-switch]
package main

import "fmt"

func main() {
	var i interface{} = 500
	switch v := i.(type) {
	case nil:
		fmt.Println("nil")
	case string:
		fmt.Println("字符串")
	case int:
		fmt.Println("整形", v) //
	default:
		fmt.Println("其他类型")
	}
}

[输出格式接口]
我们使用fmt.Print等函数时,对任意一个值都有一个缺省打印格式。本质上就是实现打印相关的接口。

package main

import "fmt"

type Person struct {
	name string
}

/*fmt包内部会对两个接口做处理
也就是你自己定义了String、GoString 就按照你自己定义方法来调用
否则就默认打印
*/

/*String Interface*/
//1.
// func (Person) String() string { //影响 缺省格式%v和详细格式%+v
// 	return "aaa"
// }
//2.指针作为receiver 只对指针有效 指针输出自定义格式
func (*Person) String() string { //影响 缺省格式%v和详细格式%+v
	return "aaa"
}

/*GoString Interface*/
//1.
// func (Person) GoString() string { //影响 %#v
// 	return "xks"
// }
//2.指针作为receiver 只对指针有效 指针输出自定义格式
func (*Person) GoString() string { //影响 %#v
	return "xks"
}
//普通函数
func (*Person) foo() string {
	return "foo"
}

func main() {
	p1 := Person{"Tom"}
	fmt.Println("1:", p1, &p1)          //{Tom} &{Tom} 缺省打印格式
	fmt.Printf("2: %+v,%+v\n", p1, &p1) //{name:Tom},&{name:Tom}
	fmt.Printf("3: %#v,%#v\n", p1, &p1) //main.Person{name:"Tom"},&main.Person{name:"Tom"}
	fmt.Println(p1.foo(), (&p1).foo())  //foo foo

	// 如果不定义 String GoString 使用系统内建方式
	// 1: {Tom} &{Tom}
	// 2: {name:Tom},&{name:Tom}
	// 3: main.Person{name:"Tom"},&main.Person{name:"Tom"}

	// 1.如果定义 String GoString 使用系统内建方式
	// 1: aaa aaa
	// 2: aaa,aaa
	// 3: xks,xks

	//2.如果指针作为receiver 只对指针有效 指针输出自定义格式
	// 1: {Tom} aaa
	// 2: {name:Tom},aaa
	// 3: main.Person{name:"Tom"},xks

	//可以使用类型断言进行判断
	var i interface{}
	i = p1
	//i.(fmt.Stringer)
	switch v := i.(type) {
	case nil:
		fmt.Println("nil")
	case string:
		fmt.Println("字符串")
	case int:
		fmt.Println("整形", v)
	case Person:
		fmt.Println("Person type:", v)
	case fmt.Stringer: //接口类型
		fmt.Println("String Interface type:", v) //Person type: aaa
	default:
		fmt.Println("其他类型")
	}
}
[总结]
Stringer、GoStringer接口的方法,如果receiver如果是指针,只能对指针有作用;如果receiver如果是实例,实例、指针都有作用。 
普通方法receiver不管是实例还是指针,实例、指针都可以调用该方法时

【9-9-自定义错误和断电调试】

=====================================================================
异常处理
	type error {
		Error() string
	}

[实现源码 自定义error]-系统内置源码error.new(type)

package main

import "fmt"

//包内部使用
type errorString struct {
	s string
}

//首先是errorString的方法,同时也符合error接口的定义
func (e *errorString) Error() string {
	return e.s
}

//构造函数
// func New(text string) *errorString {
// 	return &errorString{s: text}
// }
func New(reason string) error { //error是内建函数
	return &errorString{s: reason} //既是errorString类型,也是error接口类型
}

func (e *errorString) foo() {
	fmt.Println("errorString结构体的foo方法")
}

func main() {
	e1 := New("Error:[1]")  //var e1 error = New("xxx")
	fmt.Printf("%T\n", e1)  //*main.errorString
	fmt.Println(e1.Error()) //Error:[1]
	if v, ok := e1.(*errorString); ok {
		v.foo() //errorString结构体的foo方法
	}

	// e1,e2 区别
	e2 := errorString{"Error:[2]"} // var e2 errorString
	fmt.Println(e2.Error())        //Error:[2]
}

[自定义Error]
package main

import (
	"errors"
	"fmt"
)

var ErrDisvisionByZero = errors.New("除零异常") //var ErrDisvisionByZero error类型

func div(a, b int) (int, error) {
	if b == 0 {
		return 0, ErrDisvisionByZero
	}
	return a / b, nil
}

func main() {
	if v, err := div(5, 0); err == nil { //nil 无错误
		fmt.Println("v =", v)
	} else { //有错误
		// fmt.Println("err = ", err)
		// fmt.Println("有错误", err.Error(), v)
		fmt.Println("有错误", err) //fmt.Print* 内部对error特殊处理

	}
	//div(5, 2) => v = 2
	//div(5, 0) => 有错误 除零异常 0

	// fmt.Println(div(5, 2)) //2 <nil>
}

【9-10-panic和recover】

=====================================================================

panic是不好的,因为它发生时,往往会造成程序崩溃、服务终止等后果,所以没人希望它发生。但是如果在错误发生时,不及时panic而终止程序运行
	继续运行程序恐怕造成更大的损失,付出更加惨痛的代价。所以,有时候,panic导致的程序崩溃实际上可以及时止损,只能两害相权取其轻。
panic虽然不好,体验很差,但也是万不得已,可以马上暴露问题,及时发现和纠正问题。

panic产生
	runtime运行时错误导致抛出panic,比如数组越界、除零
	主动手动调用panic(reason),这个reason可以是任意类型

panic执行
	逆序执行当前已经注册过的goroutine的defer链(recover从这里介入)
	打印错误信息和调用堆栈
	调用exit(2)结束整个进程

抛出异常的方法
	系统运行时抛出panic
	手动panic
如果panic没有处理,将向外排除,直到把
程序


EG:
package main

import (
	"errors"
	"fmt"
)

func div(a, b int) int {
	defer fmt.Println("start")
	defer fmt.Println(a, b)
	r := a / b //这一行有可能panic
	fmt.Println("end") //panic了不会再执行了
	return r
}

func main() {
	fmt.Println(div(5, 0))
}

// 5 0
// start
// panic: runtime error: integer divide by zero
 
// goroutine 1 [running]: 下面是调用栈,div压着main
// main.div(0x5, 0x0)
// 	e:/goprojects/main.go:13 +0x2cc 出错的行号
// main.main()
// 	e:/goprojects/main.go:19 +0x25

[recover]
package main

import (
	"errors"
	"fmt"
	"runtime"
)

var ErrDisvisionByZero = errors.New("除零异常") //var ErrDisvisionByZero error类型

func div(a, b int) int {
	defer func() {
		err := recover()
		fmt.Printf("1 %+v, %[1]T\n", err)
	}()
	defer fmt.Println("start")
	defer fmt.Println(a, b)
	defer func() {
		err := recover() //一旦recover了,就相当于处理过错误
		fmt.Printf("2 %+v, %[1]T\n", err)
		switch v := err.(type) { //类型断言
		case runtime.Error:
			//在源码种 runtime/error.go No.75 行,还为errorString也实现了RuntimeError方法
			//也就是说,标准库runtime运行时错误,都是runtime.Error接口的
			fmt.Printf("原因:%T, %#[1]v\n", v)
		case []int:
			fmt.Println("原因:切片", v)
		}
		fmt.Println("离开recover处理")
	}()

	r := a / b //这一行有可能panic

	panic([]int{1, 3, 5})
	fmt.Println("end")
	return r
}

func main() {
	fmt.Println(div(5, 2), "!!!")
	fmt.Println("main exit")
}

/*1*/
//	fmt.Println(div(5, 0), "!!!")
// 2 runtime error: integer divide by zero, runtime.errorString
// 原因:runtime.errorString, "integer divide by zero"
// 离开recover处理
// 5 0
// start
// 1 <nil>, <nil>
// 0 !!!
// main exit

/*2*/
//	fmt.Println(div(5, 2), "!!!")
// 2 [1 3 5], []int
// 原因:切片 [1 3 5]
// 离开recover处理
// 5 2
// start
// 1 <nil>, <nil>
// 0 !!!
// main exit

上例中,一旦在某函数中panic,当前函数panic之后的语句将不再执行,开始执行defer。如果在defer中错误被recover后
	就相当于当前函数产生的错误得到了处理。当前函数执行完defer,当前函数退出执行,程序还可以从当前函数之后继续执行。

可以观察到panic和recover有如下
	有panic,一路向外抛出,但没有一处进行recover,也就是说没有地方处理错误,程序崩溃有painc,有recover来捕获,相当于错误被处理掉了
	当前函数defer执行完后,退出当前函数从当前函数之后继续执行

 

posted @ 2024-06-23 23:07  しみずよしだ  阅读(9)  评论(0)    收藏  举报