Go06-文件操作+单元测试+goroutine+channel+反射

发布时间 2023-03-26 13:17:33作者: 行稳致远方

Go06-文件操作+单元测试+goroutine+channel+反射

1.打开和关闭文件

func main() {
	// 1 打开文件。
	// file可以称为file对象、file指针、file文件句柄。
	file, err := os.Open("D:\\1.txt")
	if err != nil {
		fmt.Println(err)
	}

	// file是一个*File指针
	// file=&{0xc00007e780}
	fmt.Printf("file=%v\n", file)

	// 关闭文件。
	err = file.Close()
	if err != nil {
		fmt.Println(err)
	}
}

2.读取文件的两种方式

  1. 带缓冲的方式读取文件。
file, err := os.Open("D:\\1.txt")
if err != nil {
    fmt.Println(err)
}

// 函数退出时关闭file
defer file.Close()
const defaultBufferSize = 1024
reader := bufio.NewReader(file)

for {
    // 读取到换行\n就结束
    str, err := reader.ReadString('\n')
    fmt.Print(str)
    // io.EOF表示文件末尾。
    if err == io.EOF {
        break
    }
}
fmt.Println()
fmt.Println("读取文件结束")
  1. 使用ioutil一次性将文件读取到内存(适合小文件的读取)。
// 使用ioutil.ReadFile()读取文件时,没有显示的Open()文件,所以也不需要显示
// 的Close()文件。Open()和Close()被封装到ReadFile()函数的内部。
file := "d:\\1.txt"
// ioutil.ReadFile()一次性将文件读取到内存。
content, err := ioutil.ReadFile(file)
if err != nil {
    fmt.Println(err)
}

// [104 101 108 108 111 13 10 119 111 114 108 100]
fmt.Println(content)
// content是[]byte,显示到中断要转换为string。
/*
	hello
	world
	 */
fmt.Printf("%v", string(content))

3.写文件

  1. 创建新文件然后写入数据。
filePath := "D:\\2.txt"

// OpenFile()需要三个参数。第一个参数是文件路径;第二个参数是文件打开模式,可以组合;
// 第三个参数是文件控制权限。
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
if err != nil {
    fmt.Println("err")
    return
}

defer file.Close()

str := "hello\n"
writer := bufio.NewWriter(file)
for i := 0;i < 5;i++ {
    writer.WriteString(str)
    // writer是带缓存的,因此在调用WriteString时,先将数据写入到缓存,所以需要调
    // 用Flush()将缓存的数据刷新到文件中,否则文件是没有数据的。
    writer.Flush()
}
  1. 打开已经存在的文件,并将文件中的内容覆盖为其他内容。
filePath := "D:\\2.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)
if err != nil {
    fmt.Println(err)
    return
}

defer file.Close()
str := "张三、李四、王五\r\n"
writer := bufio.NewWriter(file)
for i := 0; i < 3; i++ {
    writer.WriteString(str)
}
writer.Flush()
  1. 打开已经存在的文件,在原来的文件后面追加ABC。
filePath := "D:\\2.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)
if err != nil {
    fmt.Println(err)
    return
}

defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("ABC")
writer.Flush()
  1. 打开已经存在的文件,现将文件内容输出到终端,然后再文件后面追加QQ。
filePath := "D:\\2.txt"
file, err := os.OpenFile(filePath, os.O_RDONLY | os.O_APPEND, 0666)

if err != nil {
    fmt.Println(err)
    return
}

defer file.Close()

reader := bufio.NewReader(file)
for {
    str, err := reader.ReadString('\n')
    fmt.Print(str)
    if err == io.EOF {
        break
    }
}

writer := bufio.NewWriter(file)
writer.WriteString("QQ")
writer.Flush()

4.文件的拷贝

  1. 使用ioutil.ReadFile和ioutil.WriteFile将一个文件拷贝到另一个文件。
filePath1 := "D:\\1.txt"
filePath2 := "D:\\2.txt"

data, err := ioutil.ReadFile(filePath1)
if err != nil {
    fmt.Println(err)
    return
}

err = ioutil.WriteFile(filePath2, data, 0666)
if err != nil {
    fmt.Println(err)
}
  1. 判断文件是否存在。
filePath := "D:\\3.txt"
_, err := os.Stat(filePath)

// os.Stat返回的错误为nil,表示文件或文件夹存在。
if err == nil {
    fmt.Println("存在")
}

// os.Stat返回的错误类型使用os.IsNotExist()判断为true,则文件夹不存在。
if os.IsNotExist(err) {
    fmt.Println("不存在")
}

// 如果os.Stat返回的错误类型为其它,则不确定是否存在。
fmt.Println("不确定是否存在")
  1. 使用io.Copy()进行文件拷贝。
func main() {
	// 使用io.Copy()进行文件拷贝。
	_, err := test01("D:\\1.jpg", "D:\\2.jpg")
	if err == nil {
		fmt.Println("文件拷贝成功")
	} else {
		fmt.Println("文件拷贝失败")
	}
}

func test01(destFilePath string, srcFilePath string) (written int64, err error) {
	descFile, err := os.Open(destFilePath)
	if err != nil {
		fmt.Println("打开文件错误", err)
		return
	}

	defer descFile.Close()
	reader := bufio.NewReader(descFile)

	srcFile, err := os.OpenFile(srcFilePath, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("打开文件错误", err)
	}
	defer srcFile.Close()
	writer := bufio.NewWriter(srcFile)

	return io.Copy(writer, reader)
}

5.获取命令行参数

// 1 使用os.Args获取命名行参数。
// 使用cmd执行 go run main.go -h 127.0.0.1 -p 8080
/*
	第0参数为main.exe
	第1参数为-h
	第2参数为127.0.0.1
	第3参数为-p
	第4参数为8080
	 */
for index, value := range os.Args {
    fmt.Printf("第%d参数为%v\n", index, value)
}

// 2 使用flag包来解析命令行参数。os.Args是原生的参数解析形式,
// 这种方式在解析特性形式的参数是是不方便的。

// 定义变量用于接受参数
var host string
var post int

// &host,参数存放的地址;h,返回命令行输入的-h参数;localhost,参数默认值;主机名,参数说明。
flag.StringVar(&host, "h", "localhost", "主机名")
flag.IntVar(&post, "p", 0, "端口号")
// 调用flag方法后才会将参数的解析。
flag.Parse()

fmt.Println(host, post)

6.json的序列化和反序列化

  1. json序列化。
func main() {
	// 1 对map进行json序列化。
	var m map[string]string
	m = make(map[string]string, 3)
	m["name"] = "tom"
	m["age"] = "10"
	m["address"] = "A01"

	// json.Marshal()返回的是一个[]byte,在输入时需要转换为string。
	str1, _ := json.Marshal(m)
	// {"address":"A01","age":"10","name":"tom"}
	fmt.Println(string(str1))

	// 2 对切片进行json序列化。
	var s []map[string]string
	s = make([]map[string]string, 1)
	s[0] = m
	str2, _ := json.Marshal(s)
	// [{"address":"A01","age":"10","name":"tom"}]
	fmt.Println(string(str2))

	// 3 对结构体进行json序列化。
	student := Student{"tom", 10, "A10"}
	str3, _ := json.Marshal(student)
	// {"Name":"tom","Age":10,"address":"A10"}
	fmt.Println(string(str3))
}

type Student struct {
	Name string
	Age int
	// 默认不指定结构体的tag时,序列化的json的key为字段名。可以通过指定结构体
	// 字段的tag来设置字段在json序列后的key。
	Address string `json:"address"`
}
  1. json反序列化。
func main() {
	// 1 json反序列化为map。
	var m map[string]string
	str1 := "{\"address\":\"A01\",\"age\":\"10\",\"name\":\"tom\"}"
	json.Unmarshal([]byte(str1), &m)
	// map[address:A01 age:10 name:tom]
	fmt.Println(m)

	// 2 json反序列化为切片。
	var s []map[string]string
	str2 := "[{\"address\":\"A01\",\"age\":\"10\",\"name\":\"tom\"}]"
	json.Unmarshal([]byte(str2), &s)
	fmt.Println(s) // [map[address:A01 age:10 name:tom]]

	// 3 json反序列化为结构体。
	var student Student
	str3 := "{\"Name\":\"tom\",\"Age\":10,\"address\":\"A10\"}"
	json.Unmarshal([]byte(str3), &student)
	fmt.Println(student) // {tom 10 A10}
}

type Student struct {
	Name string
	Age int
	// 默认不指定结构体的tag时,序列化的json的key为字段名。可以通过指定结构体
	// 字段的tag来设置字段在json序列后的key。
	Address string `json:"address"`
}

7.单元测试

  1. 要测试的方法,cal.go。
func Cal(a int, b int) int {
	return a + b
}
  1. 单元测试,cal_test.go。
// 单元测试的注意事项。
// 1 测试用例文件必须使用_test.go结尾,比如cal_test.go,但是cal不是固定的。
// 2 测试函数必须以Test开头,一般就是Test+被测试函数名,比如TestCal。
// 3 func TestCal(t *testing.T),形参类型必须是t *testing.T。
// 4 一个测试文件中可以有多个测试用例函数。
// 5 运行测试用例的命令。go test,运行正确,无日志;运行错误,有日志。
// go text -v,运行正确和错误都会输出日志。
// 6 当出现错误时可以使用t.Fatalf()来输出错误信息,并退出程序。
// 7 t.Logf()可以输出相应的日志。
// 8 测试用例执行结果,PASS表示执行成功,FAIL表示测试用例执行失败。
/*
=== RUN   TestCal
    cal_test.go:15: Cal(10, 20) success
--- PASS: TestCal (0.00s)
PASS
ok      command-line-arguments  0.353s
 */

// 9 测试单个文件,需要带上被测试的原文件。比如
// go test -v cal_test.go cal.go

// 10 测试单个方法。go test -v -run TestCal
func TestCal(t *testing.T) {
	a := 10
	b := 20
	c := a + b

	result := Cal(a, b)
	if result != c {
		t.Fatalf("Cal(10, 20) error")
	}

	t.Logf("Cal(10, 20) success")
}

func TestCal2(t *testing.T) {

}

8.并发和并行

  1. 并发:多线程在单核上运行。有10个线程在一个CPU上运行,每个线程需要执行10毫秒,进行轮训操作,从人的角度看这10个线程都在运行,但是从微观上看,某个时间点只有一个线程在执行,这就是并发。
  2. 并行:多线程在多核上运行。有10个线程在10个CPU上运行,每个线程需要执行10毫秒,各自在不同的CPU上执行,从人的角度看,这个10个线程都在运行;但是从微观上看,在某一个时间点,也同时有10个线程在执行,这就是并行。
  3. Go主线程,可以理解为线程,也可以理解为进程。
  4. Go协程,一个Go线程可以起多个协程,协程就是轻量级的线程,编译器进行了优化。
  5. Go协程的特点。有独立的栈空间、共享程序堆空间、调度由用户控制、协程是轻量级的线程。

9.协程

func main() {
	// 1 主线程中开启一个协程,每隔一秒输出hello world。
	// 2 主线程每隔一秒,输出一次hello world。
	// 3 主线程和开启的协程同时执行。
	// 使用go关键字来开启一个协程。
	go test01()
	for i := 0; i < 10; i++ {
		fmt.Println("main() hello world", i)
		time.Sleep(time.Second)
	}

	// 4 主线程是一个物理线程,直接作用在CPU上,是重量级的,非常耗CPU资源。
	// 5 协程从主线程开启,是轻量级的线程,是逻辑态的,对资源消耗较小。
	// 6 Go中协程是一个很重要的特性,可以轻松开启上万个协程。
	// 7 其他编程语言并发机制一般是基于线程的,开启过多的线程,
	// 资源消耗大,这就提现出Go在并发上的优势。
}

func test01()  {
	for i := 0; i < 10; i++ {
		fmt.Println("test01() hello world", i)
		time.Sleep(time.Second)
	}
}

10.Go获取和设置CPU数

// 1 获取CPU数据。
num := runtime.NumCPU()
fmt.Println(num) // 6

// 2 设置CPU数据。
// 设置num - 1个CPU运行Go程序。
runtime.GOMAXPROCS(num - 1)
num = runtime.NumCPU()
fmt.Println(num)

// 3 Go1.8之后默认让程序在多核上运行,可以不用设置。
// 4 Go1.8之前可以设置CPU核数来提高程序运行效率。

11.goroutine存在的并发问题

func main() {
	// 1 使用goroutine存在的并发问题。
	fmt.Println(myMap)
	// 报错 fatal error: concurrent map writes。
	// 由于没有对全局变量myMap加锁,因此会出现资源竞争问题,所以代码会出现错误,
	// error: concurrent map writes
	test01()
	time.Sleep(time.Second * 5)
	for key, value := range myMap {
		fmt.Println(key, value)
	}

	// 2 针对goroutine存在的并发问题有两种解决方式。
	// 2.1 全局变量互斥锁。
	// 2.2 使用管道channel来解决。
}

var myMap = make(map[int]int, 10)
func test01() {
	for i := 0; i < 200;i++ {
		// 报错 fatal error: concurrent map writes
		go put(i)
	}
}

func put(n int) {
	result := 1
	for i := 0; i < n;i++ {
		result += i
	}
	myMap[n] = result
}

12.使用全局互斥锁来解决goroutine存在的并发问题

func main() {
	// 1 goroutine存在并发问题,当开启多个协程给map中存放数据时,
	// 就会出现error: concurrent map writes。

	// 2 通过互斥锁解决error: concurrent map writes。
	// 2.1 全局声明一个互斥锁lock。
	// 2.2 在给map中添加数据时加锁,然后再添加完数据后释放锁。

	// 3 通过互斥锁解决error: concurrent map writes问题的缺点。
	// 3.1 主线程不知道所有的goroutine处理完任务的时间(程序中简单让主程序睡眠
	// 10秒来等待所有的goroutine执行完成),存在的问题:设置的时间长了,会增加
	// 等待时间;设置的时间短了,有可能还有goroutine处于工作状态,这时会随着主
	// 程序的退出而销毁。
	// 3.2 基于存在的问题,可以使用channel来处理。
	test01()
}

var (
	myMap = make(map[int]int, 10)
	// 声明一个全局的互斥锁。
	// sync是同步的意思;mutex是互斥的意思。
	lock sync.Mutex
)
func test01() {
	for i := 0; i < 100; i++ {
		go put(i)
	}
	time.Sleep(time.Second * 10)
}

func put(n int) {
	result := 0
	for i := 0; i < n; i++ {
		result += i
	}

	// 再给map添加数据前加锁。
	lock.Lock()
	myMap[n] = result
	// 给map添加数据后释放锁。
	lock.Unlock()
}

13.管道channel

// 1 声明一个int类型的管道channel
var c1 chan int
// 2 管道channel使用时必须初始化。
c1 = make(chan int, 3)

// c1的值 0xc000102080,c1的地址 0xc000006028
fmt.Printf("c1的值 %v,c1的地址 %p\n", c1, &c1)

// 3 向管道中写入数据。
c1 <- 10
c1 <- 20
c1 <- 30
// 向管道中写入数据时,需要注意不能超过其容量。
// 当超过容量时报错:fatal error: all goroutines are asleep - deadlock!
// c1 <- 40

// 4 查看channel的长度和容量。
fmt.Println(len(c1), cap(c1)) // 3 3

// 5 从管道中取出数据。
n1 := <- c1
fmt.Println(n1) // 10
n2 := <- c1
fmt.Println(n2) // 20
n3 := <- c1
fmt.Println(n3) // 30

// 6 在没有协程的情况下channel中没有数据时,在从channel中取出数据就会
// 报错fatal error: all goroutines are asleep - deadlock!
// n4 := <- c1

fmt.Println(len(c1), cap(c1)) // 0 3

// 7 管道channel总结。
// 7.1 channel是一个先进先出的队列。
// 7.2 多个goroutine访问channel时,是线程安全的。
// 7.3 channel是有类型的,string类型的channel只能保存string类型的数据。
// 7.4 channel是引用类型,channel必须初始化后才能写入数据。
// 7.5 channel中存满数据后就不能在存放数据了,当从channel中取出数据后就可以在存放数据。
// 7.6 在没有协程的情况下,将channel的数据取完,再取,就会报dead lock。

14.管道channel读写数据

  1. 定义一个mapChan,用于存放10个map[string][string],并进行数据的读写。
var mapChan chan map[string]string
mapChan = make(chan map[string]string, 10)

m1 := make(map[string]string, 10)
m1["A01"] = "01"
m1["A02"] = "02"

m2 := make(map[string]string, 10)
m2["B02"] = "002"
m2["B02"] = "002"

// 数据写入channel
mapChan <- m1
mapChan <- m2

// 从channel中读取数据。
m := <- mapChan
fmt.Println(m) // map[A01:01 A02:02]
  1. 定义allChan,用于存放任意数据类型变量。
var allChan chan interface{}
allChan = make(chan interface{}, 10)

allChan <- 10

m1 := make(map[string]string, 10)
m1["A01"] = "01"
m1["A02"] = "02"
allChan <- m1

a1 := <- allChan
fmt.Println(a1) // 10

a2 := <- allChan
fmt.Println(a2) // map[A01:01 A02:02]

// 编译错误,需要类型断言后才能使用map的相关操作
// a3 := a2["A02"]
a3 := a2.(map[string]string)
fmt.Println(a3["A01"]) // 01

15.管道channel的关闭和遍历

// 1 channel的关闭。使用内置换行close可以关闭channel,当channel关闭后就
// 不能再向channel中写数据了,但是仍然可以从channel中读取数据。
intChan := make(chan int, 10)

intChan <- 10
intChan <- 20
close(intChan)

// channel关闭后可以从其中读取数据。
a1 := <- intChan
fmt.Println(a1) // 10

// channel关闭后在向其中写取数据,会报错:panic: send on closed channel。
// intChan <- 30

// 2 channel的遍历。
// channel支持for-range遍历;
// 在遍历channel时,如果没有关闭,就会出现deadlock的错误;
// 在遍历channel时,如果channel已经关闭,则正常遍历。

floatChan := make(chan float32, 10)
// 通过for向channel中添加数据。
for i := 0; i < 10; i++ {
    floatChan <- float32(i)
}

// 没有关闭channel时,遍历channel会报错 fatal error: all goroutines are asleep - deadlock!。
//for value := range floatChan {
//	fmt.Println(value)
//}

close(floatChan)
// 0 1 2 3 4 5 6 7 8 9
for value := range floatChan {
    fmt.Println(value)
}

// 当退出遍历后,channel中就没有数据了。
// channel的数据 0
fmt.Println("channel的数据", <- floatChan)
// channel的数据 0
fmt.Println("channel的数据", <- floatChan)

16.两个协程读取数据

func main() {
	// 1 开启writeData协程,向intChan中写入50个整数。
	// 2 开启readData协程,从intChan中读取数据。
	// 3 主程序等待writeData协程和readData协程都完成后退出程序。

	// 4 定义一个exitChan,当读取完数据后就向exitChan中存放true,
	// 主程序读取到exitChan中的true后就退出程序。
	// 当exitChan中没有true时,会阻塞主程序,指导读取到exitChan才不会阻塞主程序的执行。
	// writeData协程向intChan中写入50个整数,写入完成之后close了intChan,
	// 所以readData协程才能在读取完50个数字,返回0(该channel类型的默认值) false(没有数据并且channel已经关闭了。)。

	intChan := make(chan int, 10)
	exitChan := make(chan bool, 1)

	go writeData(intChan)
	go readData(intChan, exitChan)

	for  {
		value, ok := <- exitChan
		fmt.Println("exitChan ->", value, ok)
		if ok {
			break
		}
	}
	fmt.Println("main end")
}

func readData(intChan chan int, exitChan chan bool)  {
	for {
		value, ok := <- intChan
		fmt.Println("readData ->", value, ok)
		if !ok {
			break
		}
	}

	exitChan <- true
	close(exitChan)
}

func writeData(intChan chan int) {
	for i := 0; i < 50; i++ {
		intChan <- i
	}

	close(intChan)
}

17.开启4个协程统计1-20000中的素数

func main() {
	// 1 使用goroutine和channel结合统计1-20000中的素数,要求开启4个协程完成统计。
	intChan := make(chan int, 20000)
	exitChan := make(chan bool, 4)
	go addDataToChan(intChan)

	for i := 0; i < 4; i++ {
		go handlerData(intChan, exitChan)
	}

	for i := 0; i < 4; i++ {
		value, ok := <- exitChan
		fmt.Println("exitChan ->", value, ok)
	}
	fmt.Println("main end")
}

func handlerData(intChan chan int, exitChan chan bool)  {
	for {
		value, ok := <- intChan
		if !ok {
			break
		}

		// 假设所有的数都是素数
		flag := true
		for i := 2; i < value; i++ {
			if value % i == 0 {
				flag = false
				break
			}
		}
		if flag {
			fmt.Println(value, "是素数")
		}
	}
	fmt.Println("有一个协程因为获取不到数据而退出")
	exitChan <- true
}


func addDataToChan(intChan chan int) {
	for i := 1; i <= 20000; i++ {
		intChan <- i
	}

	close(intChan)
}

18.只读和只写的管道channel

func main() {
	// 1 channel可以声明为只读或者只写的。

	// 1.1 在默认情况下声明的channel是可读可写的。
	var chan1 chan int
	chan1 = make(chan int, 10)
	fmt.Println(chan1)

	// 1.2 声明只写的channel。
	var chan2 chan<- int
	chan2 = make(chan int, 10)
	chan2 <- 20

	// 使用只写的channel读取数据编译错误:
	// invalid operation: cannot receive from send-only channel chan2 (variable of type chan<- int)
	// <-chan2

	// 1.3 声明只读的channel。
	var chan3 <-chan int
	chan3 = make(chan int, 10)

	// 向只读的channel中写入数据编译错误:
	// invalid operation: cannot send to receive-only channel chan3 (variable of type <-chan int)
	// chan3 <- 10
	fmt.Println(chan3)

	// 2 只读和只写channel的实践。
	intChan := make(chan int, 10)
	intChan <- 10
	intChan <- 20
	test01(intChan)

	// 3 使用select可以解决channel取数据阻塞的问题。
	floatChan := make(chan float32, 10)
	for i := 0; i < 10; i++ {
		floatChan <- float32(i)
	}

	stringChan := make(chan string, 10)
	for i := 0; i < 10; i ++ {
		stringChan <- "stringChan" + fmt.Sprintf("%d", i)
	}

	// 当使用for...range遍历channel时,如果没有使用close关闭channel,
	// 则会报错:all goroutines are asleep - deadlock!
	//for v := range floatChan {
	//	fmt.Println(v)
	//}

	// 在开始中不好确定channel是否已经close了,所以可以使用select。
	// select可以解决从channel中读取数据阻塞的问题。
	label1:
	for {
		select {
		case v := <-floatChan:
			fmt.Println(v)
		case v := <-stringChan:
			fmt.Println(v)
		default:
			fmt.Println("没有取到数据")
			break label1
		}
	}

	// 4 goroutine中使用recover(),解决协程中出现的panic,导致程序崩溃问题。
	// 使用recover()处理panic后,后面的程序继续执行。
	go test02()
	time.Sleep(time.Second * 3)
	fmt.Println("main end")
}

func test02()  {
	defer func() {
		// 捕获test02()抛出的panic。
		if err := recover(); err != nil {
			// test01发生错误 assignment to entry in nil map
			fmt.Println("test01发生错误", err)
		}
	}()

	var myMap map[string]string
	myMap["name"] = "alice"
}

// intChan <-chan int,当前intChan只能读操作
func test01(intChan <-chan int)  {
	n1 := <- intChan
	fmt.Println(n1) // 10
}

19.反射

func main() {
	// 1 反射可以在运行时获取变量的各种信息,比如类型和类别。
	// 2 如果是结构体变量,反射可以获取到结构体中字段和方法的信息。
	// 3 通过反射可以修改变量的值,调用变量关联的方法。
	// 4 使用反射,需要import ("reflect")

	// 5 反射中常用的函数。reflect.TypeOf("变量名"),获取变量的类型;
	// reflect.ValueOf("变量名"),获取变量的值。
	
	// 6 通过反射获取变量的类型type、类别kind、值。
	var a1 int = 10
	test01(a1)

	// 7 变量、interface{}、reflect.Value之间的相互转换。
	var a2 int = 20
	test02(a2)

	// 8 对结构体的反射。
	s1 := Student{"tom", 10}
	test03(s1)
}

type Student struct {
	Name string
	Age int
}
func test03(a interface{})  {

	// 获取结构体类型
	rType := reflect.TypeOf(a)
	fmt.Println(rType) // main.Student

	// 获取结构体类型的值。
	rValue := reflect.ValueOf(a)
	fmt.Println(rValue) // {tom 10}

	// reflect.Value转换为interface{}
	iR := rValue.Interface()
	s1 := iR.(Student)
	fmt.Println(s1.Name) // tom
}

func test02(a interface{})  {
	// 1 interface{}转reflect.value
	rValue := reflect.ValueOf(a)
	// 20 reflect.Value
	fmt.Printf("%v %T\n", rValue, rValue)

	// 2 reflect.Value转interface{}
	b := rValue.Interface();
	// 20 int
	fmt.Printf("%v %T\n", b, b)

	// 3 通过类型断言将interface{}转换为变量
	// a++,编译错误,a是interface,不能进行整数的++操作。

	// 通过类型断言将a转换为int后,c就可以进行整数的++操作。
	c := a.(int)
	c++
	fmt.Println(c)
}

func test01(a interface{}) {
	// 6.1通过反射获取变量的类型type、类别kind、值。
	// 获取变量的类型type
	rType := reflect.TypeOf(a)
	fmt.Println(rType) // int

	// 通过反射获取变量的值。
	rValue := reflect.ValueOf(a)
	fmt.Println(rValue) // 10

	// reflect.TypeOf()和reflect.ValueOf()返回的类型。
	// *reflect.rtype reflect.Value
	fmt.Printf("%T %T\n", rType, rValue)
}

20.反射的主要事项和使用细节

// 1 类型Type和类别Kind的区别
// 1.1 对于var a1 int = 10,Type为int,Kind也是int。
var a1 int = 10
a1Type := reflect.TypeOf(a1)
fmt.Println(a1Type, a1Type.Kind()) // int int

type Student struct {
    Name string
}
// 1.2 对于var a2 = Student{"tom"},Type是main.Student,kind是struct。
var a2 Student = Student{"tom"}
a2Type := reflect.TypeOf(a2)
fmt.Println(a2Type, a2Type.Kind()) // main.Student struct

// 2 使用反射获取int类型变量和结构体类型变量的值。
var a3 int = 20
// 使用反射获取int类型变量的值。
b1 := reflect.ValueOf(a3).Int()
fmt.Println(b1) // 20

var a4 Student = Student{"alice"}
// 使用反射获取结构体字段的个数。
b2 := reflect.ValueOf(a4).NumField()
fmt.Println(b2) // 1

// 使用反射获取Student结构体Name字段的值。
fName := reflect.ValueOf(a4).FieldByName("Name")
fmt.Println(fName.String()) // alice

// 3 通过反射修改int类型变量或者结构体的值。
var a5 int = 30
// 报错:panic: reflect: call of reflect.Value.Elem on int Value
// 报错的原因,通过SetXXX()修改值时,需要使用对应的指针类型来完成。
// reflect.ValueOf(a5).Elem().SetInt(10)
var a6 *int = &a5
fmt.Println(*a6) // 30
reflect.ValueOf(a6).Elem().SetInt(40)
fmt.Println(*a6) // 40

// 使用反射修改结构体类型的值。
var a7 Student = Student{"张三"}
fmt.Println(a7.Name) // 张三

var a8 *Student = &a7
reflect.ValueOf(a8).Elem().FieldByName("Name").SetString("李四")
fmt.Println(a7.Name) // 李四

21.反射操作结构体

func main() {
	// 1 通过返回遍历结构体字段,调用结构体方法,获取结构体标签的值。
	var a1 Student = Student{"tom", 10, "男"}
	var a2 interface{} = a1

	// 获取reflect.Type类型、reflect.Value类型、Kind类别。
	rType := reflect.TypeOf(a2)
	rValue := reflect.ValueOf(a2)
	rKind := rType.Kind()

	// 类别是结构体
	if rKind == reflect.Struct {
		fmt.Println("类别是结构体")
	} else {
		fmt.Println("类别不是结构体")
	}

	fieldNumber := rValue.NumField()
	// Student结构体有3个字段
	fmt.Printf("Student结构体有%d个字段\n", fieldNumber)

	// 2 遍历结构体字段,并获取字段的Tag。
	for i := 0; i < fieldNumber; i++ {
		fieldValue := rValue.Field(i)
		/*
		Student第0个字段值为为tom
		Student第1个字段值为为10
		Student第2个字段值为为男
		 */
		fmt.Printf("Student第%d个字段值为为%v\n", i, fieldValue)

		tag := rType.Field(i).Tag.Get("json")
		// tag是字符串类型,所以可以使用if tag != "" { 字段有tag },来判断字段是否定义了tag。
		/*
		Student第0个字段的Tag为name
		Student第1个字段的Tag为age
		Student第2个字段的Tag为
		 */
		fmt.Printf("Student第%d个字段的Tag为%v\n", i, tag)
	}

	// 获取结构体中方法的数量。
	// 只能识别到首字母大写的方法,所以这里获取到的方法的数量是1。
	methodNumber := rValue.NumMethod()
	fmt.Println(methodNumber) // 1

	// 3 通过反射调用方法。
	var methodParams []reflect.Value
	// 调用第一个方法,方法默认是按照ASCII码排序。
	// 调用方法时需要传入参数,参数的类型为reflect.Value数组。
	// 调用方法的返回值也为reflect.Value数组类型。
	returnValue := rValue.Method(0).Call(methodParams)
	fmt.Println(returnValue) // [tom10]
}

type Student struct {
	Name string	`json:"name"`
	Age int `json:"age"`
	Sex string
}

func (s Student) GetNameAndAge() string {
	return s.Name + fmt.Sprint(s.Age)
}

func (s Student) getName() string {
	return s.Name
}