**反射:**在编译时静态类型语言中实现动态特性的一种机制。
Go语言通过反射提供了一种在运行时检查类型和访问类型成员的能力,它可以在不知道具体类型的情况下,动态地获取和修改变量的值、调用方法等。
使用场景:
反射中重要的函数
注意:变量、interface{} 和 reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。
var name string = "John"
var i interface{} = name
fmt.Printf("%T %v\n", i, i) // 输出: string John
var j interface{} = "Tom"
var name2 string
name2, ok := j.(string)
if ok {
fmt.Printf("%T %v\n", name2, name2) // 输出: string Tom
} else {
fmt.Println("conversion failed")
}
函数:
func ValueOf(i interface{}) Value
作用:
ValueOf返回一个初始化为 i 接口保管的具体值的Value,ValueOf(nil)返回Value零值。
var age int = 42
value := reflect.ValueOf(age)
valueInt := value.Interface()
age2, ok := valueInt.(int)
if ok {
fmt.Printf("%T %v\n", age2, age2) // 输出: int 42
} else {
fmt.Println("conversion failed")
}
详细讲解一下:
首先定义了一个 age
变量,类型为 int
,并赋值为 42
。接下来使用 reflect.ValueOf()
函数将 age
转换为 reflect.Value
类型,并将结果赋值给变量 value
。
reflect.Value
是反射库 reflect
提供的一个类型,用于存储和操作变量的信息。它包含了变量的值以及其相关的类型信息。
为了能够方便地对变量进行操作,我们可以通过 reflect.Value
调用其 Interface()
方法,将其转换为 interface{}
类型的变量。这样就可以得到原本变量的值,只不过类型变为了 interface{}
。将结果赋值给变量 valueInt
。
var score float32 = 98.5
value2 := reflect.ValueOf(score)
fmt.Printf("%T %v\n", value2, value2) // 输出: reflect.Value
var flag bool = true
value3 := reflect.ValueOf(flag)
valueInterface := value3.Interface()
flag2, ok := valueInterface.(bool)
if ok {
fmt.Printf("%T %v\n", flag2, flag2) // 输出: bool true
} else {
fmt.Println("conversion failed")
}
type Student struct {
Name string
Age int
score float64
}
func main() {
stu := Student{"Tom", 22, 88.4}
// 结构体 转 interface{}
var i interface{} = stu
fmt.Printf("i的类型:%T ,值:%v\n", i, i)
// 结构体 转 reflect.Value
value := reflect.ValueOf(stu)
fmt.Printf("value的类型:%T ,值:%v\n", value, value)
// reflect.Value 转 结构体
valueInterface := value.Interface()
// 断言,看看是否转换成功
flag1, ok := valueInterface.(Student)
if ok {
fmt.Printf("flag1的类型:%T ,值:%v\n", flag1, flag1)
} else {
fmt.Println("conversion failed")
}
}
输出结果:
i的类型:main.Student ,值:{Tom 22 88.4}
value的类型:reflect.Value ,值:{Tom 22 88.4}
flag1的类型:main.Student ,值:{Tom 22 88.4}
补充:
常量介绍
注意事项:
Golang 中 没有常量名必须字母大写的规范,比如:TAX_RATE等
常量仍然通过首字母的大小写来控制常量的访问范围。
反射的注意事项:
reflect.Value.Kind,获取变量的类别,返回的是一个常量。
方法:func (k Kind) String() string
func main() {
stu := Student{"Tom", 18, 88.1}
stuType := reflect.TypeOf(stu)
kind := stuType.Kind()
fmt.Println("kind=", kind) // 输出:kind= struct
}
Type是类型,Kind是类别,Type 和 Kind 可能是相同的,也可能是不同的。
比如:var num int = 10,num的Type是int,Kind也是int
比如:var stu Student stu的Type是 包名.Student,Kind是struct
通过反射可以让变量在interface[]和Reflect.Value之间相互转换
使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如 x 是 int,那么就应该使用reflect.Value(x).Int(),而不能使用其他的,否则报panic。
通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到reflect.Value.Elem()方法。
方法:
func (v Value) Elem() Value
作用:
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
func reflect01(i interface{}) {
// 2. 获取到reflect.Value
rVal := reflect.ValueOf(i)
// 看看 rVal的Kind是啥
fmt.Printf("rVal kind=%v\n", rVal.Kind())
// 3.通过这里直接设置,下面num的值
// rVal.SetInt(20) // 报错,这里的rVal是个指针
rVal.Elem().SetInt(20) // 成功
}
func main() {
var num int = 10
reflect01(&num)
fmt.Println("num=", num)
}
案例:使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值。
//定义了一个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
过程并不复杂,主要的是TetsSturct函数里面的内容,就是徐徐渐进,直到获得对应的值。
这里需要前置知识,计算机网络部分的知识,如果没有,建议去好好看一下,再往下学~~
服务端处理流程:
客户端处理流程:
简单的程序示意图:
服务器端功能:
**官方文档:**https://studygolang.com/pkgdoc
入门案例代码里的一些包,结构体,函数等提前预告:
Listener是一个用于面向流的网络协议的公用的网络监听器接口。多个线程可能会同时调用一个Listener的方法。
type Listener interface {
// Addr返回该接口的网络地址
Addr() Addr
// Accept等待并返回下一个连接到该接口的连接
Accept() (c Conn, err error)
// Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。
Close() error
}
**函数:**func Listen(net, laddr string) (Listener, error)
**作用:**返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
“tcp”、“tcp4”、“tcp6”、“unix"或"unixpacket”。参见Dial函数获取laddr的语法。
Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法。
type Conn interface {
// Read从连接中读取数据
// Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Read(b []byte) (n int, err error)
// Write从连接中写入数据
// Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真
Write(b []byte) (n int, err error)
// Close方法关闭该连接
// 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误
Close() error
// 返回本地网络地址
LocalAddr() Addr
// 返回远端网络地址
RemoteAddr() Addr
// 设定该连接的读写deadline,等价于同时调用SetReadDeadline和SetWriteDeadline
// deadline是一个绝对时间,超过该时间后I/O操作就会直接因超时失败返回而不会阻塞
// deadline对之后的所有I/O操作都起效,而不仅仅是下一次的读或写操作
// 参数t为零值表示不设置期限
SetDeadline(t time.Time) error
// 设定该连接的读操作deadline,参数t为零值表示不设置期限
SetReadDeadline(t time.Time) error
// 设定该连接的写操作deadline,参数t为零值表示不设置期限
// 即使写入超时,返回值n也可能>0,说明成功写入了部分数据
SetWriteDeadline(t time.Time) error
}
**函数:**func Dial(network, address string) (Conn, error)
**作用:**在网络network上连接地址address,并返回一个Conn接口。可用的网络类型有:
“tcp”、“tcp4”、“tcp6”、“udp”、“udp4”、“udp6”、“ip”、“ip4”、“ip6”、“unix”、“unixgram”、“unixpacket”
对TCP和UDP网络,地址格式是host:port或[host]:port,参见函数JoinHostPort和SplitHostPort。
我们想写一个服务器来监听
创建一个server.go文件,作为服务器,简单写一下服务器的监听功能
func main() {
fmt.Println("服务器开始监听。。。")
// net.Listen("tcp", "0.0.0.0:8888")
// 1. tcp 表示使用网络协议是tcp
// 2. 0.0.0.0:8888 表示在本地监听 8888端口
listen, err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listen.Close() // 延时关闭listen
// 循环等带客户端来连接我
for {
// 等待客户端连接
fmt.Println("等待客户端来连接。。。")
// Accept等待并返回下一个链接到该接口的连接
conn, err := listen.Accept()
if err != nil {
// 连接失败
fmt.Println("Accept() err=", err)
} else {
// 连接成功
fmt.Printf("Accept() suc con=%v\n", conn)
}
// 这里准备起一个协程,为客户端服务
}
}
使用net.Listen()
函数创建一个监听器,监听0.0.0.0:8888地址。错误处理会打印出错误信息并返回。
使用defer
关键字延迟关闭监听器。**注意:**一定要关闭,不然会一直占用资源。
进入无限循环,等待客户端的连接。在每次循环中,使用listen.Accept()
函数接受客户端的连接请求。如果连接成功,则会打印连接信息。如果出现错误,则会打印错误信息。
测试一下:
使用telnet,可以测试这个服务器能不能监听,telnet需要下载,网上有教程,去搜搜
我们运行这个server.go,下面会显示:
服务器开始监听。。。
等待客户端来连接。。。
然后,cmd,打开命令行,输入:telnet 127.0.0.1 8888
,就会进入telnet,然后我们看一下server.go
从命令行就可以看出,连接成功过~~
上一节,我们测试是否能够连接,下面我们写个客户端发送消息给服务器
创建client文件夹作为客户端,然后创建client.go文件
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("client dial err=", err)
return
}
//功能一:客户端可以发送单行数据,然后就退出
reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入[终端]
for {
//从终端读取一行用户输入,并准备发送给服务器
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("readString err=", err)
}
//如果用户输入的是 exit就退出
line = strings.Trim(line, " \r\n")
if line == "exit" {
fmt.Println("客户端退出..")
break
}
//再将line 发送给 服务器
_, err = conn.Write([]byte(line + "\n"))
if err != nil {
fmt.Println("conn.Write err=", err)
}
}
}
然后客户端写完之后,我们去server里写协程:
func process(conn net.Conn) {
//这里我们循环的接收客户端发送的数据
defer conn.Close() //关闭conn
for {
//创建一个新的切片
buf := make([]byte, 1024)
//conn.Read(buf)
//1. 等待客户端通过conn发送信息
//2. 如果客户端没有wrtie[发送],那么协程就阻塞在这里
//fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
n, err := conn.Read(buf) //从conn读取
if err != nil {
fmt.Printf("客户端退出 err=%v", err)
return //!!!
}
//3. 显示客户端发送的内容到服务器的终端
fmt.Print(string(buf[:n]))
}
}
这样就写好啦!!~~
下一步,我们先运行server,再运行client,测试一下~
然后,我们下面client输入hello world
并且还能退出,在终端输入exit退出
完美~~~~
这样就Go学习基础快速入门就基本完成啦!!!!后面将会学习与MySQL的连接和Redis的连接,冲冲冲!!!