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.读取文件的两种方式
- 带缓冲的方式读取文件。
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("读取文件结束")
- 使用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.写文件
- 创建新文件然后写入数据。
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()
}
- 打开已经存在的文件,并将文件中的内容覆盖为其他内容。
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()
- 打开已经存在的文件,在原来的文件后面追加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()
- 打开已经存在的文件,现将文件内容输出到终端,然后再文件后面追加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.文件的拷贝
- 使用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)
}
- 判断文件是否存在。
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("不确定是否存在")
- 使用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的序列化和反序列化
- 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"`
}
- 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.单元测试
- 要测试的方法,cal.go。
func Cal(a int, b int) int {
return a + b
}
- 单元测试,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.并发和并行
- 并发:多线程在单核上运行。有10个线程在一个CPU上运行,每个线程需要执行10毫秒,进行轮训操作,从人的角度看这10个线程都在运行,但是从微观上看,某个时间点只有一个线程在执行,这就是并发。
- 并行:多线程在多核上运行。有10个线程在10个CPU上运行,每个线程需要执行10毫秒,各自在不同的CPU上执行,从人的角度看,这个10个线程都在运行;但是从微观上看,在某一个时间点,也同时有10个线程在执行,这就是并行。
- Go主线程,可以理解为线程,也可以理解为进程。
- Go协程,一个Go线程可以起多个协程,协程就是轻量级的线程,编译器进行了优化。
- 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读写数据
- 定义一个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]
- 定义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
}
标签:10,string,err,Go06,fmt,goroutine,单元测试,int,Println
From: https://www.cnblogs.com/godistance/p/17258518.html