【Go】高级语法学习

文章目录

      • 1 文件操作
        • 1.1 简介
        • 1.2 常用的操作API
        • 1.3 读写操作
        • 1.4 判断文件(夹)是否存在
        • 1.5 拷贝文件
      • 2 json
        • 2.1 简介
        • 2.2 json的序列化(serialize)
        • 2.3 json的反序列化
      • 3 单元测试、goroutine、channel
        • 3.1 testing测试框架
        • 3.2 单元测试细节
        • 3.3 goroutine(协程)简介
        • 3.4 goroutine 快速入门、MPG模型
        • 3.5 goroutine 通信、并发
        • 3.6 【新手】sync
        • 3.7【荐】channel
        • 3.8 通道的打开和关闭
        • 3.9 案例:多协程同步
        • 3.10 管道阻塞机制
        • 3.11 管道的注意细节
      • 4 反射
        • 4.1 简介
        • 4.2 快速入门案例(基本类型、结构体)
        • 4.3 反射的细节
        • 4.4 设置反射字段的值
        • 4.5 反射的最佳实践(使用反射获取结构体的方法和标签tag值)
        • 4.6 修改、打印字段,调用方法

1 文件操作

1.1 简介

import "os"
【Go】高级语法学习_第1张图片

1.2 常用的操作API

// File代表一个打开的文件对象
type File struct {
    // 内含隐藏或非导出字段
}

// 打开文件
func Open(name string) (file *File, err error)

// 关闭文件
func (f *File) Close() error

1.3 读写操作

带缓冲的读取

func readFile()  {
	// [1] 打开
	file, err := os.Open("E:\\ShareDir\\Code\\GoCode\\src\\go_code\\proj07_file\\test.txt")
	if err != nil{
		fmt.Printf("err:%s\n", err)
	}
	// [end] 延时关闭
	defer file.Close()

	// 创建一个带缓冲的*Read 默认大小:4096
	reader := bufio.NewReader(file)
	// 循环读取
	for{
		readString, err := reader.ReadString('\n') // 按换行符读取
		if err == io.EOF{
			break // 读取完毕
		}
		// 输出内容
		fmt.Printf("%s", readString)
	}
}

一次性读取

一次将文件内容全部读取到内存中

func readFileInOnece()  {
	filePath := "E:\\ShareDir\\Code\\GoCode\\src\\go_code\\proj07_file\\test.txt"

	// func ReadFile(filename string) ([]byte, error)
	file, _ := ioutil.ReadFile(filePath)
	str := string(file)
	fmt.Println(str)
}

创建文件并写内容

【Go】高级语法学习_第2张图片

const (
    O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
    O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
    O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
    O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
    O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件
    O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
    O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
)
func writeToFile()  {
	filePath := "E:\\ShareDir\\Code\\GoCode\\src\\go_code\\proj07_file\\newtest.txt"
	// 打开创建新文件
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil{
		fmt.Printf("err:%s\n", err)
	}

	// 延时关闭
	defer file.Close()

	// 循环写入  使用带缓冲的写
	writer := bufio.NewWriter(file)
	for i := 0; i < 5; i++{
		writer.WriteString(fmt.Sprintf("xhh[%d]\r\n", i))
		fmt.Println("ok---")
	}
	// 需要将缓存刷入文件
	writer.Flush()
}

1.4 判断文件(夹)是否存在

// Stat返回一个描述name指定的文件对象的FileInfo。
// 如果出错,返回的错误值为*PathError类型。
func Stat(name string) (fi FileInfo, err error)

type FileInfo interface {
    Name() string       // 文件的名字(不含扩展名)
    Size() int64        // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    Mode() FileMode     // 文件的模式位
    ModTime() time.Time // 文件的修改时间
    IsDir() bool        // 等价于Mode().IsDir()
    Sys() interface{}   // 底层数据来源(可以返回nil)
}

【Go】高级语法学习_第3张图片

1.5 拷贝文件

  • 文本
func copyFile()  {
	var srcPath = "E:\\ShareDir\\Code\\GoCode\\src\\go_code\\proj07_file\\test.txt"

	// find 后缀位置
	var pointIndex = strings.LastIndex(srcPath, ".")
	var newPath = srcPath[:pointIndex] + "backup" + srcPath[pointIndex:]

	fmt.Println(srcPath)
	fmt.Println(newPath)

	// [1] openSrc
	openFile, err := os.OpenFile(srcPath, os.O_RDONLY, 0666)
	if err != nil{
		fmt.Println("open err : ", err)
		return
	}
	// 延时关闭
	defer openFile.Close()
	reader := bufio.NewReader(openFile)

	// [2] openDst
	writeFile, err := os.OpenFile(newPath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil{
		fmt.Println("open err : ", err)
		return
	}
	defer writeFile.Close()
	writer := bufio.NewWriter(writeFile)

	// [3] 循环读写
	for{
		// 读取
		readString, err := reader.ReadString('\n')
		if err == io.EOF{
			// 读取完毕
			break
		}
		// 写入和刷盘
		_, err = writer.WriteString(readString)
		if err != nil{
			fmt.Println("writer err : ", err)
			return
		}

		err = writer.Flush()
		if err != nil{
			fmt.Println("writer.Flush() err : ", err)
			return
		}
	}
	fmt.Printf("copy success!\n")
}
  • 图片或者视频
// 从src拷贝n个字节数据到dst,直到在src上到达EOF或发生错误。
// 返回复制的字节数和遇到的第一个错误。
// 对成功的调用,返回值err为nil而非EOF,因为Copy定义为从src读取直到EOF,它不会将读取到EOF视为应报告的错误。
// 如果src实现了WriterTo接口,本函数会调用src.WriteTo(dst)进行拷贝;否则如果dst实现了ReaderFrom接口,本函数会调用dst.ReadFrom(src)进行拷贝。
func Copy(dst Writer, src Reader) (written int64, err error)
func copyImg()  {
	var srcPath = "E:\\ShareDir\\Code\\GoCode\\src\\go_code\\proj07_file\\src.jpg"

	// find 后缀位置
	var pointIndex = strings.LastIndex(srcPath, ".")
	var dstPath = srcPath[:pointIndex] + "_bak" + srcPath[pointIndex:]

	// func Copy(dst Writer, src Reader) (written int64, err error)
	// [1] Reader
	readFile, err := os.Open(srcPath)
	if err != nil{
		fmt.Println("Open err : ", err)
		return
	}
	defer readFile.Close()
	reader := bufio.NewReader(readFile)

	// [2] Writer
	writeFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil{
		fmt.Println("OpenFile err : ", err)
		return
	}
	defer writeFile.Close()
	writer := bufio.NewWriter(writeFile)

	// [3] copy
	_, err = io.Copy(writer, reader)
	if err != nil{
		fmt.Println("io.Copy err : ", err)
		return
	}

	fmt.Printf("Copy img success.\n")
}

2 json

2.1 简介

【Go】高级语法学习_第4张图片
【Go】高级语法学习_第5张图片

2.2 json的序列化(serialize)

结构体、map、切片
使用tag

// import "encoding/json"
// Marshal函数返回v的json编码。
func Marshal(v interface{}) ([]byte, error)
package main

import (
	"encoding/json"
	"fmt"
)

// 定义结构体
type Person struct {
	Name string
	Age int
}

func structSerailze()  {
	// struce -> seralize
	p1 := Person{"xhh", 18}
	// 序列化
	data, err := json.Marshal(&p1)
	if err != nil{
		fmt.Printf("序列化 err %v\n", err)
		return
	}
	fmt.Printf("序列化效果:%v\n", string(data))
}


func mapSerailze()  {
	// map -> seralize
	var m1 map[string]interface{}
	m1 = make(map[string]interface{})
	m1["name"] = "xhh"
	m1["age"] = 18
	m1["info"] = "abcdef"

	// 序列化   map 引用类型 本身就是地址 不需要&m1
	data, err := json.Marshal(m1)
	if err != nil{
		fmt.Printf("序列化 err %v\n", err)
		return
	}
	fmt.Printf("序列化效果:%v\n", string(data))
}


func silceSerailze()  {
	// slice -> seralize
	var s1 []map[string]interface{}

	// values
	var m1 map[string]interface{}
	m1 = make(map[string]interface{})
	m1["name"] = "xhh"
	m1["age"] = 18

	var m2 map[string]interface{}
	m2 = make(map[string]interface{})
	m2["name"] = "mcy"
	m2["age"] = 4

	// append values
	s1 = append(s1, m1, m2)

	// 序列化   map 引用类型 本身就是地址 不需要&m1
	data, err := json.Marshal(s1)
	if err != nil{
		fmt.Printf("序列化 err %v\n", err)
		return
	}
	fmt.Printf("序列化效果:%v\n", string(data))
}

func main()  {
	// struct map slice -> serailze
	structSerailze()
	mapSerailze()
	silceSerailze()

	// 序列化效果:{"Name":"xhh","Age":18}
	// 序列化效果:{"age":18,"info":"abcdef","name":"xhh"}
	// 序列化效果:[{"age":18,"name":"xhh"},{"age":4,"name":"mcy"}]
}
  • tag

使用反射机制,实现序列化时的key替换

// 定义结构体
type Person struct {
	Name string `json:"name"`
	Age int `json:"age"`
}

2.3 json的反序列化

// 反序列化
func unmarshalStruct()  {
	// 网络得到的序列化str
	str := "{\"Name\":\"xhh\",\"Age\":18}"
	// 定义一个结构体变量
	var p1 Person
	// 反序列化为 赋值到p1
	// func Unmarshal(data []byte, v interface{}) error
	err := json.Unmarshal([]byte(str), &p1)
	if err != nil{
		fmt.Printf("反序列化 err %v\n", err)
		return
	}
	fmt.Printf("unmarshalStruct name[%s] age[%d]\n", p1.Name, p1.Age)
}

3 单元测试、goroutine、channel

单元测试:测试一个函数或者模块

3.1 testing测试框架

import "testing"

【Go】高级语法学习_第6张图片

3.2 单元测试细节

【Go】高级语法学习_第7张图片
【Go】高级语法学习_第8张图片

3.3 goroutine(协程)简介

传统方法:单核进行,很慢
单CPU运行(并发),多CPU(并行)

【Go】高级语法学习_第9张图片
【Go】高级语法学习_第10张图片

3.4 goroutine 快速入门、MPG模型

以主线程结束为主

package main

import (
	"fmt"
	"time"
)

func routFunc()  {
	for i := 0; i < 10; i++{
		time.Sleep(time.Millisecond * 500)
		fmt.Printf("Hello go_routione %d\n", i)
	}
}

func main()  {
	// 10次 每个 1s
	// 主线程 打印 go_land
	// 协程 打印 go_routione

	// 开启一个协程
	// 交替执行 主线程执行完毕结束
	go routFunc()

	for i := 0; i < 5; i++{
		time.Sleep(time.Millisecond * 500)
		fmt.Printf("Hello go_land %d\n", i)
	}
}

【Go】高级语法学习_第11张图片

  • M:主线程(物理线程)
  • P:线程执行上下文
  • G:协程

【Go】高级语法学习_第12张图片
【Go】高级语法学习_第13张图片

设置运行CPU个数

【Go】高级语法学习_第14张图片

3.5 goroutine 通信、并发

// 多协程并发计算 1-200的阶乘 放入map
问题一:主线程等待协程多长时间?
问题二:同时写入,存在并发竞争关系?(采用全局锁或者管道)

// 查看竞争
go build -race m2.go

3.6 【新手】sync

  • sync
// sync包提供了基本的同步基元,如互斥锁。
// 除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
import "sync"
package main

import (
	"fmt"
	"sync"
	"time"
)

// 全局map
var(
	mapii = make(map[int]int)
	// 全局互斥锁
	gLock sync.Mutex
)

// 计算 n! 放入map
func calcJC(n int) {
	res := 1
	for i := 1; i <= n; i++{
		res *= i
	}
	// 放入map   [1] 加锁和解锁
	gLock.Lock()
	mapii[n] = res
	gLock.Unlock()
}

func main()  {
	// 多协程并发计算 1-200的阶乘 放入map
	for i := 1; i <= 50; i++{
		go calcJC(i)
	}

	time.Sleep(time.Millisecond * 3000)

	// 遍历  读取数据时  加锁
	gLock.Lock()
	for idx, val := range mapii{
		fmt.Printf("mapii[%d]=%d\n", idx, val)
	}
	gLock.Unlock()
}

3.7【荐】channel

channel(管道:数据结构中的队列)
不需要加锁,channel是线程安全的
channel是有类型的,只能放置声明的类型

基本使用

chan 需要make初始化,才能使用
chan 有类型

// 存放 int
var intChan chan int
// 存放 map
var mapChan chan map[int]string

创建管道,取数据,存数据

package main

import "fmt"

func main()  {
	// [1] make存放容量cap 3 的chan
	var intChan chan int = make(chan int, 3)
	// [2] 查看类型  intChan type : chan int  v: 0xc04206a080  -> (是个地址,说明是个引用类型)
	fmt.Printf("intChan type : %T  v: %v\n", intChan, intChan)
	// [3] 向管道写入  不允许超过容量
	intChan <- 11
	intChan <- 12
	// [4] 查看大小和容量
	fmt.Printf("intChan size:%v cap:%v\n", len(intChan), cap(intChan))
	// [5] 取出数据  不使用协程的时候 从空chan取数据 则 deadlock
	num := <- intChan
	fmt.Printf("num : %v\n", num)
}

3.8 通道的打开和关闭

关闭:只能读,不能写

// func close(c chan<- Type)
close(intChan) // close 通道

在这里插入图片描述

package main

import "fmt"

func main() {
	intChan := make(chan int, 5)
	intChan <- 11
	intChan <- 44
	// [1] close 
	close(intChan)

	// [2] 遍历
	for v := range intChan{
		fmt.Printf("value : %v\n", v)
	}
}

读和写协程

package main

import (
	"fmt"
	"time"
)

// 写入
func writeData(intChan chan int)  {
	for i := 0; i < 20; i++{
		intChan<- i
		fmt.Printf("writeData 写入 [%d]\n", i)
		time.Sleep(time.Millisecond * 100)
	}
	// 写入30个数据,关闭通道,这样读取到末尾会退出
	close(intChan)
}

// 读取
func readData(intChan chan int, exitChan chan bool)  {
	for{
		// 若没有数据会进行阻塞
		val, ok := <-intChan

		if !ok{
			// 读到了 close 关闭的末尾
			break
		}
		fmt.Printf("readData 读取 [%d]\n", val)
		time.Sleep(time.Millisecond * 200)
	}

	// 退出通道标志 发送为主线程用
	exitChan<- true
	close(exitChan)
}




func main() {
	// [1] 数据通道   退出通道
	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)

	// [2] 读 和 写 的协程
	go writeData(intChan)
	go readData(intChan, exitChan)

	// [3] exitChan 阻塞在此处,等待读取数据
	// 直到 exitChan 中有数据,就进行读取
	// 当exitChan 关闭时 此处的循环退出,主线程结束
	for v := range exitChan{
		fmt.Printf("读取到了 %v\n", v)
	}
	fmt.Printf("Main thread exit\n")
}

3.9 案例:多协程同步

整空间为4的通道来控制4个协程的退出

【Go】高级语法学习_第15张图片

package main

import "fmt"

func putNum(intChan chan int)  {
	for i := 1; i <= 8000; i++ {
		intChan<- i
	}
	// 关闭intChan
	close(intChan)
}

func getPrime(intChan chan int, primeChan chan int, eixtChan chan bool)  {
	for{
		// 没有关闭管道,并且管道为空,这里会阻塞
		num, ok := <-intChan
		if !ok{
			// intChan管道【关闭】  并且读取到【末尾】
			// 退出处理
			break
		}

		isPrime := true
		//判断是否为prime
		for i := 2; i < num; i++{
			if num % i == 0{
				isPrime = false
				break
			}
		}

		if isPrime{
			// 加入集合
			primeChan<- num
		}
	}

	// 当前协程退出(放入数组 说明自己退出)
	// 不能关闭 primeChan 因为不知道其他协程是否完成
	eixtChan<- true
}

func main() {
	intChan := make(chan int, 1000)   // 放数据
	primeChan := make(chan int, 2000) // 放结果
	exitChan := make(chan bool, 4)	  // 标识4个线程

	// 开启协程 放1-8000的数  容量是1000 所以会阻塞进行
	go putNum(intChan)

	// 开启4个协程 进行同时处理
	for i := 0; i < 4; i++ {
		go getPrime(intChan, primeChan, exitChan)
	}

	// 主线程在此处进行阻塞 等待协程完成
	// 读取四次exitChan  表示4个协程全部退出了
	// 如果没读取那么多 说明协程还在工作 此处阻塞
	for i := 0; i < 4; i++ {
		_ = <-exitChan
	}
	// 4个协程工作完毕后 关闭素数primeChan通道
	close(primeChan)

	// 读取计算后的数据
	for val := range primeChan{
		fmt.Printf("prime val : %d\n", val)
	}
}

3.10 管道阻塞机制

不关闭通道,或者不读会产生 deadlock
读空了,或者加入满了,会进行阻塞

func calcNum(numChan chan int, fnChan chan Fn, exitChan chan bool)  {

	for{
		//time.Sleep(time.Millisecond * 500)
		// 取得 num
		// [1] 通道空 阻塞此处
		// [2] 通道关闭 -> false 退出
		num, ok := <-numChan
		if !ok{
			exitChan<- true
			fmt.Printf("Exit calcNum(?) add bool to exitChan\n")
			break
		}

		// 计算 resNum
		// Fn 创建并放入FnChan
		fn := Fn{num, calcN(num)}
		fnChan<- fn
		fmt.Printf("calcN(%d)\n", num)
	}
}

3.11 管道的注意细节

读写说明

// [1] 读写
chan1 := make(chan int, 3)

// [2] 只读
chan2 := make(<-chan int, 3)

// [3] 只写
chan1 := make(chan<- int, 3)

【Go】高级语法学习_第16张图片

select可以解决管道取数据阻塞的问题

注意:select不阻塞,使用for-select进行包裹
使用xhhlabel和bread xhhlable进行跳出循环

package main

import (
	"fmt"
	"time"
)

func main() {
	// select解决管道取数据阻塞的问题
	// [1] intChan
	intChan := make(chan int, 2)
	intChan<- 11
	intChan<- 33
	// [2] stringChan
	stringChan := make(chan string, 2)
	stringChan<- "xhh123"
	stringChan<- "mcy456"

	// [-- old]传统方法读取不关闭的通道,会产生 deadlock
	// 并且不能使用
	//for val := range intChan{
	//	fmt.Printf("intChan : %v\n", val)
	//}
	//for val := range stringChan{
	//	fmt.Printf("intChan : %v\n", val)
	//}

	// [-- new]使用select方法解决阻塞
	//      实际开发可能不知道什么时候阻塞
	xhhlabel:
	for{
		select {
			case v := <-intChan:
				fmt.Printf("[select] intChan : %v\n", v)
				time.Sleep(time.Millisecond * 500)

			case v := <-stringChan:
				fmt.Printf("[select] stringChan : %v\n", v)
				time.Sleep(time.Millisecond * 500)

			default:
				fmt.Printf("[select] default : no val\n")
				time.Sleep(time.Millisecond * 500)
				break xhhlabel // 退出for循环
				// return // 退出函数
		}
	}
	fmt.Printf("main exit sys.\n")
}


/*
	[select] stringChan : xhh123
	[select] intChan : 11
	[select] intChan : 33
	[select] stringChan : mcy456
	[select] default : no val
	main exit sys.
 */

解决协程中的panic

goroutine中使用recover,解决协程中出现的panic导致程序奔溃的问题

package main

import (
	"fmt"
	"time"
)

func sayHello()  {
	for i := 0; i < 10; i++ {
		fmt.Printf("say Hello\n")
		time.Sleep(time.Millisecond * 500)
	}
}

func errFunc()  {
	// ------------------------------------------------------------
	// 使用 defer+recover 解决 panic: assignment to entry in nil map
	defer func() {
		// 捕获异常
		if err := recover(); err != nil{
			fmt.Printf("errFunc 产生错误 err[%s]\n", err)
		}
	}()
	// ------------------------------------------------------------

	// 定义一个错误
	var m1 map[int]string
	// m1 = make(map[int]string)
	// 没有make进行使用 会产生错误err
	m1[1] = "xhh"
}


func main() {
	go sayHello() // panic: assignment to entry in nil map
	go errFunc() // errFunc协程出现错误 导致 sayHello协程 和 整个程序结束


	time.Sleep(time.Millisecond * 10000) // 主线程休眠10s防止线程退出
}

/*
	say Hello
	errFunc 产生错误 err[assignment to entry in nil map]
	say Hello
	say Hello
 */

4 反射

reflection

4.1 简介

【Go】高级语法学习_第17张图片

// reflect.Func(...)
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

变量、interface{}、reflect.Value相互转换

【Go】高级语法学习_第18张图片

4.2 快速入门案例(基本类型、结构体)

对基本类型操作

package main

import (
	"fmt"
	"reflect"
)

func changeType(i interface{}) int {
	// [1] 函数传入 类型 int -> interface{}

	// [2] interface{} -> reflect.Value
	rType := reflect.TypeOf(i)
	// rValue val : int
	fmt.Printf("rValue val : %v\n", rType)
	rValue := reflect.ValueOf(i)
	// rValue val : 99 type : reflect.Value   这里的rValue的类型还是 reflect.Value
	fmt.Printf("rValue val : %v type : %T\n", rValue, rValue)


	// [3] reflect.Value -> int  获取值
	numSrc := rValue.Int()
	fmt.Printf("numSrc type : %T\n", numSrc)

	// [4] reflect.Value -> interface{}
	rValueInter := rValue.Interface()

	// [5] interface{} -> 断言成int
	numEnd := rValueInter.(int)

	return numEnd
}

func main() {
	num := 99
	fmt.Printf("num type : %T\n", num)
	_ = changeType(num)
}

对结构体的操作

package main

import (
	"fmt"
	"reflect"
)

type Cat struct {
	Name string
	Age int
}

func changeCat(i interface{})  {
	// 传入 Cat -> interface{}

	// [1] 获取 reflect.Type    rType:  main.Cat
	rType := reflect.TypeOf(i)
	fmt.Println("rType: ", rType)

	// [2] 获取 reflect.Value   rValue val : {xhh 18}  type : reflect.Value
	rValue := reflect.ValueOf(i)
	fmt.Printf("rValue val : %v  type : %T\n", rValue, rValue)
	// rValue type : main.Cat kind : struct
	// type(包名)   kind(结构体)
	fmt.Printf("rValue type : %v kind : %v\n", rValue.Type(), rValue.Kind())

	// [3] reflect.Value -> interface{}
	rICat := rValue.Interface()

	// [4] 断言获取原类型变量 执行
	if c2, ok := rICat.(Cat); ok{
		// 断言成功 显示信息
		fmt.Printf("Cat name:%s  age:%d\n", c2.Name, c2.Age)
	}
}

func main() {
	c1 := Cat{"xhh", 18}
	changeCat(c1)

}

断言的最佳实践

【Go】高级语法学习_第19张图片

常量

常量的访问范围还是有首字符大小的要求
没有全部大写的要求

【Go】高级语法学习_第20张图片

4.3 反射的细节

  • Kind(类别):都是struct
  • Type(类型):pkg.Cat
  • 类别和类型可能相同,可能不同

【Go】高级语法学习_第21张图片

4.4 设置反射字段的值

// func ValueOf(i interface{}) Value
// Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
// 如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
func (v Value) Elem() Value
  • 利用地址和Elem修改
package main

import (
	"fmt"
	"reflect"
)

type Cat struct {
	Name string
	Age int
}

func setIntVal(i interface{})  {
	rVal := reflect.ValueOf(i)

	// [Int-2] .Elem() 得到指针  然后去修改值
	rVal.Elem().SetInt(99)
}



func setCat(i interface{})  {
	// 传入 Cat -> interface{}
	// 获取 reflect.Value   rValue val : {xhh 18}  type : reflect.Value
	rValue := reflect.ValueOf(i)
	rValue.Elem().FieldByName("Name").SetString("abcd")
	
	// 这样也可以修改
	//cat := i.(*Cat)
	//cat.Name = "abc"
}


func main() {

	// - 整形的修改
	num := 1
	fmt.Printf("src num : %d\n", num)
	setIntVal(&num) // [Int-1] 注意这里传入的是地址
	fmt.Printf("dst num : %d\n", num)

	// - 结构体的修改
	c1 := Cat{"xhh", 18}
	fmt.Println("src Cat : ", c1)
	setCat(&c1)
	fmt.Println("dst Cat : ", c1)
}

/*
	src num : 1
	dst num : 99
	src Cat :  {xhh 18}
	dst Cat :  {abcd 18}
 */

4.5 反射的最佳实践(使用反射获取结构体的方法和标签tag值)

// 字段个数
func (v Value) NumField() int
// 第i个字段的值
func (v Value) Field(i int) Value

// 获得第i个方法 从0开始
func (v Value) Method(i int) Value
// 调用函数
func (v Value) Call(in []Value) []Value
  • Code
package main
import (
	"fmt"
	"reflect"
)
//定义了一个Monster结构体
type Monster struct {
	Name  string `json:"name"`
	Age   int `json:"monster_age"`
	Score float32 `json:"成绩"`
	Sex   string

}

//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}
//方法, 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}

//方法,显示s的值
func (s Monster) Print() {
	fmt.Println("---start~----")
	fmt.Println(s)
	fmt.Println("---end~----")
}
func TestStruct(a interface{}) {
	//获取reflect.Type 类型
	typ := reflect.TypeOf(a)
	//获取reflect.Value 类型
	val := reflect.ValueOf(a)
	//获取到a对应的类别
	kd := val.Kind()
	//如果传入的不是struct,就退出
	if kd !=  reflect.Struct {
		fmt.Println("expect struct")
		return
	}

	//获取到该结构体有几个字段
	num := val.NumField()

	fmt.Printf("struct has %d fields\n", num) // 4
	//变量结构体的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))
		//获取到struct标签, 注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Field(i).Tag.Get("json")
		//如果该字段于tag标签就显示,否则就不显示
		if tagVal != "" {
			fmt.Printf("Field %d: tag为=%v\n", i, tagVal)
		}
	}

	//获取到该结构体有多少个方法
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)

	//var params []reflect.Value
	//方法的排序默认是按照 函数名的排序(ASCII码)
	val.Method(1).Call(nil) //获取到第二个方法。调用它


	//调用结构体的第1个方法Method(0)
	var params []reflect.Value  //声明了 []reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Value
	fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/

}
func main() {
	//创建了一个Monster实例
	var a Monster = Monster{
		Name:  "黄鼠狼精",
		Age:   400,
		Score: 30.8,
	}
	//将Monster实例传递给TestStruct函数
	TestStruct(a)
}


/*
	struct has 4 fields
	Field 0: 值为=黄鼠狼精
	Field 0: tag为=name
	Field 1: 值为=400
	Field 1: tag为=monster_age
	Field 2: 值为=30.8
	Field 2: tag为=成绩
	Field 3: 值为=
	struct has 3 methods
	---start~----
	{黄鼠狼精 400 30.8 }
	---end~----
	res= 50
	
	进程完成,并显示退出代码 0
 */

4.6 修改、打印字段,调用方法

修改、打印字段,调用方法

package main

import (
	"fmt"
	"reflect"
)

type Nums struct {
	Num1 int `json:"xhh_num1"`
	Num2 int `json:"xhh_num2"`
}

func (nums Nums) GetAdd(user string) int {
	fmt.Printf("[%v] is coming func (nums Nums) GetAdd(info string) int\n", user)
	return nums.Num1 + nums.Num2
}

func editNums(i interface{})  {
	// [0] 获得 reflect.Value 类型的 .Elem 指针类型
	iElem := reflect.ValueOf(i).Elem()

	// [2] 设置字段
	iElem.FieldByName("Num1").SetInt(88)
	iElem.FieldByName("Num2").SetInt(99)

	// [3] 打印字段 打印 Tag
	for i := 0; i < iElem.NumField(); i++ {
		fmt.Printf("iElem.Field(i) : %v\n", iElem.Field(i))
		/*
			iElem.Field(i) : 88
			iElem.Field(i) : 99
		 */
	}
	iTypeElem := reflect.TypeOf(i).Elem()
	for i := 0; i < iTypeElem.NumField(); i++ {
		fmt.Printf("iTypeElem.Field(i).Tag.Get(\"json\") : %v\n", iTypeElem.Field(i).Tag.Get("json"))
		/*
			iTypeElem.Field(i).Tag.Get("json") : xhh_num1
			iTypeElem.Field(i).Tag.Get("json") : xhh_num2
		 */
	}

	// [4] 调用方法
	fmt.Printf("iElem.NumMethod() = %d\n", iElem.NumMethod())

	rGetAdd := iElem.MethodByName("GetAdd")

	var params []reflect.Value
	params = append(params, reflect.ValueOf("xhh"))
	rets := rGetAdd.Call(params)
	// rets  88 + 99 = 187
	fmt.Printf("rets  %d + %d = %d\n",
		iElem.FieldByName("Num1").Int(),
		iElem.FieldByName("Num2").Int(),
		rets[0].Int())
}

func main() {
	nums := Nums{
		Num1: 11,
		Num2: 22,
	}
	fmt.Printf("not edit.Num1:%d  .Num2:%d\n", nums.Num1, nums.Num2)
	editNums(&nums)
	fmt.Printf("after edit.Num1:%d  .Num2:%d\n", nums.Num1, nums.Num2)
}

/*
	not edit.Num1:11  .Num2:22
	iElem.Field(i) : 88
	iElem.Field(i) : 99
	iTypeElem.Field(i).Tag.Get("json") : xhh_num1
	iTypeElem.Field(i).Tag.Get("json") : xhh_num2
	iElem.NumMethod() = 1
	[xhh] is coming func (nums Nums) GetAdd(info string) int
	rets  88 + 99 = 187
	after edit.Num1:88  .Num2:99
 */

new 新的变量

package main

import (
	"fmt"
	"reflect"
)

type Dog struct {
	Name string
	Age int
}

// func (v Value) Elem() Value
/*
	Elem返回v持有的接口保管的值的 Value封装,或者v持有的指针指向的值的 Value封装。
	如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
 */


func TestCreateDog() {
	var (
		dogPtr *Dog
		dogT reflect.Type
		dogV reflect.Value
	)
	// [1] 获取类型 *Dog
	dogT = reflect.TypeOf(dogPtr)
	fmt.Printf(".Kind() : %v\n", dogT.Kind().String()) // ptr 类型

	// [2] 指向 持有的类型 struct
	dogT = dogT.Elem()
	fmt.Printf(".Kind() : %v\n", dogT.Kind().String()) // struct 类型

	// [3] 返回Value类型 持有Type类型 新申请的0指针
	// 这里已经得到了反射的 ptr 但是未得到对象 *Dog 去指向他
	dogV = reflect.New(dogT)
	fmt.Printf("dogV.Kind() : %v\n", dogV.Kind().String()) // ptr 种类  Value类型

	// 类型 均为 reflect.Value  种类Kind 有ptr、Struct
	// [使用的是 ptr种类  需要 .Elem()拿到值]---------------------------------
	dogV.Elem().FieldByName("Name").SetString("abc")
	dogV.Elem().FieldByName("Age").SetInt(18)

	fmt.Printf("dog.Name = %v dog.Age = %v\n",
		dogV.Elem().FieldByName("Name"), // ptr 种类通过 .Elem()拿到值
		dogV.Elem().FieldByName("Age"))
	// [使用的是 ptr种类  需要 .Elem()拿到值]---------------------------------


	// [4] 得到对象 指针类型 ptr
	dogPtr = dogV.Interface().(*Dog) // *Dog
	fmt.Printf("dog.Name = %v dog.Age = %v\n", dogPtr.Name, dogPtr.Age)

	// [ptr种类通过 .Elem()转为 struct种类  直接访问或者修改]--------------------
	dogV = dogV.Elem()               // struct 种类 把dogV从ptr种类转为 struct种类
	fmt.Printf("dogV.Kind() : %v\n", dogV.Kind().String()) // struct 种类
	fmt.Printf("dog.Name = %v dog.Age = %v\n",
		dogV.FieldByName("Name"),
		dogV.FieldByName("Age"))
	// [ptr类型通过 .Elem()转为 Value类型  直接访问或者修改]--------------------

}

func main() {
	TestCreateDog()
}

/*
	.Kind() : ptr
	.Kind() : struct
	dogV.Kind() : ptr
	dog.Name = abc dog.Age = 18
	dog.Name = abc dog.Age = 18
	dogV.Kind() : struct
	dog.Name = abc dog.Age = 18
 */

你可能感兴趣的:(【Golang】,golang,开发语言,后端)