【Go语言快速上手(六)】管道, 网络编程,反射,用法讲解

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Go语言专栏⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多Go语言知识

  🔝🔝


GO快速上手

  • 1. 前言
  • 2. 初识管道
  • 3. 管道的高级用法
  • 4. GO中的网络编程
  • 5. GO语言中的反射
  • 6. 总结以及拓展

    1. 前言

    本篇文章是GO语言快速上手系列的最后一篇文章, 学完本章后你就掌握了GO语言常用的所有知识和语法, 在未来使用GO语言时你可能还会遇见一些奇怪的语法,但是别害怕, GO就是为了简洁而生,你有Java或CPP的基础,学什么都很快的

    本章重点:

    本篇文章着重讲解GO中的管道的概念以及用法. 还会讲解GO语言是怎样实现网络编程的, 会通过一个例子来讲解. 最后会讲解GO语言的反射机制,以及反射的语法.由于GO语言没有传统意义上的泛型编程概念,所以学习反射还是有必要的


    2. 初识管道

    说白了管道就是一个数据结构: 队列

    管道的定义:

    var 变量名 chan 管道类型
    var channel chan int
    管道可通过make进行初始化
    channel = make(chan int,3)//管道可以存放三个int类型变量
    

    管道是有类型的,int类型管道只能写入int类型数据,并且管道是引用类型,必须初始化后,即make后才能使用它.除此之外,管道的用法比较奇特,通过左箭头<-来向管道存放或取出数据

    var intchan chan int = make(chan int,3)
    //向管道中存放数据
    intchan<-10
    intchan<-20
    num := 30
    intchan<-num
    //从管道中取出数据
    data1 := <-intchan
    data2 := <-intchan
    data3 := <-intchan
    

    显而易见,插入管道的顺序为10,20,30,所以取出数据时,data123分别对应10,20,30. 并且从管道中取出数据的意思就是把它拿出来. 除此之外,使用范围for进行遍历时,打印出数据后, 管道中的数据也会被取出.

    var intchan chan int = make(chan int,3)
    //向管道中存放数据
    intchan<-10
    intchan<-20
    num := 30
    intchan<-num
    for v:= range intchan{fmt.Println("value: ",v)
    }
    

    管道的for-range循环只有value,没有key,并且如果你直接这样写代码,会报错, 因为for-range会一直遍历管道,并不会在乎管道中是否还存在数据. 所以在使用for-range之前应该先用close函数将管道关闭

    • 不关闭管道, for-range会一直取数据
    • 关闭管道后, 不可写入,但可读取

      3. 管道的高级用法

      管道可以声明为只读或只写性质:

      var intchan1 chan<- int //只写
      var intchan2 <-chan int //只读
      

      除此之外,如果你学过多路转接select,那么这对于你来说应该很轻松.当有多个管道时,需要解决选择问题,select的作用就是在多个管道中随机公平的选择一个来执行.这是通过select和case来实的,case后面必须进行IO操作,不能是等值, 随机去选取一个IO操作.若select时迟迟没有管道就绪,那么就会一直阻塞在select处.加上default语句,可以避免这种阻塞发生

      package main
      import (
      	"fmt"
      	"time"
      )
      func main() {intchan1 := make(chan int, 10)
      	intchan2 := make(chan string, 10)
      	intchan3 := make(chan float32, 10)
      	go func() {//匿名函数
      		time.Sleep(time.Second * 5)
      		intchan1 <- 10
      	}()
      	go func() {time.Sleep(time.Second * 4)
      		intchan2 <- "zbcdefg"
      	}()
      	go func() {time.Sleep(time.Second * 3)
      		intchan3 <- 3.14
      	}()
      	select {case v1 := <-intchan1:
      		fmt.Printf("intchan1: %v", v1)
      	case v2 := <-intchan2:
      		fmt.Printf("intchan2: %v", v2)
      	case v3 := <-intchan3:
      		fmt.Printf("intchan3: %v", v3)
      	default:
      		fmt.Println("防止select被阻塞")
      	}
      }
      

      4. GO中的网络编程

      GO语言有net包直接进行网络编程,十分的方便,不像CPP一样进行网络编程时需要调用系统调用来完成.话不多说,直接上手代码示例:

      服务器端:

      1. 监听一个地址和端口

      2. 接受客户端的连接

      3. 读取和写入数据

      package main  
      import (  
          "bufio"  
          "fmt"  
          "net"  
          "os"  
      )  
      func main() { // 监听TCP端口  
          listener, err := net.Listen("tcp", ":8080")  
          if err != nil { fmt.Println(err)  
              return  
          }  
          defer listener.Close()  
          for { // 接受新的连接  
              conn, err := listener.Accept()  
              if err != nil { fmt.Println(err)  
                  continue  
              }  
              //到来连接时,让协程去执行读写操作
              go handleRequest(conn) // 使用goroutine处理连接  
          }  
      }  
      func handleRequest(conn net.Conn) { defer conn.Close()  
          // 读取数据  
          reader := bufio.NewReader(conn)  
          message, err := reader.ReadString('\n')  
          if err != nil { fmt.Println(err)  
              return  
          }  
          fmt.Print("Message received: ", string(message))  
          // 发送数据  
          conn.Write([]byte("Message received!\n"))  
      }
      

      如果你清楚TCP通信的基本流程,那么你一定知道,net.Listen函数就是封装了系统调用listen,而方法listener.Accept封装了系统调用accept,返回的conn也就是就绪的连接fd

      客户端:

      1. 连接到服务器

      2. 读取和写入数据

      package main  
      import (  
          "bufio"  
          "fmt"  
          "net"  
          "os"  
      )  
      func main() { // 连接到TCP服务器  
          conn, err := net.Dial("tcp", "127.0.0.1:8080")  
          if err != nil { fmt.Println(err)  
              return  
          }  
          defer conn.Close()  
          // 发送数据  
          fmt.Fprintf(conn, "Hello, Server!\n")  
          // 读取响应  
          reader := bufio.NewReader(conn)  
          message, err := reader.ReadString('\n')  
          if err != nil { fmt.Println(err)  
              return  
          }  
          fmt.Print("Message from server: ", string(message))  
      }
      

      对Linux熟悉的同学一眼就能看出,这个fprintf就是向文件描述符conn中输入数据的,都是封装了C,所以说我建议学GO语言要先把基础知识打牢固


      5. GO语言中的反射

      GO不直接支持泛型编程,但通过反射可以弥补这一缺陷,话不多说,直接上概念:

      代码示例:

      func test(i interface{}){//1.调用typeof函数,返回reflect.Type类型数据
      	reType := reflect.TypeOf(i)
      	fmt.Println(reType)
      	//2. 调用valueof函数,返回reflect.value类型数据
      	reValue := reflect.ValueOf(i)
      	fmt.Println(reValue)
      	//注,revalue是valueof类型,不能直接进行运算
      	//想要获取revalue的值要调用revalue.Int()方法
      }
      func main(){//对基本类型进行反射
      	var num int = 100
      	test(num)
      }
      

      反射要通过传递空接口来实现,不知各位是否还记得在讲接口时说到,任意类型都实现的空接口,也就是说任意类型的变量都可以传递给空接口,接口和reflect.value类型可以相互转换,转换关系如下:

      获取变量的类别有两种方式:

      reflect.Type.kind()

      reflect.Value.kind()

      注意,类别和类型是两种概念,类别是指bool,int,int32,int64,struct,map这种大分类,而变量的类型是小分类. 除此之外,还可以对结构体类型进行反射:

      func test(i interface{}){//1.调用typeof函数,返回reflect.Type类型数据
      	reType := reflect.TypeOf(i)
      	fmt.Println(reType)
      	//2. 调用valueof函数,返回reflect.value类型数据
      	reValue := reflect.ValueOf(i)
      	fmt.Println(reValue)
      	fmt.Println(reType.kind())
      }
      type stu struct{Name string
      	Age int
      }
      func main(){//对结构体类型进行反射
      	student := stu{Name : "张三"
      		Age : 18
      	}
      	test(student)
      }
      

      更多关于反射的使用手册,大家可以自行问GPT


      6. 总结以及拓展

      GO语言快速上手这一系列的文章就结束了,但GO语言的学习之旅还远远没有结束, 本系统文章只讲解了很常用的GO语法,在实际生产生活中,如果遇见了诸如像context类型的新概念,大家可以自行去学习,感谢您的阅读,再见