Go

Go语言学习笔记

Posted by 吴俊贤 on July 30, 2018

包、变量和函数

Go的程序由多个包组成。

main包是程序开始运行的包。

import语句

使用import关键字导入包,一行的格式为,可以使用多个import语句导入多个包

import "fmt"
import "math"

也可使用一个import导入多个包,格式如下

import (
  "fmt"
  "math/rand"
)

包的层次

包是有层次结构的,如math/rand,导入包是路径上的最后一个,则我们导入的包实际上是以package rand定义的包。

导出名

若一个名称(变量名或函数名)是以大写字母开头的,则这个名字会被导出,类似Java的public关键字。只有被导出的名称,才能在导入该包的文件中使用。

函数

函数有一个或多个参数。由func关键字开头,而后接函数名,随后是参数列表,返回值类型,函数体。需要注意的是,参数类型在参数名的后面。格式如下

func <函数名>([param1 Type, [param2, Type, ...]) <返回值类型> {
  <函数体>
}

func add(x int, y int) int {
  return x + y
}

若相邻的两个参数的类型一样,则可以合并起来,如x int, y int可以合并为x, y int

函数返回多个值

函数可以返回多个值,在返回值类型中,使用括号括起值的类型,中间使用逗号分割开。需要使用函数的返回值时,则赋值语句左边也用逗号分隔开变量,如

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

命名返回值

返回值可以在本来声明返回值类型的位置给其命名,相当于在函数开始定义好了返回值变量。当返回时,只需要return即可。

建议只在短小的函数中使用

变量

var语句声明一连串的变量,变量名在前名称在后。

可以在var语句中初始化变量,直接跟等于号即可。如果有多个变量需要初始化,则用逗号分开值。初始化变量的var语句中,可以省略类型,变量会与值同类型。

若右边的值是常数,则Go会根据数值类型精确度,推断类型,通常为int,float64,complex128等。

若不初始化,则变量会自动被初始化为该类型的零值。

可以使用块语法,一个var语句定义多个变量,如

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

:=运算符

在函数里面,可以使用:=运算符,同时声明和赋值一个或多个新的变量,而不用var语句,新变量的类型会与值一致。

基本数据类型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte //uint8的别名

rune //int32的别名,代表一个Unicode字符

float32 float64

complex64 complex128

int, uint, uintptr的长度与系统的步长有关,32bit系统就是32bit长度。

零值

数值类型的零值为0

布尔类型的零值为false

字符串类型的零值为""

复杂类型(字典、数组、结构)的零值为nil

类型转换

使用T(x)来将x变量转换为T类型。

在赋值语句中,只能显式声明类型转换,而不会自动执行类型转换。

常数

使用const定义常数。不能使用:=来赋值。

数值常数精度非常的高,可以超出int的范围等。

流程控制语句

for语句

for包含三个元素,使用分号分隔开。

  • 初始化语句,在第一次循环前执行
  • 条件表达式,在循环语句开始之前执行
  • 后置语句,在循环语句运行结束后执行

不需要使用括号括起来,但是大括号是必须的。

Go中没有while语句,省略初始化语句和后置语句的for语句就是while

当所有的元素都被省略时,for语句将变成无限循环。

if语句

不需要圆括号,但需要大括号,跟for类似。

if语句同样可以有初始化语句,初始化语句中声明的变量,只能在if以及其else语句中使用。

switch语句

switch语句与其他语言的类似,只是没有break关键字,也因此,一个case语句只能跟自己的语句块,而不能实现多个case使用同一个语句块。

case的判断顺序是从上到下的,当找到符合的case时,立即停止,不会再判断下面的case。

当没有表达式判断时,相当于switch true,会判断case语句的表达式,如果是true,则运行case下的语句。

defer语句

defer语句会将其后面的语句,在其周围的函数退出时执行。

defer语句将会把执行语句推入到栈中,多个defer语句存在时,后面的会先执行。

复杂类型:指针、结构、字典

指针

Go有指针,在类型前加*号成为其指针,&依然为取指针的运算符,*作用在指针时,依然为解引用。

但是Go没有指针运算。

结构

结构是多个字段(Field)的组合。

使用.运算符取变量。如果使用结构的指针来取结构中值,依然使用.运算符而不是->

结构的可以使用硬编码初始化。有多种语法,可以使用字段名加值的方式,也可以使用顺序列表的方式,没有被赋值的字段依然是零值。如下

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  
	v2 = Vertex{X: 1}  //暗含Y=0
	v3 = Vertex{}      //X=0, Y=0
	p  = &Vertex{1, 2} //类型为*Vertex
)

不能复制只能共享的结构

有些结构不能复制,比如File结构,声明

// File represents an open file descriptor.
type File struct {
	*file // os specific
}

那么结构内嵌了一个指针,指向一个不公开的类型file。那么能够确保开发者在使用这个结构的时候,无法真正对结构的值进行赋值,仅能够复制file的指针,确保了File结构内值的安全,也确保了所有的代码都共用同一个实例。

内嵌结构

我们可以在结构体中定义一个没有名字的结构类型的字段,该字段便是结构的内嵌结构。如

type Wheel {
  Radius int
}

type engine {
  Power int
}

type Car {
  Wheel
  engine
}

上面的Wheel便是Car的内嵌结构。内嵌结构可以定义为值类型或者是指针类型。

内嵌结构使得我们取用内嵌结构的字段时,无需输入所有的中间结构名,如我们要获取Radius,只需使用car.Radius即可,而无需car.Wheel.Radius

值得注意的是,即使中间结构是未导出的,如engine,我们仍能够通过内嵌结构的特性,通过car.Power访问Power但是,我们无法使用car.engine.Power访问Power,因为engine仍然是未导出的。

数组

数组的类型格式为[n]T,n为数量,T为类型。数组的长度是静态定义的,无法改变。

数组片段

数组片段是数组的一个可以动态调节长度的视图,是一个引用,其并不存储数值。类型为[]T,T为元素类型。

片段使用两个数值定义,分别是起始下标和终止边界下标,得到的片段不包含终止边界下标的元素,语法如下

a[low:high]

low和high是都可以省略的,默认为0和数组长度值。

数组的硬编码初始化如下,

[3]bool{true, true, false}

片段的硬编码初始化如下,

[]bool{true, true, false}

两个的类型是不一样的。

片段有长度length和容量capacity,分别反映该片段包含的元素数量以及该片段所引用的数组的元素数量。一个片段的长度和容量可以通过len函数和cap函数获得。

使用内置的make函数构建一个新的数组片段,这也是创建动态长度的数组的方法。

数组片段的元素可以为数组片段,即可以有二维数组等。

Go提供了一个append函数,在一个数组片段的后方加入元素,若该数组不够长,则会返回一个更大的数组,包含所有的元素。该函数的定义如下

func append(s []T, vs ...T) []T

range关键字

range关键字用于遍历一个数组或字典,用在for语句中,语法如下

for index, value := range array {

}

若不需要使用index,用下划线_代替即可。若不需要value,则直接不需要写value出来。

字典

字典保存键值对,使用一个键来获取对应的值。make函数可以创建一个新的字典。类型的格式为map[KeyType]ValueType

使用硬编码定义字典,类似js的语法

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

//也可以
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

插入或更新键的值

m[key] = elem

取得键的值,并得知是否这个键有值,ok为true则存在此键。

elem, ok = m[key]

删除键的值

delete(m, key)

我们无法取得值的地址,这会引起编译错误。由于字典的值可能会因为字典的增长,地址发生改变。

键必须是能够比较的,即能够使用==进行比较的。结构中若所有字段都可以进行比较,则该结构可以进行比较。为了使用一个不能进行比较的,如片段,的值作为键,我们可以使用一个转换函数,将该不可比较类型的值转换为一个字符串,然后作为键存储值。

函数值

函数也是一个值,可以传给其他变量或参数。

函数有闭包概念,就会有一个函数的值,可以获取在其外面的变量的引用。

方法与接口

方法

Go没有类的概念,但是可以在类型上定义方法,既可以是结构类型,也可以是数值类型等。方法是一类特殊的函数,在函数声明中间,多了一个类型接受者(Receiver)的声明,位置在func关键字和函数名中间。如

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

Vertex就多了一个方法,名为Abs,该方法没有参数。

注意,方法只能在定义类型的同一个包内定义。如果要对基本类型定义方法,则需要使用type定义一个类型的别名。

接受者可以是指针,我们可以修改接受者的值,在我们需要对结构或数值进行修改时有用。同时这可以减少每次调用方法时复制值的成本,特别是结构特别复杂时。因此,在可能的时候,推荐使用指针接受者

但是,不要同时使用值接受者和指针接受者

若接收者是值,则无论方法接受者为指针还是值,都可以同时使用值或者指针调用该方法。

var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

但是如果接收者是指针,则只能使用指针调用该方法。

接口

接口是方法签名的集合。一个接口的值,可以是任何的实现了该接口的类型的值。其定义的语法如下

type <接口名> interface {
  <方法名>(<参数列表>) <返回值列表>
  ...
}

Go中没有implement关键字,因此,接口是被隐式实现的,任何实现了接口方法的类型,其值都可以是接口的值。这使得在我们可以在任意的包中实现接口,不需要提前的设计。

一个接口实际上是一个二元组,包含了值和值的具体类型。

若接口的值为nil,调用该接口的方法时,就类似nil.method(),这在其他的语言中是一个异常,但是在Go中是被允许的,在方法中,参数将是nil。值得注意的是,一个接口的值是nil的话,其本身并不是nil。而如果接口变量没有被赋值,就不存在具体类型,此时调用接口的方法会出错,因为并不知道调用哪个类型的方法。

空接口

空接口是没有任何方法的接口,可以用于接收任何值。若你无法事先知道值的类型,可以使用这个空接口来接收。

类型断言

类型断言可用于判断一个值的类型,并可以安全的在随后调用该类型的方法。

格式为

value, ok := i.(T)

valuei所包含的底层数值,ok变量用于确定i是否属于该类型。若i不是类型T,则下面对于T类型的方法的调用将会发生异常。

Type switch语法

可以在type switch语法中,断言多个类型,对每种类型,运行不同的代码。格式如下

switch v := i.(type) {
case T:
    //v是T类型的
case S:
    //v是S类型的
default:
    //v不是case所列出来的类型的
}

注意,i.(type)type是关键字。

Stringer接口

类似Java的toString()fmt包会使用该方法,转换一个实现了该接口的类的值为字符串,并输出。定义如下

type Stringer interface {
    String() string
}

error接口

也是另一个内置接口定义,如下

type error interface {
    Error() string
}

内置函数通常会返回error值,若errornil,则得知没有出错。

Reader接口

io.Reader接口内有许多读取流的方法。Go的标准库有许多实现了该接口的。

方法集

方法集定义了一组关联到给定类型的值或者指针的方法。

并发

Goroutines

Goroutine是Go运行时管理的轻量级线程。

使用go 语句语法,使语句在新的goroutine中并行运行。新的goroutine与原本的运行在同一个地址空间中,因此,需要注意同步共享内存

通道Channel

使用通道来发送和接收值,使用通道运算符<-。语法如下

ch <- v    // 发送v的值到通道ch
v := <-ch  // 从通道ch接收值,并赋给变量v

通道通过make创建

ch := make(chan int)

默认情况下,只有当接收端和发送端都准备好的时候,才能发送和接收数据,否则会等待。因此,无需使用锁或者条件变量进行同步。此时,这个通道被称为同步通道。

使用缓存的通道

通道能够使用缓存,只需要在make函数中加入一个参数,定义缓存的大小,如

ch := make(chan int, 100)

发送端在缓存满的时候被阻塞。接收端会在缓存是空的时候被阻塞。若不空不满,则都不被阻塞,能够发送和接收,这样子就将发送端和接收端解耦了。

单向通道

为了避免误用通道,如本应用来接收的通道错误的用来发送了,我们可以声明一个单向通道类型的变量,值为一个通道,但是只能发送或者接收,违反则会导致编译错误。

仅发送通道的类型为chan<- type,仅接收通道的类型为<-chan type

通道的关闭

只有 发送端可以关闭通道。接收端在接收时使用第二个参数,测试是否已经关闭了通道。如

v, ok := <-ch

okfalse,则通道被关闭,不会再有数据传送过来。若尝试往已关闭的通道发送数据,会产生异常。

关闭通道不是必须的,只有在有必要告诉接收端通道需要关闭的时候,才关闭通道。

使用for i := range ch,可以一直接收数据,直到通道关闭。

Select关键字

Select关键字会让goroutine等待多个使用通道的沟通操作。

Select暂停当前运行,直到有一个case运行了,然后执行那个case的语句。当多个case准备好运行时,将会随机选择一个case运行。

select {
case c <- x://c的接收端准备好时运行
  x, y = y, x+y
case <-quit://quit有数据发送来时运行
  fmt.Println("quit")
  return
}

默认选择

select语句没有case准备好时,将会直接运行default case。为了程序不暂停运行,就是用default case吧。

Fan-In函数

我们可以将多个Channel合并为一个Channel,实现这个功能的函数就叫做Fan-In函数,扇入函数。

func FanIn(chan1, chan2 chan int) chan int {
    c := make(chan int)
    select {
    case a := <-chan1:
        c <- a
    case b := <-chan2:
        c <- b
    }
    return c
}

若是需要合并多个channel,则用上面的函数一一合并

func FanInMore(chans ...chan int) chan int {
    if len(chans) == 1 {
        return chans[0]
    }

    var c = FanIn(chans[0], chans[1])

    for i := 2; i < len(chans); i++ {
        c = FanIn(c, chans[i])
    }

    return c
}

goroutine泄露

有时候,我们可能为了快速从多个源头获取结果,会同时发送请求,然后只返回第一个结果,类似下面的语句。

func mirroredQuery() string {
  responses := make(chan string)
  go func() { responses <- request("asia.gopl.io") }()
  go func() { responses <- request("europe.gopl.io") }()
  go func() { responses <- request("america.gopl.io") }()
  return <- responses
}

上面的函数中,当其中一个goroutine返回结果,便会发送结果到responses中,然后mirroredQuery立即退出。此时,由于mirroredQueryresponses的唯一接收者,其余两个goroutine的发送,就会没有接收者,此时将会永久的被阻塞,且无法退出,也没有其他goroutine使用到这些阻塞的goroutine。这种情况就是goroutine泄露。

我们只需要给responses分配大小为3的缓存就可以,此时,通道的发送操作将会立即执行,goroutine立即退出,便不会产生泄露问题。

我们应该保证在程序运行完毕退出时,除了主goroutine外,其他所有在运行过程中由我们启动的goroutine,要正常退出,不留下goroutine。我们可以通过下面这个语句,放在main函数退出的地方,检查是否有goroutine没有退出的。

panic("Checking goroutines")

其实就是panic调用,因为panic会打印出当前所有正在运行的goroutine。

仅发送信号,无需内容

类似done这种通道,只是用于通知另外一个goroutine已经完成了某件事的话,为了节省空间,我们可以发送空结构,如下

done := make(chan struct{})
// 完成时
done <- struct{}{}

空结构所占空间为0。

反射

go的反射主要使用reflect包实现。反射包中,最重要的东西有两个,TypeValue

判断种类(Kind)与类型(Type)

我们可以使用reflect.ValueOf(i interface{})函数,来判断一个变量的种类与类型,该函数返回reflect.Value类型,该类型有许多有用的方法。需要注意的是,这些函数必须在Value属于该函数对应的变量的种类(Kind)的时候执行,若不是,会发生一个panic。具体可以调用Kind()函数判断。变量的种类只有几种,看Kind的定义可以看出。

Kind(种类)

Kind只是一个枚举类型,用来描述某个变量的种类,种类是有限的,但是类型是很多种的,因为用户能够定义任意的结构。

type Kind uint

const (
        Invalid Kind = iota
        Bool
        Int
        Int8
        Int16
        Int32
        Int64
        Uint
        Uint8
        Uint16
        Uint32
        Uint64
        Uintptr
        Float32
        Float64
        Complex64
        Complex128
        Array
        Chan
        Func
        Interface
        Map
        Ptr
        Slice
        String
        Struct
        UnsafePointer
)

Type

Type用于描述某个具体的Go变量的类型,这是一个接口类型。各个函数在下面小节中介绍。

值得注意的是,Type类型可以用==比较,因此能够用于map的键中,根据类型,返回特定的值。

通用函数

// Align returns the alignment in bytes of a value of
// this type when allocated in memory.
// 通用对齐字节值,具体看对齐值小节
Align() int

// FieldAlign returns the alignment in bytes of a value of
// this type when used as a field in a struct.
// 字段对齐字节值
FieldAlign() int

// Method returns the i'th method in the type's method set.
// It panics if i is not in the range [0, NumMethod()).
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
// 返回第i个方法,若i超出NumMethod,则会panic
// 若类型不是接口,则返回该类型(指针或值类型)的Method,其Type和Func字段都描述了Method,该Method的第一个参数是类型接收者。
// 若类型是接口,则返回的Method的Type字段,包含了方法签名,而Func则是nil
Method(int) Method

// MethodByName returns the method with that name in the type's
// method set and a boolean indicating if the method was found.
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
// 根据名称获取方法
MethodByName(string) (Method, bool)

// NumMethod returns the number of exported methods in the type's method set.
// 获取方法的总数
NumMethod() int

// Name returns the type's name within its package.
// It returns an empty string for unnamed types.
// 返回本类型的名字
Name() string

// PkgPath returns a named type's package path, that is, the import path
// that uniquely identifies the package, such as "encoding/base64".
// If the type was predeclared (string, error) or unnamed (*T, struct{}, []int),
// the package path will be the empty string.
// 返回本类型的包路径
PkgPath() string

// Size returns the number of bytes needed to store
// a value of the given type; it is analogous to unsafe.Sizeof.
// 指定存储该类型值时所需要的内存空间的字节大小
Size() uintptr

// String returns a string representation of the type.
// The string representation may use shortened package names
// (e.g., base64 instead of "encoding/base64") and is not
// guaranteed to be unique among types. To test for type identity,
// compare the Types directly.
// 返回该Type的字符串表示。该表示会使用缩短的包名,因此不推荐用于类型比较,直接使用`==`比较即可。
String() string

// Kind returns the specific kind of this type.
// 返回这个Type的种类
Kind() Kind

// Implements reports whether the type implements the interface type u.
// 返回该Type是否实现了u接口类型
Implements(u Type) bool

// AssignableTo reports whether a value of the type is assignable to type u.
// 返回该Type是否能够赋值给u类型
AssignableTo(u Type) bool

// ConvertibleTo reports whether a value of the type is convertible to type u.
// 返回该Type是否能够转换为u类型
ConvertibleTo(u Type) bool

// Comparable reports whether values of this type are comparable.
// 返回该Type所存储的值是否能够用于比较
Comparable() bool

对齐值

对齐值,指的是如果一个类型T的对齐值为N,则保证每一个T类型的值,其内存地址的必定为N的倍数。

在Go语言中,有两种对齐值。分别是当一个类型T为某个结构的字段时候,拥有一个字段对齐值,称为field alignment guarantee。而当类型T用于定义一个变量的时候,则使用通用对齐值,称为general alignment guarantee。

而通用对齐值与字段对齐值,在官方go编译器中,一般与通用对齐值相等。

更多内容,在https://go101.org/article/memory-layout.html中。

基本类型可使用的方法

// Bits returns the size of the type in bits.
// It panics if the type's Kind is not one of the
// sized or unsized Int, Uint, Float, or Complex kinds.
// Int、Uint、Float或这复杂类型可调用
// 返回以位来衡量的类型的大小。
Bits() int

// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
// 通道种类的类型可调用
// Chandir返回通道类型的方向。
ChanDir() ChanDir

// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
// 数组、通道、Map、片段以及指针可调用
// 返回类型所包含的元素的类型,只有
Elem() Type

// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
// Map种类的类型可以调用
// 返回键的类型
Key() Type

// Len returns an array type's length.
// It panics if the type's Kind is not Array.
// 数组种类的类型可以调用
// 返回数组的长度
Len() int

Func类可使用函数

// IsVariadic reports whether a function type's final input parameter
// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
// implicit actual type []T.
//
// For concreteness, if t represents func(x int, y ... float64), then
//
//	t.NumIn() == 2
//	t.In(0) is the reflect.Type for "int"
//	t.In(1) is the reflect.Type for "[]float64"
//	t.IsVariadic() == true
//
// IsVariadic panics if the type's Kind is not Func.
// Func种类的类型可调用
// 返回某个方法的最后参数是不是变长参数。若是,则t.In(t.NumIn() - 1)返回参数的类型为[]T。
IsVariadic() bool

// In returns the type of a function type's i'th input parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
// 函数种类的类型可以调用
// 返回第i个参数的类型,i在[0, NumIn())范围。
In(i int) Type

// NumIn returns a function type's input parameter count.
// It panics if the type's Kind is not Func.
// 函数类类型调用
// 返回参数数量
NumIn() int

// NumOut returns a function type's output parameter count.
// It panics if the type's Kind is not Func.
// 函数类类型可调用
// 返回返回值的数量
NumOut() int

// Out returns the type of a function type's i'th output parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
// 函数类类型调用
// 返回第i个返回值的类型
Out(i int) Type

结构类型可调用函数

// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
// 结构种类的类型可以调用
// 返回结构的第i个字段,需在[0, NumField())范围内
Field(i int) StructField

// FieldByIndex returns the nested field corresponding
// to the index sequence. It is equivalent to calling Field
// successively for each index i.
// It panics if the type's Kind is not Struct.
// 结构种类类型可调用
// 返回index数组指定的多个字段
FieldByIndex(index []int) StructField

// FieldByName returns the struct field with the given name
// and a boolean indicating if the field was found.
// 结构种类类型可调用
// 返回结构中名称为name的字段,以及是否找到该字段的布尔值
FieldByName(name string) (StructField, bool)

// FieldByNameFunc returns the struct field with a name
// that satisfies the match function and a boolean indicating if
// the field was found.
//
// FieldByNameFunc considers the fields in the struct itself
// and then the fields in any anonymous structs, in breadth first order,
// stopping at the shallowest nesting depth containing one or more
// fields satisfying the match function. If multiple fields at that depth
// satisfy the match function, they cancel each other
// and FieldByNameFunc returns no match.
// This behavior mirrors Go's handling of name lookup in
// structs containing anonymous fields.
// 返回符合match函数的结构的字段。首先从有名字段开始搜索,然后以深度优先的方法,搜索匿名结构字段,在包含一个或多个符合match函数的字段的最浅嵌套层数停止。若该层的多个字段符合该函数,则会各自抵消,并返回没有符合match的结果。
FieldByNameFunc(match func(string) bool) (StructField, bool)

// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
// 结构种类的类型可以调用
// 返回字段的数量
NumField() int

reflect包中Type相关函数

这些函数用于构建各种各样的Type,具体不再讲解

func ArrayOf(count int, elem Type) Type
func ChanOf(dir ChanDir, t Type) Type
func FuncOf(in, out []Type, variadic bool) Type
func MapOf(key, elem Type) Type
func PtrTo(t Type) Type
func SliceOf(t Type) Type
func StructOf(fields []StructField) Type
func TypeOf(i interface{}) Type

Value

Value是Go的值的反射接口。Value拥有许多的方法,但是不是所有方法都对特定的Value适用,首先需要知道Value的种类。

零值Value代表没有值,其IsValid方法返回falseKind方法返回InvalidString方法返回<invalid Value>,并且其余方法都将panic。

多数函数与方法不会返回非法(Invalid)值,若是,其文档将会说出返回非法值的条件。

如果底层的值可以被并发的用于多个等效直接的计算(equivalent direct operations),则Value能够被多个goroutine使用。

我们无法使用==比较两个Value,因为这个运算并不会比较底层的值,使用接口方法返回的值更佳。

我们比较常用的函数是ValueOf函数,取得某个空指针的值,以Value表示。

Value相关函数

// Append appends the values x to a slice s and returns the resulting slice.
// As in Go, each x's value must be assignable to the slice's element type.
// 将x值加入到s片段值中,返回新的片段。每个x必须能够赋值到s片段中
func Append(s Value, x ...Value) Value

// AppendSlice appends a slice t to a slice s and returns the resulting slice.
// The slices s and t must have the same element type.
// 将片段t添加到片段s后面,返回新的片段。s与t的元素类型必须一致
func AppendSlice(s, t Value) Value

// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value.
// If v is not a pointer, Indirect returns v.
// 若v是指针,返回指针所指向的值的Value。若v是指针且值为nil,则返回零值。若v不是指针,则返回v
func Indirect(v Value) Value

// MakeChan creates a new channel with the specified type and buffer size.
// 使用指定的Type,构建新的通道,并返回
func MakeChan(typ Type, buffer int) Value

// MakeFunc returns a new function of the given Type that wraps the function fn.
// 使用Type指定的函数类型,并构建函数,下方有详解
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value

// MakeMap creates a new map with the specified type.
// 使用typ构建Map
func MakeMap(typ Type) Value

// MakeMapWithSize creates a new map with the specified type and initial space for approximately n elements.
// 构建Map并指定Map的大小
func MakeMapWithSize(typ Type, n int) Value

// MakeSlice creates a new zero-initialized slice value for the specified slice type, length, and capacity.
// 构建数组片段
func MakeSlice(typ Type, len, cap int) Value

// New returns a Value representing a pointer to a new zero value for the specified type. That is, the returned Value's Type is PtrTo(typ).
// 构建typ所指定的类型的零值的指针,也就是PtrTo(typ)这个Value。
func New(typ Type) Value

// NewAt returns a Value representing a pointer to a value of the specified type, using p as that pointer.
// 构建一个由typ指定的新的零值,并被指针p指向。
func NewAt(typ Type, p unsafe.Pointer) Value

// ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value.
// 返回i所指向的值,以Value形式表示
func ValueOf(i interface{}) Value

// 返回typ类型的零值
func Zero(typ Type) Value

MakeFunc函数

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value

typ的种类是Func,指定了函数的输入值与输出值的类型,而fn则是一个定义好的,与typ类型一致的函数,该函数处理的是Value类型的输入,并产生输出。调用MakeFunc的时候,将会使用args调用该具体的fn函数,并且取得返回值resultsMakeFunc返回的Value,就是一个符合typ的函数,该函数可以被调用。

示例如下

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// swap is the implementation passed to MakeFunc.
	// It must work in terms of reflect.Values so that it is possible
	// to write code without knowing beforehand what the types
	// will be.
  // swap函数交换两个value的位置,并返回。参数是Value数组,因此不需要事先知道参数具体类型。
	swap := func(in []reflect.Value) []reflect.Value {
		return []reflect.Value{in[1], in[0]}
	}

	// makeSwap expects fptr to be a pointer to a nil function.
	// It sets that pointer to a new function created with MakeFunc.
	// When the function is invoked, reflect turns the arguments
	// into Values, calls swap, and then turns swap's result slice
	// into the values returned by the new function.
  // 构建一个使用具体类型作为参数的swap函数,接收一个指向空值的函数指针,该指针描述了具体的函数类型,
  // 构建以后,将会设置该指针的值为构建好的函数,这样就能使用该函数指针调用该函数了。
	makeSwap := func(fptr interface{}) {
		// fptr is a pointer to a function.
		// Obtain the function value itself (likely nil) as a reflect.Value
		// so that we can query its type and then set the value.
		fn := reflect.ValueOf(fptr).Elem()

		// Make a function of the right type.
		v := reflect.MakeFunc(fn.Type(), swap)

		// Assign it to the value fn represents.
		fn.Set(v)
	}

	// Make and call a swap function for ints.
	var intSwap func(int, int) (int, int)
	makeSwap(&intSwap)
	fmt.Println(intSwap(0, 1))

	// Make and call a swap function for float64s.
	var floatSwap func(float64, float64) (float64, float64)
	makeSwap(&floatSwap)
	fmt.Println(floatSwap(2.72, 3.14))

}

如何在MakeFunc的fn里面返回Kind为Interface的Value

error接口为例,我们日常定义许多返回error接口的函数,根据https://stackoverflow.com/questions/41309499/returning-error-value-in-reflect-makefunc的回答,我们可以做如下的转换:

err := errors.New("This is a error") // Type error
errPtrValue := reflect.ValueOf(&err) // Type Value Kind Ptr
errValue := errValue.Elem() // Type Value with Kind Interface and Type error

这应该也是新建一个种类为Interface的Value的方法。

通用方法

// Addr returns a pointer value representing the address of v. It panics if
// CanAddr() returns false. Addr is typically used to obtain a pointer to a
// struct field or slice element in order to call a method that requires a
// pointer receiver.
// 返回一个Value,值为v的地址
func (v Value) Addr() Value

// CanAddr reports whether the value's address can be obtained with Addr. Such
// values are called addressable. A value is addressable if it is an element of
// a slice, an element of an addressable array, a field of an addressable struct,
// or the result of dereferencing a pointer. If CanAddr returns false, calling
// Addr will panic.
// 返回v是否能够取地址。一个值,如果是片段中的元素,能够取地址的数组中的元素、能够
// 取地址的结构的字段、或者是指针解引用的结果,则该v是能够取地址的。
func (v Value) CanAddr() bool

// CanSet reports whether the value of v can be changed. A Value can be changed
// only if it is addressable and was not obtained by the use of unexported
// struct fields. If CanSet returns false, calling Set or any type-specific
// setter (e.g., SetBool, SetInt) will panic.
// 返回v能否被改变。一个值,只在能够取地址或者不是通过使用结构中未导出的字段获取的话,就能被改变。
func (v Value) CanSet() bool

// Convert returns the value v converted to type t. If the usual Go conversion
// rules do not allow conversion of the value v to type t, Convert panics.
// 转换v为类型t的值,需要符合Go的类型转换规则
func (v Value) Convert(t Type) Value

// 返回能否使用Interface函数
func (v Value) CanInterface() bool

// 以空指针的形式返回该v。若v是结构中未导出的值,将会panic
func (v Value) Interface() (i interface{})

// IsValid reports whether v represents a value. It returns false if v is the
// zero Value. If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid value. If one does, its
// documentation states the conditions explicitly.
// 返回是否有效值,若是零值(zero value),则是无效值
func (v Value) IsValid() bool

// Kind returns v's Kind. If v is the zero Value (IsValid returns false),
// Kind returns Invalid.
// 返回值的种类
func (v Value) Kind() Kind

// Method returns a function value corresponding to v's i'th method. The
// arguments to a Call on the returned function should not include a receiver;
// the returned function will always use v as the receiver. Method panics if i
// is out of range or if v is a nil interface v
// 返回v所拥有的第i个方法的Value。注意v不能是没有赋值的接口。返回的方法都将由v作为接收者。
func (v Value) Method(i int) Value

// 根据方法名获取方法Value。
func (v Value) MethodByName(name string) Value

// NumMethod returns the number of exported methods in the value's method set.
// 返回值的方法集中方法的总数
func (v Value) NumMethod() int


基本类型所使用的Value的方法

// Bool returns v's underlying value. It panics if v's kind is not Bool.
// 布尔值使用,返回底层的布尔值
func (v Value) Bool() bool

// Bytes returns v's underlying value. It panics if v's underlying value is
// not a slice of bytes.
// 字节数组([]byte)使用,返回该数组
func (v Value) Bytes() []byte

// Cap returns v's capacity. It panics if v's Kind is not Array, Chan, or Slice.
// 数组、片段和通道可使用,返回v的长度
func (v Value) Cap() int

// Close closes the channel v. It panics if v's Kind is not Chan.
// 关闭v代表的通道
func (v Value) Close()

// Complex returns v's underlying value, as a complex128. It panics if v's Kind is not Complex64 or Complex128
// complex类型使用,返回对应的值
func (v Value) Complex() complex128

// Elem returns the value that the interface v contains or that the pointer v
// points to. It panics if v's Kind is not Interface or Ptr. It returns the
// zero Value if v is nil.
// 接口和指针使用,返回接口v或者指针v所指向的值
func (v Value) Elem() Value

// Float returns v's underlying value, as a float64. It panics if v's Kind is not Float32 or Float64
// 浮点数类型值使用,返回浮点数
func (v Value) Float() float64

// Index returns v's i'th element. It panics if v's Kind is not Array, Slice,
// or String or i is out of range.
// 数组使用,返回第i个元素
func (v Value) Index(i int) Value

// Int returns v's underlying value, as an int64. It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64.
// 整型值使用,返回对应的整数
func (v Value) Int() int64

// InterfaceData returns the interface v's value as a uintptr pair. It panics
// if v's Kind is not Interface.
// 接口类型使用,返回接口的数据,即类型和值
func (v Value) InterfaceData() [2]uintptr

// IsNil reports whether its argument v is nil. The argument must be a chan,
// func, interface, map, pointer, or slice value; if it is not, IsNil panics.
// Note that IsNil is not always equivalent to a regular comparison with nil in
// Go. For example, if v was created by calling ValueOf with an uninitialized
// interface variable i, i==nil will be true but v.IsNil will panic as v will
// be the zero Value.
// 通道、函数、接口、map、指针和数组片段类型使用,返回是否是nil值。值得注意的是,`v==nil`与
// IsNil的结果不完全一致,当接口值没有被初始化时,本函数会panic,而`==`则不会
func (v Value) IsNil() bool

// Len returns v's length. It panics if v's Kind is not Array, Chan, Map, Slice,
// or String.
// 数组、通道、字典、片段、字符串使用,返回长度
func (v Value) Len() int

// MapIndex returns the value associated with key in the map v. It panics if
// v's Kind is not Map. It returns the zero Value if key is not found in the map
// or if v represents a nil map. As in Go, the key's value must be assignable
// to the map's key type.
// map使用,返回key对应的值
func (v Value) MapIndex(key Value) Value

// map使用,返回所有的键
func (v Value) MapKeys() []Value



函数类型的Value方法

// Call calls the function v with the input arguments in. For example,
// if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
// Call panics if v's Kind is not Func. It returns the output results as Values.
// As in Go, each input argument must be assignable to the type of the function's
// corresponding input parameter. If v is a variadic function, Call creates the
// variadic slice parameter itself, copying in the corresponding values.
// 使用Value的各个值调用函数v,并返回。需要确保in的各个元素,一一对应v所代表的函数。若有可变长
// 参数,本函数将会创建一个片段,并复制对应的值。
func (v Value) Call(in []Value) []Value

// CallSlice calls the variadic function v with the input arguments in,
// assigning the slice in[len(in)-1] to v's final variadic argument.
// For example, if len(in) == 3, v.CallSlice(in) represents the Go call v(in[0],
// in[1], in[2]...). CallSlice panics if v's Kind is not Func or if v is not
// variadic. It returns the output results as Values. As in Go, each input
// argument must be assignable to the type of the function's corresponding input
// parameter.
// 使用in,调用变长参数函数v,给in片段的所有元素,赋值给v的变长参数,并返回值。
func (v Value) CallSlice(in []Value) []Value

结构类型使用的方法

// Field returns the i'th field of the struct v. It panics if v's Kind is not
// Struct or i is out of range.
// 返回第i个字段
func (v Value) Field(i int) Value

// 这些均与Type的定义相同,只不过是返回具体的值,而不是类型
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value

// NumField returns the number of fields in the struct v. It panics if v's Kind
// is not Struct.
// 返回结构v的字段数量
func (v Value) NumField

还没分类

func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
func (v Value) Pointer() uintptr
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, val Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
func (v Value) Slice(i, j int) Value
func (v Value) Slice3(i, j, k int) Value
func (v Value) String() string
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Type() Type
func (v Value) Uint() uint64
func (v Value) UnsafeAddr() uintptr

如何开发go语言项目

总览

  • go的所有源代码只会放在一个工作区
  • 一个工作区中有多个代码仓库(repositories,如git仓库)
  • 每个仓库有一到多个包(packages)
  • 每个包包含了一个到多个go源代码文件
  • 到达包文件夹的路径定义了导入路径(import path)

工作区

一个工作区有三个文件夹

  • src包含了go源代码
  • pkg包含了包对象
  • bin包含可执行的命令行程序

go工具构建源代码,然后把二进制或者包安装到bin和pkg文件夹中。

典型结构如下

bin/
    hello                          # command executable
    outyet                         # command executable
pkg/
    linux_amd64/
        github.com/golang/example/
            stringutil.a           # package object
src/
    github.com/golang/example/
        .git/                      # Git repository metadata
	hello/
	    hello.go               # command source
	outyet/
	    main.go                # command source
	    main_test.go           # test source
	stringutil/
	    reverse.go             # package source
	    reverse_test.go        # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
	bmp/
	    reader.go              # package source
	    writer.go              # package source
    ...

GOPATH环境变量

GOPATH环境变量指定了你的工作区的位置。默认是$HOME/go

方便起见,可以设置一下PATH变量:export PATH=$PATH:$(go env GOPATH)/bin

导入路径

导入路径是唯一定义一个包的一串字符。包的导入路径对应其在工作区或者远程代码仓库的路径。

标准库的导入路径是很短的,但是我们的导入路径需要选一个将来不会与其他项目冲突的路径比较好。基路径可以是github.com/user

然后,我们可以在该文件夹下,创建我们的项目文件夹,然后就可以在项目文件夹内放置你的代码了。

编译安装程序

使用go install命令编译,并安装编译好的程序到bin文件夹下。格式

go install github.com/user/project

注意到我们不需要src开头。

编译程序库

当我们编写好程序库时,使用go build命令,go将会编译程序库,并放置在pkg文件夹下。我们以后能够使用import语句,写入我们的库名,即可调用该程序库。

包名

Go源代码文件的第一行一定是

package name

name是导入之后的默认包名,意思是可以通过该name调用包的函数等。一个包内的所有文件必须使用同一个包名。

Go定义一个包导入路径的最后一个是包名,如crypto/rot13的包名是rot13

可执行程序必须使用main包名。

测试

创建一个文件名以_test.go结尾的文件,包含名为TestXXX的函数,参数为(t *testing.T)。Go的测试框架会执行这些测试函数,如果函数调用了如t.Errort.Fail的失败执行函数,则会认为本次的测试失败。测试文件放置在与需要测试的Go文件相同的目录下。

创建完测试文件后,就可以使用go test加包名的命令,执行测试了。

远程代码包

go get命令会根据参数名,自动判断使用哪种版本管理工具,获取包的源代码,如

go get github.com/golang/example/hello

会使用git获取该库的源代码。下载完成后会放入到GOPATH指定的第一个目录中。

关键字

_

函数有多个返回值时,但是我们不会全部使用返回值,使用_可以忽略某个位置的返回值。这在我们需要的返回值不在前面的时候有用。

fmt

math

strings