GO语言中包的使用
- 一个目录下的go文件归属一个包,package的声明需要一致
- package声明的包和对应的目录名可以不一致,但是我们一般不这么做
- 包可以嵌套(和Java一样)
- 同包下的函数不需要导包(和Java一样)
- main包,main()函数所在的包,其他包不能导入
- 导入包的时候,路径要从
$GIPATH/src
下开始写(就是项目文件夹名称也要写上)
go build
的包,生成的.a
文件只存在于临时目录
go install
的包,会在$GOPATH/pkg/对应平台系统
目录里面
另外,如果go build
的是main()
函数,其依赖的包会在$GOPATH/pkg/对应平台系统
目录生成.a
文件,其实是因为,编译依赖包用的是go install
命令
点操作
点操作的含义就是这个包导入之后在你调用这个包的函数的时候,可以省略前面的包名
package main
import (
. "fmt"
)
func main() {
Printf("%v", 100)
}
起别名
可以把报名命名成另一个容易记忆的名字
package main
import (
f "fmt"
)
func main() {
f.Printf("%v", 100)
}
_ 操作
如果仅仅需要导入包时执行初始化函数,并不需要包中的其他函数,则可以在导入包的时候匿名导入
import (
_ "packagename"
)
init 函数
- init() 和 main() 时 go 语言中的保留函数,init() 函数用来初始化,在导入一个包的时候如果存在 init() 函数,则会先执行(导包过程中就会执行,而不是使用的时候才执行)
- init() 函数无参数无返回值,该函数只能由 go 程序自动调用,无法人为调用
- init() 函数可以重复定义,即使是在同一个 go 文件中。导包执行的时候,哪个在上面先执行哪个
- 同一个 package 的不同文件,将文件名按照字符串从小到大进行排序,之后按顺序调用各文件中的 init() 函数
- 对于不同 package 的文件,按照 import 的先后顺序执行
- 如果 package 之间存在依赖关系,调用顺序为最后被依赖的最先被调用
- 避免出现循环import
- 一个包被其他多个包 import,但只能被初始化一次
package utils
import "fmt"
func init() {
fmt.Println(1)
}
func init() {
fmt.Println(2)
}
func init() {
fmt.Println(3)
}
func Test() {
}
管理外部包
执行go get github.com/go-sql-driver/mysql
,会将源码下载到$GOPATH/src
目录下
(也可以自己手动下载,手动复制到$GOPATH/src
目录下)
常用包
时间 - time
package main
import (
"fmt"
"time"
)
func main() {
t1 := time.Now()
t2 := time.Date(2020, 12, 12, 12, 12, 12, 12, time.Local)
// 时间格式化:必须用"2006-01-02 15:04:05这里的数值(真是不要脸)
fmt.Println(t1.Format("2006-01-02 15:04:05"))
fmt.Println(t2.Format("2006-01-02 15:04:05"))
// 字符串转时间
if t3, err := time.Parse("2006-01-02 15:04:05", "1999-12-12 00:00:00"); err == nil {
fmt.Println(t3)
} else {
fmt.Println(err)
}
// 常用方法
fmt.Println(t1.Date())
fmt.Println(t1.Clock())
fmt.Println(t1.Month())
fmt.Println(t1.Hour())
fmt.Println(t1.Second())
fmt.Println(t1.Day())
fmt.Println(t1.Year())
fmt.Println(t1.Unix()) // 时间戳,秒
fmt.Println(t1.UnixNano()) // 时间戳,纳秒
// 通过Add方法可以获取指定时间段之后(或者前,用负号)的时间
t4 := t1.Add(time.Second * 60)
fmt.Println(t4)
// 以后到了channel再看有什么用
t5 := t1.After(t1)
fmt.Println(t5)
time.Sleep(time.Second * 10)
}
文件 - file、io、os
基本操作
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// 获取文件信息
fileInfo, err := os.Stat("Main.go")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("文件名称:", fileInfo.Name())
fmt.Println("文件大小:", fileInfo.Size())
fmt.Println("文件权限:", fileInfo.Mode())
fmt.Println("修改时间:", fileInfo.ModTime().Format("2006-01-02 15:04:05"))
}
// 判断是否为绝对路径
fmt.Println(filepath.IsAbs("D:\GOPATH\src"))
fmt.Println(filepath.IsAbs("Main.go"))
// 获取绝对路径
fmt.Println(filepath.Abs("Main.go"))
// 目录拼接(这里可以用..来获取上级目录)
fmt.Println(filepath.Join("D:\GOPATH\src", ".."))
fmt.Println(filepath.Join("D:\GOPATH\src", "go-test"))
// 创建目录
fmt.Println(os.Mkdir("test", os.ModePerm))
fmt.Println(os.MkdirAll("D:\test", os.ModePerm)) // 创建完整路径
// 创建文件:如果文件不存在就会创建,如果文件已存在就会截断文件(把文件清空)
file_1, _ := os.Create("test.txt") // 创建文件默认权限是0666,可读可写不可执行
fmt.Println(file_1)
// 打开文件:Open函数打开文件为只读模式
file_2, _ := os.Open("test.txt")
fmt.Println(file_2)
// 已指定模式打开文件(第三个参数是如果不存在,创建一个文件时,指定其权限)
file_3, _ := os.OpenFile("test.txt", os.O_RDWR, os.ModePerm)
fmt.Println(file_3)
// 关闭文件
fmt.Println(file_1.Close())
fmt.Println(file_2.Close())
fmt.Println(file_3.Close())
// Remove函数只能删除一级:如果有嵌套需要先删除最底层的文件或文件夹
// RemoveAll可以直接删除任意路径
// 删除文件
fmt.Println(os.Remove("test.txt"))
//fmt.Println(os.RemoveAll("全路径"))
// 删除目录
fmt.Println(os.Remove("test"))
//fmt.Println(os.RemoveAll("全路径"))
}
io基本操作
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
)
func main() {
testIOWriter("test.txt")
testIOReader("test.txt")
testCopyFile("E:\Pictures\15030Q05046-1.jpg", "test_1.jpg")
testIOCopy("E:\Pictures\15030Q05046-1.jpg", "test_2.jpg")
testIOUtilCopy("E:\Pictures\15030Q05046-1.jpg", "test_3.jpg")
testSeek("test.txt")
}
func testIOReader(file string) {
file_1, _ := os.OpenFile(file, os.O_RDONLY, os.ModePerm)
defer file_1.Close()
bs_1 := make([]byte, 10, 10)
for {
length_1, err := file_1.Read(bs_1)
if length_1 == 0 || err == io.EOF {
fmt.Println("
文件已读取完毕~")
break
}
fmt.Print(string(bs_1[:length_1]))
}
}
func testIOWriter(file string) {
file_1, _ := os.OpenFile(file, os.O_CREATE|os.O_APPEND, os.ModePerm)
defer file_1.Close()
_, _ = file_1.Write([]byte("hello go~
"))
_, _ = file_1.WriteString("test test ~
")
fmt.Println("文件写入完毕:", file_1)
}
// 通过io实现文件复制
func testCopyFile(src, dst string) {
os.RemoveAll(dst)
fileInfo, _ := os.Stat(src)
srcFile, _ := os.Open(src)
defer srcFile.Close()
dstFile, _ := os.OpenFile(dst, os.O_CREATE|os.O_APPEND, os.ModePerm)
defer dstFile.Close()
bs := make([]byte, 1024*10, 1024*10)
// 测试一下图片复制一半的效果
total := 0
for {
length, err := srcFile.Read(bs)
total += length
if total >= int(fileInfo.Size()/2) || length == 0 || err == io.EOF {
fmt.Println("
文件已复制完毕")
break
}
dstFile.Write(bs[:length])
}
}
func testIOCopy(src, dst string) {
os.RemoveAll(dst)
srcFile, _ := os.Open(src)
defer srcFile.Close()
dstFile, _ := os.OpenFile(dst, os.O_CREATE|os.O_APPEND, os.ModePerm)
defer dstFile.Close()
io.Copy(dstFile, srcFile)
io.CopyBuffer(dstFile, srcFile, make([]byte, 1024*100, 1024*100))
}
// ioutil包,封装了一系列简单的io操作,一般用来操作小文件比较合适,不适合大的文件操作,因为它是一次操作的,容易出现内存溢出
func testIOUtilCopy(src, dst string) {
os.RemoveAll(dst)
bs, err := ioutil.ReadFile(src)
if err != nil {
fmt.Println("文件读取异常:", err)
} else {
ioutil.WriteFile(dst, bs, os.FileMode(0666))
fmt.Println("文件复制完毕")
}
}
// 通过Seek接口可以实现断点续传(没看出来有什么卵用)
func testSeek(file string) {
/*
Seek(offset int64, whence int) (int64, error)
SeekStart = 0 // seek relative to the origin of the file
SeekCurrent = 1 // seek relative to the current offset
SeekEnd = 2 // seek relative to the end
*/
file_1, _ := os.Open(file)
defer file_1.Close()
// 光标当前位置向后偏移,其后执行read或者write方法
file_1.Seek(4, io.SeekCurrent)
bs := make([]byte, 1024, 1024)
length, _ := file_1.Read(bs)
fmt.Println(string(bs[:length]))
}
bufio
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
testBufIOWriter("test.txt")
testBufIOReader("Main.go")
}
func testBufIOReader(str string) {
file, _ := os.Open(str)
defer file.Close()
reader := bufio.NewReader(file)
//line, _, _ := reader.ReadLine() // 不推荐使用,不一定可以读取完整的一行
line, err := reader.ReadString('
') // 推荐使用这种方法读取整行
if err == io.EOF {
fmt.Println("文件已读取完成")
} else {
fmt.Println(line)
}
// 结合标准io输入流,获取控制台输入
stdReader := bufio.NewReader(os.Stdin)
fmt.Println(stdReader.ReadString('
'))
}
func testBufIOWriter(str string) {
file, _ := os.OpenFile(str, os.O_CREATE|os.O_APPEND, os.ModePerm)
defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("testBufIOWriter hello go ~
")
writer.Flush() // 必须要刷新才会真正写入磁盘,否则可能写不进去(只有缓冲区满了才会自己写一次)
}
iouitl
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
bs, _ := ioutil.ReadFile("Main.go")
fmt.Println(string(bs)) // 一次读完,不适合大文件读取
ioutil.WriteFile("test.txt", bs, os.FileMode(0666)) // 注意:会清空原文件
fileInfos, _ := ioutil.ReadDir("D:\")
for _, info := range fileInfos {
fmt.Println(info.Name(), info.IsDir())
}
// 第一个参数是一个目录,必须存在,第二个参数是生成的临时文件夹前缀
// 生成的临时目录需要手动去删除
tempDir, _ := ioutil.TempDir("utils", "ttt-")
defer func() {
err := os.Remove(tempDir)
if err != nil {
fmt.Println("删除临时目录失败:", err)
}
}()
fmt.Println(tempDir)
tempFile, _ := ioutil.TempFile("utils", "fff-")
defer func() {
tempFile.Close()
err := os.Remove(tempFile.Name())
if err != nil {
fmt.Println("删除历史文件失败:", err)
}
}()
fmt.Println(tempFile.Name())
}
并发
初始 goroutine
package main
import (
"fmt"
"time"
)
func main() {
/*
go中的协程(goroutine),只需要用关键字go即可
1. go关键字执行的函数,其返回值会被忽略,所以最好就不要有返回值
2. main的goroutine执行结束之后,其他的goroutine也会结束
主goroutine的作用:
0. 设定每个goroutine所能申请的栈空间的最大尺寸(32位机器是250M,64位机器是1G)。如果有某个goroutine的栈空间尺寸大于这个限制,系统就会引发一个栈溢出(stack overflow)运行时panic,随后结束程序
1. 创建一个特殊的defer语句,用于主goroutine结束之后进行必要的善后处理。(因为主goroutine也可能非正常的结束)
2. 启动专门用于在后台清扫内存垃圾的goroutine,并设置GC可用标识
3. 执行main包中的init函数
4. 执行main函数
执行main函数后,它还会检查主goroutine是否引发了运行时panic,并进行必要的处理,最后主goroutine会结束自己以及当前进程的运行
*/
for i := 0; i < 10000; i++ {
go test_1()
go test_2()
}
time.Sleep(time.Second * 30)
}
func test_1() {
for {
fmt.Println("test_1")
time.Sleep(time.Second)
}
}
func test_2() {
for {
fmt.Println(" test_2")
time.Sleep(time.Second)
}
}
goroutine 并发模型
GO语言并发模型中的四个重要结构,分别是M、G、P、Sched,前三个定义在runtime.h,Sched定义在proc.h
- Sched结构就是调度器,它为维护有存储M和G的队列以及调度器的一些状态信息
- M结构是Machine,系统线程,它有操作系统管理的,goroutine就是运行在M之上的。M是一个很大的结构,里面维护小对象内存caceh(mcache)、当前执行的goroutine、随机数发生器等非常多的信息
- P结构是Processos,处理器,它的主要用途是用来执行goroutine的,它维护了一个goroutine队列,即runqueue。Processos是让我们从N:1调度到M:N调度的重要部分
- G是goroutine实现的核心结构,它包含了栈、指令指针、以及其他对调度goroutine很重要的信息,例如其阻塞的channel
(Processor的数量在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有GOMAXPROCS个线程在运行go代码)
在单核处理器的场景下,所有goroutine运行在同一个M系统线程中,每一个M系统线程维护一个Processor,任何时刻,一个Processor中只有一个goroutine,其他goroutine在runqueue中等待。一个goroutine运行完自己的时间片后,让出上下文,回到runqueue中。 多核处理器的场景下,为了运行goroutines,每个M系统线程会持有一个Processor。
当正在运行的goroutine阻塞的时候,例如进行系统调用,会再创建一个系统线程(M1),当前的M线程放弃了它的Processor,P转到新的线程中去运行。
当其中一个Processor的runqueue为空,没有goroutine可以调度。它会从另外一个上下文偷取一半的goroutine。
runtime
package main
import (
"fmt"
"runtime"
)
func init() {
// GO的1.8版本之前可以设置一下,1.8版本之后不需要设置了
// 因为:如果你没有特意配置runtime.SetMaxThreads,那么在可没有复用的线程下会一直创建新线程。 golang默认的最大线程数10000个线程,这个是硬编码。 如果想要控制golang的pthread线程数可以使用 runtime.SetMaxThreads() 。
// 如果要设置Processor数量,最好时在main包的init函数里面
runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
fmt.Println(runtime.GOROOT())
fmt.Println(runtime.GOOS)
fmt.Println(runtime.NumCPU())
// 当前goroutine让出CPU时间片,类似Java中的Thread.yield();
runtime.Gosched()
// 终止当前的goroutine(defer还是会执行的)
runtime.Goexit()
}
锁
GO语言不推荐使用锁
package main
import (
"fmt"
"strconv"
"sync"
)
func main() {
/*
go语言中的并发问题
go语言也支持通过上锁的方式,某一段时间只允许一个goroutine来访问这个共享数据,当前goroutine访问完毕解锁后,其他goroutine才能访问
需要借助sync包下的锁操作
但是不建议使用:不要以共享内存的方式通信,而要以通信的方式去共享内存
*/
testWaitGroup() // 同步等待组
testMutex() // 互斥锁
testRWMutex() // 读写锁
}
// 同步等待组,类似Java中的CountDownLatch
func testWaitGroup() {
//var wg sync.WaitGroup // 这个写法也行
wg := sync.WaitGroup{} // WaitGroup的成员都不需要赋值,直接用零值即可
wg.Add(10)
for i := 0; i < 10; i++ {
go func(s string) {
fmt.Println(s)
wg.Done()
}(strconv.Itoa(i))
}
wg.Wait()
fmt.Println("等待完毕")
}
// 互斥锁,类似synchronized
func testMutex() {
wg := sync.WaitGroup{}
wg.Add(10)
mx := sync.Mutex{}
var count int // go语言中并不需要初始化,带有默认0值
for i := 0; i < 10; i++ {
go func(s string) {
for j := 0; j < 10000; j++ {
mx.Lock()
count++
mx.Unlock()
}
wg.Done()
}(strconv.Itoa(i))
}
wg.Wait()
fmt.Println(count) // 不加锁的话这里的值就会混乱
}
// 读写锁
func testRWMutex() {
/*
RWMutex时基于Mutex实现的
读锁可以重复获取
但是写锁一旦获取,释放前既不能获取读锁也不能获取写锁
读锁释放前无法获取写锁?
*/
wg := sync.WaitGroup{}
wg.Add(10)
rx := sync.RWMutex{}
for i := 0; i < 10; i++ {
go func(s string) {
for j := 0; j < 10; j++ {
rx.RLock() // 读锁
fmt.Println("读锁获取", s)
rx.RUnlock() // 读解锁
fmt.Println("读锁释放", s)
rx.Lock() // 写锁
fmt.Println("写锁获取", s)
rx.Unlock() // 写解锁
fmt.Println("写锁释放", s)
}
wg.Done()
}(strconv.Itoa(i))
}
wg.Wait()
}
channel 通道
通道的注意点:
- 用于goroutine,传递消息的
- 通道必须有关联的数据类型,nil chan是没有意义的
- 阻塞(chan <- data,向通道写数据,是阻塞的,直到另一个goroutine中读取了数据才会解除阻塞;data <- chan,从通道读数据,也是阻塞的,直到另一个goroutine中写入了数据才会解除阻塞)
- 本身channel就是同步的,意味着同一时间只有一条goroutine来操作
- 通道时goroutine之间的连接,所以通道的发送和接受必须在不同的goroutine中
(基本就是要先开始读再开始发)
初识通道
package main
import (
"fmt"
)
func main() {
/*
通道用于goroutine之间通信,每个通道都有与其相关的类型(该通道允许传输的数据类型)
声明通道:var 变量名称 chan 数据类型
创建通道:变量名称 = make(chan 数据类型)
通道是引用数据类型的,数据传递时引用传递
*/
chan_1 := make(chan string)
go func() {
// 向通道写入数据
chan_1 <- "你好呀"
}()
// 从通道读取数据,是阻塞式的
data := <- chan_1
fmt.Println(data)
}
关闭通道和通道上范围循环
package main
import "fmt"
func main() {
/*
关闭通道和通道上范围循环
*/
ch := make(chan int)
go func() {
for i := 0; i < 10000; i++ {
ch <- i // 每写一个就会阻塞一次,直到其他goroutine读取了这个数据才会解除阻塞
}
// 发送者可以通过关闭通道,来通知接收方不会有更多的数据被发送到channel上
close(ch)
}()
// 方式一:
//for {
// data, ok := <- ch // ok位false表示尝试同一个已经关闭的通道获取数据,ok位true表示成功从通道获取到了一个数据
// if ok {
// fmt.Println(data)
// } else {
// fmt.Println("发送完毕")
// break
// }
//}
// 方式二:也可以使用for - range简化写法
for i := range ch {
fmt.Println(i)
}
fmt.Println("发送完毕")
}
缓冲通道
package main
import "fmt"
func main() {
/*
非缓冲通道:make(chan T)
一次发送一次接收,都是阻塞的
缓冲通道:make(chan T, capacity)
发送:缓冲区数据满了才会阻塞
接收:缓冲区数据空了才会阻塞
*/
chbuf := make(chan int, 1024)
fmt.Println(len(chbuf), cap(chbuf))
go func() {
for i := 0; i < 10000; i++ {
chbuf <- i
}
close(chbuf)
}()
for i := range chbuf {
fmt.Println(len(chbuf), cap(chbuf), i)
}
}
定向通道
package main
import "fmt"
func main() {
/*
定向通道(单项通道)
之前的demo都用的双向通道,既可以读又可以写
ch1 := make(chan <- int) // 只能写不能读
ch2 := make(<- chan int) // 只能读不能写
定向通道一般作为参数,用来限制函数中的某些操作(比如在某个函数中只能写或者只能读),实际传入的参数往往还是双向通道(单向通道形参可以接收双向通道实参)
*/
ch := make(chan int, 1024)
go func() {
for i := 0; i < 10000; i++ {
testIn(ch, i)
}
close(ch)
}()
testOut(ch)
}
func testIn(ch chan<- int, i int) {
ch <- i
}
func testOut(ch <-chan int) {
for i := range ch {
fmt.Println(i)
}
}
time包中的通道相关函数 - 定时器
package main
import (
"fmt"
"time"
)
func main() {
/*
time包中的通道相关函数 - 定时器
定时器:标准库中的Timer让用户可以定义自己的超时逻辑,尤其是在应对select处理多个channel的超时、单channel读写超时等情形时尤为方便
Timer是一次性的时间触发事件,与Ticker不同,Ticker是按照一定时间间隔持续触发时间事件
t1 := time.NewTimer(d)
t2 := time.AfterFunc(d, f)
t3 := time.After(d)
Timer的三要素:
定时时间 d
触发动作 f
事件channel t.C
*/
//test_1() // time.NewTimer
//test_2() // time.After
test_3() // time.AfterFunc
}
func test_1() {
t1 := time.NewTimer(3 * time.Second)
fmt.Printf("t1: %T
", t1)
go func() {
fmt.Println(time.Now())
// 这里会阻塞三秒,通道中的数据是3s之后时间
fmt.Println(<-t1.C)
}()
time.Sleep(4 * time.Second)
flag := t1.Stop() // 计时器可以提前停止(如果计时器已经停止,则返回false,如果计时器尚未停止则返回true并停止计时器)
if flag {
fmt.Println(111)
} else {
fmt.Println(222)
}
}
func test_2() {
t2 := time.After(3 * time.Second) // 相当于t1.C
fmt.Printf("t2: %T
", t2)
go func() {
fmt.Println(time.Now())
// 这里会阻塞三秒,通道中的数据是3s之后时间
fmt.Println(<-t2)
}()
time.Sleep(4 * time.Second)
}
func test_3() {
t3 := time.AfterFunc(3 * time.Second, func() {
fmt.Println("定时器t3结束", time.Now())
})
fmt.Printf("t3: %T
", t3)
go func() {
fmt.Println(time.Now())
// 这里会阻塞三秒,然后执行上面指定的函数
<-t3.C
// 这里似乎执行不到,不知道为啥
fmt.Println("~~~~~~~~~~")
}()
time.Sleep(4 * time.Second)
flag := t3.Stop() // 计时器可以提前停止(如果计时器已经停止,则返回false,如果计时器尚未停止则返回true并停止计时器)
if flag {
fmt.Println(111)
} else {
fmt.Println(222)
}
}
select 语句
说明:
- 语法结构上有点像switch...case...(仅仅是语法结构)
- 每个case都必须是一个通信
- 所有被发送的表达式都会被求值
- 如果有多个case可以运行,select会随机公平的选择一个来执行,其他的不会执行
- 否则,如果有default语句,则执行,没有的话select将会阻塞,直到某个case可执行
- Go不会重新对channel的值进行求值
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
for {
ch1 <- "通道1"
time.Sleep(3 * time.Second)
}
}()
go func() {
for {
ch2 <- "通道2"
time.Sleep(2 * time.Second)
}
}()
for {
select {
case data := <-ch1:
fmt.Println(data)
case data := <-ch2:
fmt.Println(data)
default:
fmt.Println("没有新数据")
}
time.Sleep(1 * time.Second)
}
}
反射
初识反射
package main
import (
"fmt"
"reflect"
)
func main() {
/*
反射:
在Go中,每个interface变量都有一个pair,pair记录了实际变量的值和类型:(value, type)
TypeOf - 用来动态获取输入参数值的类型
ValueOf - 用来动态获取输入参数的值
反射的缺点:
1. 可读性差
2. 编译过程难以发现错误
3. 对性能会产生比较大的影响,往往会满一到两个数量级
*/
p := Persion{name: "zhangsan"}
v := reflect.ValueOf(p)
t := reflect.TypeOf(p)
// Kind是类别,如stauct,Type是具体的类型
fmt.Println(v.Kind(), v)
fmt.Println(t.Kind(), t)
// 空接口类型可以被任意赋值,类似与动态语言
var i interface{}
i = 1
i = "hello go"
i = Persion{name: "lisi"}
i.(Persion).show()
}
type Persion struct {
name string
}
func (p Persion) show() {
fmt.Printf("my name is %v
", p.name)
}
反射与结构体
package main
import (
"fmt"
"reflect"
)
func main() {
/*
反射三大定律:
1. 反射可以通过接口值得到反射对象
2. 反射可以从反射对象获得接口值
3. 如果需要操作一个反射变量,则其值必须可以修改
*/
test(Persion{name: "zhangsan"})
}
func test(i interface{}) {
t := reflect.TypeOf(i)
v := reflect.ValueOf(i)
fmt.Println(v)
if t.Kind() == reflect.Struct {
fmt.Println("遍历成员:", t.NumField())
for ii := 0; ii < t.NumField(); ii++ {
fmt.Println(t.Field(ii))
}
fmt.Println("遍历方法:", t.NumMethod())
for ii := 0; ii < t.NumMethod(); ii++ {
fmt.Println(t.Method(0))
}
}
}
type Persion struct {
name string // 反射可以获取私有的成员,但无法获取私有的方法
}
func (p Persion) show_1() {
fmt.Printf("my name is %v
", p.name)
}
// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2() {
fmt.Printf("my name is %v
", p.name)
}
反射 - 修改实际值
package main
import (
"fmt"
"reflect"
)
func main() {
p := Persion{Name: "zhangsan"}
v := reflect.ValueOf(&p)
elem := v.Elem() // 只有上面传入指针,这里才能正常执行,否则将出现panic
canSet := elem.CanSet() // 判断是否可以重新赋值
if canSet {
elem.FieldByName("Name").SetString("lisi") // 注意:只能修改公共的属性,无法修改私有的属性
}
p.Show_2()
}
type Persion struct {
Name string // 反射可以获取私有的成员,但无法获取私有的方法
}
func (p Persion) show_1() {
fmt.Printf("my name is %v
", p.Name)
}
// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2() {
fmt.Printf("my name is %v
", p.Name)
}
反射 - 调用方法和函数
package main
import (
"fmt"
"reflect"
)
func main() {
// 方法的反射
p := Persion{Name: "zhangsan"}
v := reflect.ValueOf(&p)
v.MethodByName("Show_1").Call(nil) // Call的参数是一个切片,如果实际函数没有值,传nil或者是空切片都行
v.MethodByName("Show_2").Call([]reflect.Value{reflect.ValueOf("aaa")}) // 创建一个切片作为函数参数,注意参数类型是reflect.Value
// 函数的反射
t := reflect.ValueOf(test)
if t.Kind() == reflect.Func {
input := make([]reflect.Value, 1, 1) // 使用make创建的切片没法直接赋值,不太方便
input[0] = reflect.ValueOf("go")
t.Call(input)
}
// Call的返回值是 []reflect.Value ,通过反射执行的函数或方法的返回值保存在这个切片中,返回有几个这里的长度就是多少
}
type Persion struct {
Name string // 反射可以获取私有的成员,但无法获取私有的方法
}
func (p Persion) Show_1() {
fmt.Printf("my name is %v
", p.Name)
}
// 注意:反射只能获取公开的方法,无法获取私有的方法
func (p Persion) Show_2(message string) {
fmt.Printf("my name is %v - %v
", p.Name, message)
}
func test(msg string) {
fmt.Println("hello", msg)
}