世界上最好的语言——Go

时间:2022-07-28
本文章向大家介绍世界上最好的语言——Go,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

编译运行

在安装了go环境的机器上,通过go run 源代码文件名可以直接运行,如

go run main.go

同时还可以通过go build命令生成二进制文件

go build main.go
.main.exe

也就是说Go编译的可执行文件不需要环境支持,为exe文件

go的基本数据类型

  • 布尔型 bool 布尔型的值只可以是常量 true 或者 false,默认值为 false。
  • 字符串类型 string 编码统一为 UTF-8 编码标识 Unicode 文本,默认值为空字符串。
  • 整型(默认值为 0) uint8:无符号 8 位整型(0 ~ 255); uint16:无符号 16 位整型(0 ~ 65535); uint32:无符号 32 位整型(0 ~ 4294967295); uint64:无符号 64 位整型(0 ~ 18446744073709551615); int8:有符号 8 位整型(-128 ~ 127); int16:有符号 16 位整型(-32768 ~ 32767); int32:有符号 32 位整型(-2147483648 ~ 2147483647); int64:有符号 64 位整型(-9223372036854775808 ~ 9223372036854775807)
  • 浮点型(默认值为 0) float32:IEEE-754 32 位浮点数; float64:IEEE-754 64 位浮点数; complex64:32 位实数和虚数; complex128:64 位实数和虚数;
  • 其他数值类型 byte:类似 uint8; rune:类似 int32; uint:32 或 64 位; int:与 uint 一样大小; uintptr:无符号整型,用于存放一个指针;

包管理

golang中语句结尾可以不加分号。 定义包名在程序代码第一行使用

package 包名

指定包名,使用

import (
alias "p1"    
)

方式导入包,其中alias参数是包的别名,不指定别名时,可以省略括号,使用'.'代表以当前路径作为别名,所以使用包中成员时不需要加包前缀。当导入包的源文件包含init函数时,会在主函数运行前执行其init函数,然后再执行当前源程序的init函数,最终再执行当前程序的主函数。

自动补齐分号

golang在一些情况下,会在代码中每行末尾自动补齐分号:

1.当输入被断开为标记时,如果行末标记为:

  • 一个标识符
  • 一个整数、浮点数、虚数、字符或字符串文字
  • 关键字break、continue、fallthrough或return中的一个
  • 运算符和分隔符++、--、)、]或}中的一个 则分号将被自动插入到标记流中非空白行的末尾。

2.如果让复合语句占用单独一行,则在“)”或“"}"”后面的分号可以被省略

类型别名

golang支持使用type关键字为类型起别名,如

type duration int

则为int起了个别名duration,接下来duration就可以用来指代int

变量定义

golang具备编译时的类型推断机制,可以使用var关键字定义变量,变量类型会根据初始值自动推断类型,此种方式必须在定义时指定变量初始值。比如如下定义一个整型变量

var a = 5

此种定义方式还可以简写为

a := 5

如果不想指定初始值或者指定变量类型,可以通过

var variable typename

使用指定类型定义变量,或者同时初始化

var variable typename = value

比如

var a int
var b int = 9

使用fmt输出

package main

import (
    "fmt"
)

func main() {
    var a = 10    //自动推导为int
    var b int = 5 //主动定义为int
    c := 9        //相当于 var c = 9
    fmt.Printf("Hello word %d ,%d ,%d", a, b, c)
}

golang还支持同时初始化多个变量,使用逗号分隔变量和初始值,形式如

var A,B = 100,200

则A的值为100,B的值为200

函数定义

golang中的函数定义如下

func name() (typename...){}

golang中的函数可以一次返回多个数据,这与他可以同时初始化多个变量值的特性相关,当返回数据就一个时,可以省略返回值的括号,如

func re2val() (int,string){
    return 100,"aaa"
}
var num,str = re2val()

当函数没有返回值时,可以不写返回类型 所以完整的主类如下,fmt是输入输出包

package main

import "fmt"

func main() {
    fmt.Printf("Hello word")
}

与其他语言不同的是,go中函数传参数组名,与数组赋值,执行的是数组复制而不是引用赋值

匿名变量

当我们需要使用一个复用一个表达式的结果,而不想为其开辟内存空间时,会面临类似c++的右值引用问题,golang中提供了匿名变量,通过下划线,表示定义一个匿名变量,形式如下

_, b := 100,200

此处代表,b接收200,匿名变量指向100的临时变量空间

字符串

golang提供类型string代表字符串类型,字符串中,ascii字符会占据一个字节,其他字符根据需要自动计算为2~4个字节。 字符串的字符可以直接使用索引获取,如

var s string = "abc"
println(s[0])//字符a

使用len内置函数获取字符串长度

len(s)

字符串可以直接使用+拼接,返回一个新字符串

s1,s2 := "a","bc"
s3 := s1+s2

struct

golang中支持使用struct定义复合类型,如下定义一个包含一个整型成员和一个字符串成员的复合类型,并起名为Stu

package main

import (
    "fmt"
)

type Stu struct{
    num int
    name string
}

func main() {
    var stu1 = Stu {
            num : 1,
            name : "yyt",   //如果不加逗号,则右括号必须在最后一行成员末尾,不能换行
            }   
    var stu2 Stu        //使用自动初始化
    var test = struct {
        txt string
    }{
        txt: "aaa",
    } //定义匿名结构体,并进行初始化
    fmt.Printf("Hello word %d ,%d,%s", stu1.num, stu2.num, test.txt)    // 1,0,aaa
}

指针类型

通过在类型前面加上 *代表是改类型的指针类型,比如

var a int = 1
var p * int = &a

与c++不同,golang中的结构体指针和普通结构体变量,都是通过.运算符获得成员,所以指针变量使用起来与普通变量差别看起来不是很大,只是因为其代表的是指针的变量,不一定是栈中变量。比如

type Stu struct{
    name string
}
var stu1 Stu;
var stu2 * Stu = &stu1
println(stu1.name,stu2.name)

println是golang默认提供的输出函数,不需要导入包,通过逗号运算符自动拼接多个输入

结构体函数

golang支持为结构体定义未实现的函数成员,并且可以通过赋值提供实现,这也意味着,我们可能实例化一个结构体对象,而没有提供其方法成员的实现,这时如果我们调用该成员方法,则会抛出invalid memory address or nil pointer dereference的错误,比如

type Test struct{
    test func()
}
func main(){
    t := Test{}
    t.test = func(){}
    t.test()
}

golang支持在结构体外部为结构体提供函数成员,形式如下

func (variable typename) funName(){}

variable代表指定调用当前函数的结构体变量对象的别名,则函数中可以通过这个别名访问改变量,typeName代表对应的结构体类型,比如

type Stu struct{
    name string
}
func (stu Stu) call() string {
    return stu.name
}
func (stu *Stu) pCall() string {
    return "*" + stu.name
}

如上定义,则 Stu类型和* Stu类型变量都具备了call和pCall方法,这是因为golang会完成自动的取地址运算和指针解引用运算,在使用Stu类型变量调用Stu指针类型的方法时,golang会自动将代码执行为(&variable).方法名;对应的Stu指针类型变量调用Stu类型方法时,会被执行为(*variable).方法名

函数变量

go语言真的没有oop,其方法就是函数,而非行为,只是通过调用结构体方法时,会传递该结构体对象自身,所以还可以通过函数变量,来接受结构体函数,比如

type Stu struct{
    name string
}
func (stu Stu) call(age int) string {
    return stu.name
}

var stu Stu
f := stu.call
f(1)    //=> stu.call(1)

const与iota

go中存在const关键字,用于创建常变量,比如

const a = 5

iota表示一个计数器,在一个const表达式中,多个变量定义的iota会逐行递增,递增变量为1,默认iota初始值为0,如

const (
    A = iota
    B = iota
)

最终结果,A变量值为0,B变量值为1 在const表达式中,不指定变量初始值,会自动沿用上一变量的初始值表达式,所以上述代码可以简写为

const (
    A = iota
    B
)

再利用匿名变量,如

const (
    A = iota
    _
    B
)

则此时B的值会是2 使用iota执行计算,如下

const (
    A,B = iota+1 , iota+2
    C = iota+4
    D
)

则结果A的值为1,B的值为2,C的值为5,D的值为6(沿用iota+4)

接口

golang支持定义接口类型,作为限制类型规范,要求变量的值类型必须是实现对应方法的结构体类型或对应结构体指针类型,比如

type Reader interface {
    read(b []byte) ()
}
type File struct{
}
func(file File) read(b []byte){}
var fileReader Reader = File{}

接口类型不自动转化指针类型与普通类型,也就是说,如果只为struct的指针类型实现了接口方法,则只能传递指针变量,虽然他的普通变量也可以调用该方法,但那是因为他会被执行取地址后再执行该方法,不算实现了该接口,也就是说,如

type Man struct{
    name string
}
func (man * Man ) call() string {
    return man .name
}
type  Human interface{
    call()
}

//var human Human = Man{}        无法编译,因为没有实现call方法
var  human Human = &Man{}

嵌入类型与方法重写

golang支持在结构体中组合其他的结构体,其中如下为普通的成员组合,没有嵌入的情况

type Man struct{
}
func (man Man ) call() {
}
type Postman struct{
    man Man
}
var postman Postman = Postman {
        man : Man{},
}

postman .man.call()

而一种独特的形式是嵌入类型,嵌入类型使用类型名作为字段名,然后则该嵌入类型的所有成员及方法的作用域会扩展到外部类型(实际调用时自动解析为里面的嵌入类型),如

type Man struct{
}
func (man Man ) call() {
}
type Postman struct{
    Man
}
var postman Postman = Postman {
        Man: Man{},
}

postman .man.call()
postman .call()    //自动解析为postman .Man.call()

因为嵌入类型使得可以通过外部类型访问内部类型的成员。所以内部类型实现的接口,相当于外部类型也间接实现了,此时可以通过外部类型变量传递给内部类型实现的接口类型变量,比如

type Caller interface {
    call()
}
type Man struct {
}

func (man Man) call() {
    println("call ...")
}

type Postman struct {
    Man
}
var caller Caller = Postman {
        Man: Man{},
}

caller .call()    //call...

同时,此时因为外部类型和嵌入类型是不同struct,则如果为外部类型创建于嵌入类型相同的方法,则在通过外部类型调用时,优先匹配外部类型方法,形成类似继承对方法的重写,比如刚刚的例子,在PostMan中重写call方法

func (postman Postman) call() {
    println("postman call ...")
}

最终输出就成为了重写方法的结果,类似的接口也存在嵌套效果,其表现行为与结构体一样,嵌套以后,外部接口则具备了内部接口的所有成员

type Reader interface {
    read(b []byte) ()
}
type MyReader interface{
    Reader
}

类型转化

通过 typeName(variable)进行安全类型转化,如

var a int = 5
var b float32 = float32(a)

通过variable.(typeName)进行非安全类型转化

使用该方法的对象必须是interface{}类型,而这是个空接口,意味着我们只需要通过一次变量赋值,即可得到(空接口类型可以存储任意值) 其第一个返回值为,将该对象转化为指定类型后的值,第二个返回值为是否满足该类型定义。

package main;

type A interface{
    a()
}
type B struct{}

func main(){
    var var1 interface{} = B{}
    //不能使用val1 := B{},因为这样类型是B{}
    val,ok := var1.(A)
    println(ok) //false
    println(val == nil) //true

    var a interface{} = 5
    var b int = a.(int)//ok

    var d interface{} = 5
    e,_ := d.(B)
    e.a = 9 //ok
}
  • 转化类型是基本类型,如果对象不满足类型定义,则抛出panic
  • 转化类型是结构体类型,则即便该对象不是该结构体类型,也会使用默认构造方法构造转化对象
  • 转化类型是接口类型,如果对象不满足类型定义,则返回nil

同时,因为golang的switch的case表达式可以是任意类型(包括类型名称),所以通过关键字type结合switch可以进行类型判断,如

switch t := areaIntf.(type) {
case *Square:
    fmt.Printf("Type Square %T with value %vn", t, t)
case *Circle:
    fmt.Printf("Type Circle %T with value %vn", t, t)
case nil:
    fmt.Printf("nil value: nothing to check?n")
default:
    fmt.Printf("Unexpected type %Tn", t)
}

Go异常处理

  • error go预先定义了一个error接口,包含一个字符串返回类型的函数,用于返回错误信息 type error interface { Error() string } 可以同个errors包的new方法,传递一个字符串创建一个error对象 err := errors.New("new Error")
  • panic panic语句用于抛出一个 error对象,其接收一个interface{}类型的参数,也就是任意类型。其可以接收一个字符串作为错误信息抛出,也可以接收一个error对象作为错误抛出,抛出后程序进入异常处理 panic("error!!!") //或者 panic(errors.New("error!!!"))
  • defer defer后的语句,会在当前调用defer语句的函数返回后执行,并且同个函数内部的defer语句是压栈执行(LIFO),最后的defer语句的内容将会在函数返回后立即执行,然后才执行倒数第二个defer语句内容,依次类推 defer类似java的finally语句,无论函数是抛出异常结束还是正常返回,都会在返回调用者前执行,比如 func main(){ defer println("error occur") panic("error") println("finish") } 最终输出 error occur panic: error goroutine 1 [running]: main.main() d:/GoProject/errorDemo.go:5 +0x78 exit status 2 Process exiting with code: 1
  • recover recover函数只能在被defer修饰的内容(比如函数调用)中执行,其返回一个error对象(当程序抛出异常时)或者nil。当在defer中调用recover函数后,则异常不会再继续往外抛出,比如刚刚的代码修改为 import "fmt" func main() { defer func() { err := recover() fmt.Printf("call recover get %v", err) }() panic("error") println("finish") } 最终输出 call recover get error 因为异常出现,除了defer以外的语句都不会再执行,但是因为我们在defer中调用了recover所以异常不再对外抛出,不会像刚刚因为异常抛出到程序异常终止,但是main函数是正常结束了(panic后的语句不会执行),这时如果被结束的函数具备返回类型,则返回的都是0或者nil,即未初始化的默认值,比如 import "fmt" func test1() *int { defer func() { err := recover() fmt.Printf("call recover get %vn", err) }() panic("error") a := 1 return &a } func test2() int { defer func() { err := recover() fmt.Printf("call recover get %vn", err) }() panic("error") return 1 } func main() { a := test1() b := test2() println("a is ", a, " ,b is ", b) } 最终输出 call recover get error call recover get error a is 0x0 ,b is 0

new、make与管道

  • new new用于初始化一块内存空间并返回指针,用法为new(typeName)比如 var p *int = new (int) 其实等价于 var temp int p := &temp
  • make
    • slice make用于为slice(动态数组),map 或 chan (管道)初始化内存空间,并返回该对象(不是指针类型)。比如 //创建一个长度为5的int slice var a []int = make([]int ,5,10) //与数组不同的是slice变量在赋值(函数传参等)时是引用赋值,而不是复制 其中第三个参数是capacity不指定默认等于slice初始长度,slice可以通过append方法扩容,当capacity足够时,扩容方法返回的还是当前slice的引用,如果capacity不够,则返回的是重新分配一块内存地址,复制旧元素,即返回的不是之前的地址,但是go会自动修改前面旧slice引用的地址为新地址,所以对旧slice使用不会有影响(旧变量也会引用到新地址)
    • map golang中定义一个map格式为 var variable map[keyTypeName] valTypeName 比如通过make定义一个key为string,值为int的map m := make(map[string] int) m["key"] = 33
    • 管道 golang中,chan类型代表管道,是一个线程安全的阻塞队列,如下定义一个接收int对象,缓冲容量为5的双向管道,chan的读取操作和写入操作都是原子的 ch := make(chan int, 5) 其中第二个参数不指定默认为0,代表管道没有缓冲区,无缓冲管道的流入事件会阻塞到管道中产生流出事件,才流入,流入后直接流出 当管道具备缓冲区,如果管道中存储达到容量,则流入管道的操作回阻塞直到有容量,同理,当管道中内容为空时,流出管道操作回阻塞到管道中有数据 流入运算 ch := make(chan int, 5) ch<-5 //流入数值5 流出运算 ch := make(chan int, 5) <-ch //流出一个数值 ch<-9 //流入一个9 a := <-ch //流出数值赋值给变量 同时还可以指定管道为只读或者只写管道,如 onlyReadChan := make(<-chan int) onlyWriteChan := make(chan<-int) 通过go内置函数close,关闭chan后,所有监听管道流出的goroutine都会重复监听到零值(0或者false或者nil等等,根据管道类型而定),这时如果因为监听陷入阻塞的goroutine也会唤醒并得到一个零值 package main import "fmt" var ch1 = make(chan int) func read(ch1 chan int){ for{ v ,ok:= <- ch1 if ok{ fmt.Printf("read a int is %dn",v) }else{ fmt.Printf("sread a int is %dn",v) } } } func write(ch chan int){ for i:=0;i<10;i++{ ch <- i } close(ch) } func main() { go write(ch1) go read(ch1) for true {} }

Go多线程

  • go关键字 golang中提供go关键字来开启一个goroutine(go中的协程,类似线程,但是一个线程下可以有多个协程),用法为 go 函数调用 比如如下代码,开启一个goroutine去执行println("a") go println("a") 比较特殊的情况是,如果程序的main函数结束了,则所有goroutine会被终止
  • runtime包 go的runtime包中提供了多线程的一些库函数
    • Gosched 使得当前goroutine放弃当前的cpu时间片,进入竞争cpu资源状态
    • NumCPU 返回当前系统的cpu核数
    • GOMAXPROCS 设置当前go程序最大使用的cpu核数
    • Goexit 终止当前的goroutine(defer语句会执行)
    • NumGoroutine 返回当前处于等待执行和正在执行状态的goroutine数量
    • GOOS 设置当前程序的目标操作系统,通过修改该参数,使得go build出不同的二进制代码
  • time包 go的time包中提供了一些时间处理和线程休眠、定时的函数
    • Sleep 休眠当前的goroutine
    • Tick 返回一个定时新增数据的chan
  • sync包 go的sync包提供了锁控制的一些库函数
    • Mutex 互斥锁,其Lock方法阻塞获得锁,unlock方法释放锁
    • RWMutex 读写锁,RLock方法使用读锁,Lock方法使用写锁,RUnLock方法释放读锁,UnLock方法释放写锁

文件IO

  • io包 type pipe struct { wrMu sync.Mutex // 写时锁 wrCh chan []byte // 数据存储单元 rdCh chan int // 数据存储长度 ... } type PipeWriter struct { p *pipe // 共享pipe } type PipeReader struct { p *pipe // 共享pipe } ... pipe管道通过内存进行同步控制,是一个无缓冲管道,比如 import ( "io" ) func main() { r, w := io.Pipe() buf := make([]byte, 128) go func() { println("done") r.Read(buf) println(string(buf)) }() go w.Write([]byte("hello")) for true { //因为如果主函数结束,所有goroutine会被终止 //通过阻塞主函数保证其他goroutine执行完成 } }
    • pipe 该方法返回一个PipeReader和一个PipeWriter指针
  • io/ioutil包 func ReadAll(r io.Reader) ([]byte, error) func ReadDir(dirname string) ([]os.FileInfo, error) func ReadFile(filename string) ([]byte, error) func WriteFile(filename string, data []byte, perm os.FileMode) error func TempDir(dir, prefix string) (name string, err error) func TempFile(dir, prefix string) (f *os.File, err error)
    • TempFile 创建指定前缀名的临时文件,返回创建后的文件指针
    • TempDir 创建指定前缀名的临时目录,返回创建目录的路径
    • WriteFile 将字节slice写入文件
    • ReadFile 读一个文件,返回文件内容(字节slice)
    • ReadDir 读取一个目录,返回目录中的文件信息数组 []os.FileInfo
    • ReadAll 从一个reader中读取所有数据,返回读到的字节slice

网络通信

  • Tcp
    • server
    • client import ( "net" ) func main() { conn, _ := net.Dial("tcp", "127.0.0.1:65535") defer conn.Close() conn.Write([]byte("echo data to server ,then to client!!!")) buf := make([]byte, 1024) conn.Read(buf) println("receive : ", string(buf)) } import ( "net" ) func main() { l, _ := net.Listen("tcp", "127.0.0.1:65535") defer l.Close() for { conn, err := l.Accept() if err != nil { return } //处理tcp请求 go handleConnection(conn) } } func handleConnection(conn net.Conn) { buf := make([]byte, 1024) conn.Read(buf) println("recieve : ", string(buf)) conn.Write([]byte("hello from server")) }
  • Udp
    • client import ( "net" ) func main() { //第二个参数是本地绑定的addr,不指定由系统随机分配 socket, _ := net.DialUDP("udp4", nil, &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 23452, }) defer socket.Close() senddata := []byte("hello from udpClient!") socket.Write(senddata) // 接收数据 data := make([]byte, 4096) socket.ReadFromUDP(data) println("recieve : ", string(data)) }
    • server import ( "net" ) func main() { socket, _ := net.ListenUDP("udp4", &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 23452, }) defer socket.Close() for { // 读取数据 data := make([]byte, 1028) _, remoteAddr, err := socket.ReadFromUDP(data) if err != nil { continue } println("recieve : ", string(data)) // 发送数据 senddata := []byte("hello from udpServer!") _, err = socket.WriteToUDP(senddata, remoteAddr) if err != nil { return } } }

strings与strconv

strings包提供字符串常用操作函数:

  • strings.HasPrefix(s, prefix string) bool 判断s是否匹配前缀prefix
  • strings.HasSuffix(s, suffix string) bool 判断s是否匹配后缀suffix
  • strings.Contains(s, substr string) bool 判断s是否包含子字符串substr
  • strings.Index(s, str string) int 返回s中str第一次出现的下标,没有返回-1
  • strings.LastIndex(s, str string) int 返回s中str最后一次出现的下标,没有返回-1
  • strings.Replace(str, old, new, n) string 将str中的前n个old子字符串替换为new,如果n是-1代表全部替换
  • strings.Count(s, str string) int 统计s中str子串出现的概率
  • strings.Repeat(s, count int) string 返回一个count个s顺序拼接的新字符串
  • strings.ToLower(s) string 返回s转为小写的字符串
  • strings.ToUpper(s) string 返回s转为大写的字符串
  • strings.Split(s, sep) []string 返回s按照sep作为切割符得到的slice
  • strings.Fields(s) []string 返回s按照空白(一个或者多个空格)进行切割得到的slice
  • strings.Join(sl []string, sep string) string 返回sl这个字符串slice使用sep作为分隔符连接得到的新字符串

strconv提供字符串类型与其他类型相互转化的函数:

  • strconv.Itoa(i int) string 返回整数i的十进制字符串表示
  • strconv.Atoi(s string) (i int, err error) 返回十进制字符串s的整数表示
  • strconv.ParseFloat(s string, bitSize int) (f float64, err error) 返回bitSize型的浮点型数字字符串s的float64表示

reflect

  • TypeOf和ValueOf reflect包中提供了运行时反射的一些库:
  • func TypeOf(i interface{}) Type TypeOf方法返回一个对象的类型
  • func ValueOf(i interface{}) Value ValueOf方法返回一个对象的Value, Value 有一个 Type 方法返回 reflect.Value 的 Type。 Type 和 Value 都有 Kind 方法返回一个常量来表示类型 Value 有叫做 Int 和 Float 等的方法可以获取存储在内部的值 type MyInt int var m MyInt = 5 v := reflect.ValueOf(m) v.Kind() //reflect.Int v.Int() //5
  • 反射修改基本类型 如果在ValueOf中传递的不是地址,则得到的是一个非指针对象,是只读类型 当我们通过ValueOf传递地址时,得到的value对象是指针对象,与原对象有密切关系 指针对象因为安全原因,不允许使用setXXX进行修改,所以需要通过Elem方法获得该指针对象对应的值对象(此对象基本等价于原始对象,在其上的修改也回作用到原始对象上),此时的值对象既可以进行set修改值 可以通过value的CanSet方法判断,其是否允许修改操作,如 var x float64 = 3.4 v := reflect.ValueOf(&x) v = v.Elem() v.SetFloat(1.25) println(v.Float()) //1.25 println(x) //1.25
  • 反射修改结构类型 如果Value对象是一个方法对象,则可以通过Call方法进行调用,传递参数为Value类型的slic,代表方法形参,函数返回值也是一组Value对象slice 需要注意的是,结构类型的成员(结构内与结构外),只有以大写字母开头的字段或者方法,才能被反射调用和修改,大写开头的字段代表为公开字段,否则代表不能反射修改的字段 func (v Value) Call(in []Value) []Value { 比如 package main; import "reflect" type A struct{ InnerFunc func() Num int } func(a A) Read(){ println("call read") } func main(){ a := A{ InnerFunc: func(){ println("call innerFunc") }, Num: 9, } v := reflect.ValueOf(&a).Elem() println(v.NumField())// 2 v.Field(0).Call(nil) //call innerFunc v.Method(0).Call(nil) //call read println(v.Field(1).Int()) //9 v.Field(1).SetInt(0) println(v.Field(1).Int()) //0 }
    • NumField() int 方法返回Value对象结构内的字段数量
    • Field(n int) Value 方法返回Value对象结构内,第n个(n从0开始)成员的Value对象
    • Method(n) Value 方法返回Value对象结构外,第n个(n从0开始)外部方法的Value对象

select

golang中的管道类型经常用于协程的协调上,select可以实现对管道事件的监控,在对应事件触发时,执行对应事件(其会在没有管道事件触发时阻塞,直到监听的管道事件之一触发的时候唤醒执行对应事件),比如

//ch1和ch2都是chan类型
select {
case u:= <- ch1:
        ...
case v:= <- ch2:
        ...
        ...
default: // no value ready to be received
        ...
}

一般通过select来处理协程的调度,而不使用for循环,可以降低性能损耗 select本质是go的goroutine设计和chan设计结合设计的语言层面的epoll支持

更多资料,请搜索公众号歪歪梯Club