Go语言学习(反射、泛型、网络编程)

反射

概念

正常的开发中,很少用到,因为效率低。

更多的用于开发一些脚手架,自动实现一些底层的判断。

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
}
  1. 泛型类型(自定义类型结合泛型使用)

  1. 泛型作为接收者(实现函数的灵活变化)

  1. 泛型函数(参数是泛型)

自定义泛型

由于约束有很多,可以定义一些自己的泛型约束(本质是一个接口)

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>&nbsp;&nbsp;&nbsp;密码:<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("注册成功"))
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值