反射
概念
正常的开发中,很少用到,因为效率低。
更多的用于开发一些脚手架,自动实现一些底层的判断。
interface{} = any 由于这种动态类型是不确定的,可能在底层代码中进行判断,从而选择使用什么类型处理。
反射机制可以在程序的运行过程中,获取到信息(变量:类型、值。结构体:字段、方法)
平时可以通过反射来修改变量的值,通过反射来调用方法。
所有的方法几乎都在reflect包里面。
类型:Type (在编程中使用Type),指代的是某一种类型
种类:Kind (反射更多时候用Kind来区分),指代的是相似的一些类型
package main
import (
"fmt"
"reflect"
)
func main() {
// 根据对象获取类型
var a float64 = 3.14
// 获取对象的类型
fmt.Println("type", reflect.TypeOf(a)) // type float64
// 获取对象的值
fmt.Println("value", reflect.ValueOf(a)) // value 3.14
// 根据对象的值获取类型和种类
v := reflect.ValueOf(a)
// Kind 获取这个值的种类,在反射中,所有数据类型的判断都是使用种类
fmt.Println(v.Kind()) // float64
if v.Kind() == reflect.Float64 {
fmt.Println(v.Float()) // 3.14
}
if v.Kind() == reflect.Int {
fmt.Println(v.Int())
}
fmt.Println(v.Type()) // float64
}
静态类型 & 动态类型
Go静态类型语言,在反射过程中,编译的时候就知道变量类型的就是静态类型,如果在运行的时候才知道类型的就是动态类型。
静态类型:变量在声明时候给他赋予类型的
var name string // string 是静态类型的
var age int // int 是静态类型的
动态类型:在运行的时候发生变化,主要考虑赋值问题
var A interface{} // interface{} 静态类型
A = 10 // interface{} 静态类型 动态类型 int
A = "zgy" // interface{} 静态类型 动态类型 string
为什么要用反射:
需要编写一个函数,但是不知道函数传递的参数是什么类型?没约定好,传入的类型太多,不能统一表示,所以用反射
在某些使用中,需要根据条件来判断具体使用哪个函数处理问题,根据用户的输入来决定,这个时候需要对函数的参数进行反射,在运行期间来动态处理。
为什么不建议使用反射:
和反射相关的代码,不方便阅读,在开发中,代码可读性很重要;
Go语言是静态类型的语言,编译器可以找出开发时候的错误,如果代码中有大量的反射代码,随时可能存在安全问题,panic,项目就终止;
反射的性能很低,相当于正常的开发,至少慢2-3个数量级。项目关键位置低耗时,一定是不能使用反射的。更多时候使用约定。
反射获取变量信息
实现通过一个对象来还原它的本身结构信息。
reflect.TypeOf:获取变量的类型Type
Type.Kind:获取种类
Type.NumField():获取变量类型中字段的数量
Type.Field(i):获取字段信息
Type.NumMethod():找到类型中方法的数量
Type.Method(i):获取方法信息
reflect.ValueOf:获取变量的值 Value
Value.Field.interface()
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Sex string
}
func (user User) Say(info string) {
fmt.Println("user say: ", info)
}
func (user User) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}
func main() {
user := User{"张三", 18, "男"}
reflectGetInfo(user)
}
// 通过反射获取变量信息
func reflectGetInfo(v interface{}) {
// 1、获取参数的类型,
getType := reflect.TypeOf(v)
fmt.Println(getType.Name()) // User
fmt.Println(getType.Kind()) // struct
// 2、获取参数值
getValue := reflect.ValueOf(v)
fmt.Println(getValue) // {张三 18 男}
// 3、通过Type获取字段信息
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface()
// 字段名:Name,字段类型:string,字段值:张三
// 字段名:Age,字段类型:int,字段值:18
// 字段名:Sex,字段类型:string,字段值:男
fmt.Printf("字段名:%s,字段类型:%s,字段值:%v\n", field.Name, field.Type, value)
}
// 4、通过Type获取结构体的方法
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
// 方法名:PrintInfo,方法类型:func(main.User)
// 方法名:Say,方法类型:func(main.User, string)
fmt.Printf("方法名:%s,方法类型:%s\n", method.Name, method.Type)
}
}
反射修改变量的值
value.CanSet()
value.SetXXX()
package main
import (
"fmt"
"reflect"
)
// 反射修改变量的值
func main() {
var num float64 = 3.14
update(&num)
fmt.Println(num) // 2.23
}
func update(v any) {
// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象
pointer := reflect.ValueOf(v)
newValue := pointer.Elem()
fmt.Println(newValue) // 3.14
fmt.Println("类型:", newValue.Type()) // float64
fmt.Println("判断该类型是否可以修改:", newValue.CanSet()) // true
// 通过反射对象给变量赋值
if newValue.Kind() == reflect.Float64 {
newValue.SetFloat(2.23)
}
if newValue.Kind() == reflect.Int {
newValue.SetInt(2)
}
}
修改结构体变量:通过属性名,来实现修改
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Sex string
}
func main() {
user := User{"zgy", 24, "男"}
value := reflect.ValueOf(&user)
if value.Kind() == reflect.Ptr {
// 获取指针对象
newValue := value.Elem()
if newValue.CanSet() {
// 如果是结构体:需要找到对象的结构体字段名
newValue.FieldByName("Name").SetString("yyk")
newValue.FieldByName("Age").SetInt(25)
newValue.FieldByName("Sex").SetString("女")
}
}
fmt.Println(user) // {yyk 25 女}
}
反射调用方法
通过反射调用user方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Sex string
}
func (user User) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}
func (user User) Say(msg string) {
fmt.Println("User 说:", msg)
}
func main() {
user := User{"zgy", 24, "男"}
value := reflect.ValueOf(user)
fmt.Printf("%s, %s\n", value.Kind(), value.Type()) // struct, main.User
// 通过反射调用方法
// 通过方法名,找到这个方法,然后调用Call()方法来执行
// 无参方法调用
value.MethodByName("PrintInfo").Call(nil) // 姓名:zgy,年龄:24,性别:男
// 有参方法调用
args := make([]reflect.Value, 1)
args[0] = reflect.ValueOf("这是反射调用有参方法")
value.MethodByName("Say").Call(args) // User 说: 这是反射调用有参方法
}
反射调用函数
package main
import (
"fmt"
"reflect"
)
// 反射调用函数
func main() {
// 通过函数名来进行反射
value1 := reflect.ValueOf(fun1)
fmt.Println(value1.Kind(), value1.Type()) // func func()
value1.Call(nil) // fun1: 无参
value2 := reflect.ValueOf(fun2)
fmt.Println(value2.Kind(), value2.Type()) // func func(int, string)
args := make([]reflect.Value, 2)
args[0] = reflect.ValueOf(1)
args[1] = reflect.ValueOf("zgy")
value2.Call(args) // fun2: 有参 i= 1 s= zgy
value3 := reflect.ValueOf(fun3)
fmt.Println(value3.Kind(), value3.Type()) // func func(int, string) string
args2 := make([]reflect.Value, 2)
args2[0] = reflect.ValueOf(1)
args2[1] = reflect.ValueOf("zgy")
resultValue := value3.Call(args2) // fun3: 有参有返回值 i= 1 s= zgy
fmt.Println("返回值:", resultValue) // 返回值: [zgy]
}
func fun1() {
fmt.Println("fun1: 无参")
}
func fun2(i int, s string) {
fmt.Println("fun2: 有参 i=", i, " s=", s)
}
func fun3(i int, s string) string {
fmt.Println("fun3: 有参有返回值 i=", i, " s=", s)
return s
}
泛型
泛型的概念
Go语言发展不断迭代,不断地优化功能
1.7 Context
1.11 modules
1.13 error嵌套
1.18 泛型(类型参数)
泛型的出现很受期待,但是很少用,使用场景不多。
打印一个切片,传递不同的参数类型(string、int)
package main
import (
"fmt"
"reflect"
)
func main() {
//is := []int{1, 3, 4}
//printSlice(is)
strs := []string{"zgy", "yyk"}
printSlice(strs)
}
// 如何实现一个方法可以打印不同类型的切片 反射
func printSlice(s interface{}) {
// 1.18版本之前,一般都是用反射来处理这种参数类型不确定的问题
fmt.Println(reflect.TypeOf(s)) // []string
// 断言 x.(T) 如果x实现了T,那么就将x转化为T类型
for _, v := range s.([]string) {
fmt.Println(v)
}
}
这种情况下传递的参数,需要做N种判断来进行适配。
泛型
思考:不限定参数的类型,让调用者自己去定义类型。
// 参数的类型是不确定的,让用户自己去指定。
// 泛型也是使用 [],和数组很相似
func printSlice[T any](s []T) {
for _, v := range s.([]string) {
fmt.Println(v)
}
}
泛型的作用:
减少重复性的代码,提高安全性。针对不同类型,写了相同的逻辑代码,就可以通过泛型来简化代码。
在1.18版本之前 通过反射来实现。泛型并不能完全取代反射。
泛型:指代多个类型(类型可以是不确定的)
泛型类型
package main
import "fmt"
type s1 []int
type s2 []float64
type s3 []float32
// 定义的结构都是一样的,只是类型不同,就需要重新定义很多的类型。
// 使用泛型,只定义一个类型就可以代表上面的所有类型
/*
1、T 就是一个占位符,T是不确定的,需要在使用的时候进行传递;
2、由于T类型不确定,需要加一些约束 int|float64|float32。告诉编译器,这个T只接受
int|float64|float32 类型;
*/
// 普通的定义类型,这个类型只能代表本身,而泛型可以实现参数类型传递
// 可以在使用的时候来定义类型
type Slice[T int | float64 | float32 | string] []T
func main() {
var a Slice[int] = []int{1, 2, 3}
fmt.Printf("%T\n", a) // main.Slice[int]
var b Slice[float64] = []float64{1.0, 2.0, 3.0}
fmt.Printf("%T\n", b) // main.Slice[float64]
var c Slice[float32] = []float32{1.0, 2.0, 3.0}
fmt.Printf("%T\n", c) // main.Slice[float32]
var d Slice[string] = []string{"1.0", "2.0", "3.0"}
fmt.Printf("%T\n", d) // main.Slice[string]
}
泛型的类型使用
package main
import "fmt"
// 泛型可以用在所有类型的地方
type MyStruct[T int|string] struct {
Id T
Name string
}
type IprintData[T int|float64|string] interface {
Print(data T)
}
type MyChan[T int|string] chan T
func main() {
// T 泛型的参数类型的属性可以远不止一个,所有的都可以泛型化
// map(int)string
// map[KEY]VALUE 类型形参(参数是不确定的) KEY、VALUE
// KEY int | string VALUE float32 | float64 约束
// MyMap [int, float32] [int, float64] [string, float32] [string, float64]
type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
var score MyMap[string, float64] = map[string]float64{
"Go": 99.9,
"Java": 89.9,
}
fmt.Printf("%T\n", score) // main.MyMap[string,float64]
}
特殊的泛型
package main
func main() {
// 特殊的泛型类型,泛型的参数是多样的,但是实际类型定义就是int
type AAA[T int | string] int
var a AAA[int] = 123
var b AAA[string] = 123
// var c AAA[string] = "123" // 底层类型是int
}
这里虽然使用了泛型,但是底层类型是int,所以无论传int还是string都可以,但是赋值,只能是int
这个案例本身没有意义。
泛型函数
单纯的泛型没有意义。和函数结合使用,可以使用调用者(调用者的类型可以自定义,就可以实现泛型)
package main
import "fmt"
type MySlice[T int | float32] []T
func main() {
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.sum()) // 10
var s1 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.4}
fmt.Println(s1.sum()) // 10.4
}
// 调用者,类型是不确定的,用户传什么,就实例化什么
func (s MySlice[T]) sum() T {
var sum T
for _, v := range s {
sum += v
}
return sum
}
泛型可以增加代码的灵活性,降低了可阅读性
package main
import "fmt"
func main() {
var a int = 2
var b int = 3
fmt.Println(Add[int](a, b)) // 5
var c float32 = 2.2
var d float32 = 3.2
fmt.Println(Add[float32](c, d)) // 5.4
// 每次写T的类型很麻烦,所以Go支持自动推导
// Go的泛型语法糖:自动推导(本质:Go编译器在实际运行中,加上了T)
fmt.Println(Add(a, b)) // T : int
fmt.Println(Add(c, d)) // T : float32
}
// 真正的Add实现,传递不同的参数都是可以适配的!Add[T] T在调用的时候需要实例化
// 这种带了类型形参的函数叫做泛型函数,极大的提高了代码的灵活性,同时也降低了代码的可阅读性!
func Add[T int | float32 | float64](a, b T) T {
return a + b
}
泛型类型(自定义类型结合泛型使用)
泛型作为接收者(实现函数的灵活变化)
泛型函数(参数是泛型)
自定义泛型
由于约束有很多,可以定义一些自己的泛型约束(本质是一个接口)
package main
import "fmt"
// 泛型的约束提取
type MyInt interface {
int | float32 | float64 | int64 // 作用于泛型的,而不是一个接口方法
}
// 自定义泛型
func main() {
var a int = 10
var b int = 20
fmt.Println(GetMaxNum(a, b))
}
func GetMaxNum[T MyInt](a, b T) T {
if a > b {
return a
}
return b
}
内置泛型类型:
any (就是一个泛型,表示了Go所有的内置类型,等价于interface{})
comparable:表示所有可以比较的类型
新符号~,和类型一起出现的,表示支持该类型的衍生类型
package main
import "fmt"
// int8 衍生类型
type int8A int8
type int8B = int8
// ~ 表示可以匹配该类型的衍生类型
type NewInt interface {
~int8
}
func main() {
var a int8A = 10
var b int8A = 20
fmt.Println(GetMax(a, b)) // 20
}
func GetMax[T NewInt](a, b T) T {
if a > b {
return a
}
return b
}
网络编程
基础知识
网络编程-Web开发基础底层的!
一切的网络开发,本质上就是服务的请求与响应处理
静态Web、动态Web
静态Web不发生变化:
html、css
提供给所有人看的,数据几乎不发生变化
动态Web,所有人看到的都不一样
淘宝:千人千面
提供给所有人的数据都不一致,会发生变化。不同时间不同地点都可能不同
Web应用程序
Web应用程序:指的是可以给浏览器访问的程序。
B/S:浏览器(天然跨平台)、服务器
只需要一个浏览器就可以了
a.html b.html 可以被外界访问到的任何一个web资源,都是存在于这个世界的某台服务器或者电脑上
url 统一资源定位符,通过他可以定位到网络上任何一个向外部开放的资源
C/S:客户端(图形化界面)、服务器
window
mac
linux
xxx
http
http超文本传输协议(图片、视频、音频、定位、地图),建立在底层TCP协议之上。默认端口:80
https安全ttp协议(加上了一个ssl,保证安全)。默认端口:443
请求-响应
https协议链接到某个服务器上;
通过传递userid,获取该用户信息;
封装到页面返回给浏览器。 响应
http编程
package main
import (
"fmt"
"net/http"
)
// 手动实现一个请求响应 net/http
// http://127.0.0.1:8080/hello
func main() {
// func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.HandleFunc("/hello", hello)
fmt.Println("项目已启动:localhost:8080")
fmt.Println("访问hello请求测试:localhost:8080/hello")
http.ListenAndServe("localhost:8080", nil)
}
// 请求和响应
func hello(resp http.ResponseWriter, req *http.Request) {
// 查看一些请求信息
fmt.Println("已收到来自浏览器的请求")
fmt.Println("请求的地址:", req.URL) // 请求的地址: /hello
fmt.Println("请求的方法:", req.Method) // 请求的方法: GET
// 响应信息给客户端
resp.Write([]byte("<h1>Go语言学习http编程</h1>"))
}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// 请求方式 请求的URL 接收响应结果
resp, _ := http.Get("http://localhost:8080/hello")
// 通过defer 关闭连接 resp.Body 响应的主体
defer resp.Body.Close()
fmt.Println(resp.Body)
fmt.Println(resp.Status) // 200 OK
fmt.Println(resp.Header) // 响应头
// 接收具体的响应内容
buf := make([]byte, 1024)
for {
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("读取出现了错误")
return
} else {
fmt.Println("读取完毕")
fmt.Println("读取的内容为:", string(buf[:n])) // <h1>Go语言学习http编程</h1>
break
}
}
}
带参数的请求
package main
import (
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
// 复杂请求
urlStr := "http://127.0.0.1:8080/login"
// 参数如何拼接到url上,参数封装为数据url.Values{}
data := url.Values{}
data.Set("username", "admin")
data.Set("password", "123456")
// 将url字符串转化为url对象,并携带参数
// func ParseRequestURI(rawURL string) (*URL, error)
urlNew, _ := url.ParseRequestURI(urlStr)
urlNew.RawQuery = data.Encode()
fmt.Println(urlNew) // http://127.0.0.1:8080/login?password=123456&username=yyk
// 发请求,参数是一个地址
resp, _ := http.Get(urlNew.String())
defer resp.Body.Close()
// 读取resp信息,返回buf
buf, _ := io.ReadAll(resp.Body)
fmt.Println(string(buf))
}
后台处理代码
package main
import (
"fmt"
"net/http"
)
// 手动实现一个请求响应 net/http
// http://127.0.0.1:8080/hello
func main() {
// func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.HandleFunc("/login", login)
fmt.Println("项目已启动:localhost:8080")
http.ListenAndServe("localhost:8080", nil)
}
func login(resp http.ResponseWriter, req *http.Request) {
// 模拟数据库中存在的一个数据
mysqlUserData := "admin"
mysqlPwdData := "123456"
fmt.Println("接收到了login请求")
// 拿到请求中的参数
urlData := req.URL.Query()
username := urlData.Get("username")
password := urlData.Get("password")
// 登录逻辑
if username == mysqlUserData {
if password == mysqlPwdData {
resp.Write([]byte("登录成功"))
} else {
resp.Write([]byte("密码错误"))
}
} else {
resp.Write([]byte("登录失败"))
}
}
表单参数获取
html表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册登录</title>
</head>
<body>
<form action="http://localhost:8080/register" method="post">
<p>用户名:<input type="text" name="username"></p>
<p> 密码:<input type="password" name="password"></p>
<input type="submit" value="注册" />
</form>
</body>
</html>
后端处理代码
package main
import (
"fmt"
"net/http"
)
// 手动实现一个请求响应 net/http
// http://127.0.0.1:8080/hello
func main() {
// func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.HandleFunc("/register", register)
fmt.Println("项目已启动:localhost:8080")
http.ListenAndServe("localhost:8080", nil)
}
func register(resp http.ResponseWriter, req *http.Request) {
fmt.Println("接收到了注册请求")
// 处理表单的请求,前端提交表单-后台解析表单
req.ParseForm() // 解析表单
// 获取表单参数 post
username := req.PostForm.Get("username")
password := req.PostForm.Get("password")
fmt.Println(username, password)
resp.Write([]byte("注册成功"))
}