GoLang语法
1.0 引入
//firt-in-go.go
package main
import "fmt"
func main(){
fmt.println("firt-print-in-go\n")
// println
}
-
首行包名必须存在,Go通过包管理命名空间
-
import
为引入外包;fmt
为标准输出输出包; -
func
为函数定义的关键字;
Go语言特征:
- 默认UTF-8编码;
- 标志符区分大小写;
- 语句结尾
;
可省略; - 函数{必须在函数开头同一行;
- 调用包中方法采用
.
访问符; - mian函数包名需为main;
- 强类型的静态编译语言
格式化输出:
%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
%t 布尔:true或false
%c 字符(rune) (Unicode码点)
%s 字符串
%q 带双引号的字符串"abc"或带单引号的字符'c'
%v 变量的自然形式(natural format)
%T 变量的类型
%% 字面上的百分号标志(无操作数)
各项零值:
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 //rune的实际类型是 int32
byte 0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool false
string ""
Notion:
,
操作符没有 故而多个操作使用平行赋值i,j=1,1
- 查看变量类型
fmt.println("type is",reflect.typeOf(var name))
fmt.Printf("%T",val)
1.1 Token 词法分析

1.2 标志符
-
规则:
字母和下划线开头,区分大小写;
**数据类型标志符:**20
-
数值:
byte int int8 int16 int32 int64 uint uint8 uint16 uint64 uinprt(整数)
不同类型之间必须强制类型转换;
支持算术/位运算 结果仍为整型;
float32 float64(浮点数)
默认为float64
var a:=1.0
complex64 complex128(复数)
complex real image (构造 返回实部、返回虚部)
var v complex64=3.14+5i v1:=3.14+5i var v2 = complex(3.14,5) a:=real(v)//实部 b:=imag(v)//虚步
-
字符 字符串:string rune
采用UTF-8字符集编码
字符串不可变 无法通过数字下标修改
可以使用操作符 **+**来链接字符串
var a ="直接用字符串初始化" var emptyString string ="" //声明空字符串 //同时声明多个字符串 no,yes,may :="no","yes","may" /*字符串多行声明 使用``*/ s :=`my love` // ``包括的字符串为Raw字符串,在代码中形式即为打印出的形式 没有字符转义 换行亦直接输出 no="yes" //常规赋值 /*可以数组索引下标值 但不可修改*/ var b = "abcdef" c:=b[0] b[0]='1' //ERROR!! /*字符串修改方法*/ /*- 将字符串转化为[]byte类型*/ c :=[]byte(a) c[0]='1' a1 :=string(c) //再回转为string fmt.Println("%s",a1) //输出为1bcdef fmt.Printf("%c",a1[0]) //输出为1 fmt.Printf("%q",a1[0]) //输出为'1' /*- 使用切片操作修改字符串 */ s :="mylove" s="y"+s[1:] fmt.Printf("%s\n",s) /* 字符串尾部不含NULL字符 */ /* “+”操作符链接字符串 可连接多个 s :="try" s1 :="it" s2 :="!" s3 :=s+s1 s4 :=s+s1+s2 fmt.Printf("%s\n",s+s1) fmt.Printf("%s\n",s3) fmt.Printf("%s\n",s+s1+s2) fmt.Printf("%s\n",s4)
字符串可以拼接+ len()返回长度
a:="hello" b:="world" c:=a+b //可以拼接 len(a) //内置函数无需导入 d:="hello,go!" for i:=0;i<len(d);i++ { //数组下标形式访问字符串 fmt.println(d[i]) } for i,v:=rang d{ fmt.println(i,v) }
-
array slice map
-
数组 array
定义方式为
var arr [n]type
其中arr为名称 n为数组长度 type为元素类型 n忽略则需要同时赋值 Go会自动计算长度Len(arr)可直接计算长度
支持数组嵌套成为多维数组
长度不可变 值传递和值赋值
var arr[10]int //长为10的in t类型数组arr 默认为类型的零值 a[0]=1 //从0开始 直接赋值 /*赋值同时声明 */ a:=[3]int{1,2,3} b:=[...]int{1,2,3} //省略长度 自动计算长度 /*二维数组*/ doubleArray:=[2][4]int{[4]int{1,2,3,4},[4]int{1,2,3,4}} doubleArray:=[2][4]int{{1,2,3,4},{1,2,3,4}}
-
slice
即为动态数组,
slice
并不是真正意义上的动态数组,而是一个引用类型。slice
总是指向一个底层array
。// 和声明array一样,只是少了长度 var fslice []int
slice := []byte {'a', 'b', 'c', 'd'}
slice
可以从一个数组或一个已经存在的slice
中再次声明。slice
通过array[i:j]
来获取,其中i
是数组的开始位置,j
是结束位置,但不包含array[j]
,它的长度是j-i
。// 声明一个含有10个元素元素类型为byte的数组 var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个含有byte的slice var a, b []byte// a指向数组的第3个元素开始,并到第五个元素结束, a = ar[2:5] //现在a含有的元素: ar[2]、ar[3]和ar[4] // b是数组ar的另一个slice b = ar[3:5] // b的元素是:ar[3]和ar[4]
slice
的默认开始位置是0,ar[:n]
等价于ar[0:n]
slice
的第二个序列默认是数组的长度,ar[n:]
等价于ar[n:len(ar)]
- 如果从一个数组里面直接获取
slice
,可以这样ar[:]
,因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0:len(ar)]
- 对于
slice
有几个有用的内置函数:len
获取slice
的长度cap
获取slice
的最大容量append
向slice
里面追加一个或者多个元素,然后返回一个和slice
一样类型的`slice``- ``copy
函数
copy从源
slice的
src中复制元素到目标
dst`,并且返回复制的元素的个数
注:
append
函数会改变slice
所引用的数组的内容,从而影响到引用同一数组的其它slice
。 但当slice
中没有剩余空间(即(cap-len) == 0
)时,此时将动态分配新的数组空间。返回的slice
数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice
则不受影响。// 声明一个数组 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个slice var aSlice, bSlice []byte // 演示一些简便操作 aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素 // 从slice中获取slice aSlice = array[3:7] // aSlice包含元素: d,e,f,g bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g
从概念上面来说
slice
像一个结构体,这个结构体包含了三个元素:-
一个指针,指向数组中
slice
指定的开始位置 即为引用修改 -
长度,即
slice
的长度 -
最大长度,也就是
slice
开始位置到数组的最后位置的长度Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} Slice_a := Array_a[2:5]
map
map//也就是Python中字典的概念 map[keyType]valueType // 类似表格 key对应着values
slice
的index
只能是int
类型,map
是int
,可以是string
及所有完全定义了==
与!=
操作的类型。// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化 var numbers map[string]int // 另一种map的声明方式 numbers = make(map[string]int) numbers["one"] = 1 //赋值 numbers["ten"] = 10 //赋值 numbers["three"] = 3 fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 // 打印出来如:第三个数字是: 3
Note:
map
是无序的,每次打印出来的map
都会不一样,它不能通过index
获取,而必须通过key
获取map
的长度是不固定的,也就是和slice
一样,也是一种引用类型- 内置的
len
函数同样适用于map
,返回map
拥有的key
的数量 map
的值可以很方便的修改,通过numbers["one"]=11
可以很容易的把key为one
的字典值改为11
map
有两个返回值,第一个为values第二个为存在否,存在为true,不存在为falsemap
和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
map
的初始化可以通过key:val
的方式初始化值,同时map
内置有判断是否存在key
的方式通过
delete
删除map
的元素:// 初始化一个字典 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true csharpRating, ok := rating["C#"] if ok { fmt.Println("C# is in the map and its rating is ", csharpRating) }else { fmt.Println("We have no rating associated with C# in the map") } delete(rating, "C") // 删除key为C的元素
map
也是一种引用类型,如果两个map
同时指向一个底层,那么一个改变,另一个也相应的改变:m := make(map[string]string) m["Hello"] = "Bonjour" m1 := m m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
-
-
接口:error
-
布尔:bool
默认false;&& || ! 操作符
仅两值 ture false
var ok bool
ok = true
//ok:=true
表达式/逻辑表达式的结果为bool;if for条件为bool
**内置函数:**15 全局可用 无需导入import
make new lwn cap append copy delete panic recover close complex real image Print Println
**常量标志符:**4
ture false;iota(连续枚举);nil(指针/引用变量默认值nil)
**空白标志符:**1
_
//空白标志符用于去除第一项值
sum :=0
for _;value:=range array{
sum+=value
}
var a string = "inital"
fmt.Println(a)
//输出inital
var b,c int =1,2
fmt.Println(b,c)
//输出1 2
var d =true
fmt.Println(!d)
//输出false
var e int
fmt.Println(e)
//输出0 默认int未赋值
f :="one" //命名方式 无须声明
fmt.Println(f)
const s string = "constant"
fmt.Println(s)
const n =50000000
const d = 3e20 / n
fmt.println(d)
fmt.println(int64(d))
fmt.println(math.Sin(n))
1.3 变量
var 变量名称 类型 = 表达式
其中 类型/表达式可忽略一个 类型忽略自行推倒;表达式忽略初始化0值 数值=0 boolean=false string=""空 接口。引用(slice 指针 map chan 函数)对应零值=nil
即Go中无未初始化的变量
//同时声明多个变量
var i,j,k int
var b,f,s=true,3.14,"four" //bool,float64,string
var names []string
i,j:=1,0
**:=**是变量声明语句 **=**是赋值操作 i,j=j,i //交换值
- := 其左边变量不一定是第一次声明,如果已经声明 则:=仅用于赋值 但其中至少有一个变量是新声明
in ,err :=os.Open(infile)
out,err :=os.Create(outfile) //out声明并赋值 err仅赋值
f,err :=os.Open(infile)
f,err :=os.Create(outfile) // error! 至少一个声明
指针:
var x int;p=&x
称p指针指向变量x *p表达式为读取p指针指向的变量x的值
x :=1
p :=&x
fmt.Println(*p) //p of type *int,points to x
*p=2 //equivalent to x=2
fmt.Println(x)
- 不可
++age
但可以age++
1.4 控制结构
控制结构的左大括号不可另起一行 否则会自动在大括号前插入;
reason: Go正式语法以; 分号结束语句 词法分析器自动插入分号 故而源码中不必使用;
词法分析器插入规则:新行前最后一个标记为标志符、数值、字符串常量 或者 break continue fallthrough return ++ -- ) }
等将在该标记后插入;
新行前的标记为语句末尾 则插入分号; 分号还可在闭括号之前省略 go func() { for {dst <- <-src}} ()
**关键字 keywords:**25个
-
引导程序整体结构:8
package、import、const、var(变量声明关键字)、func、defer(延迟执行)、go(并发语法关键字)、return
-
复合数据结构:4
struct、interface(接口类型)、map(声明/创建map)、chan(声明/创建通道)
-
控制程序结构:13
if eles、for range break continue(循环)、switch select type case default fallthroug(select/switch语句关键字)、goto
if
if...else...
条件表达式的值必须为boolean 可以省略括号 左花括号不可另起一行
条件表达式可以定义局部变量 初始化函数**(作用域为if/else块)** 可接受初始化语句
if x>0 {
return y
}
//初始化语句
if err:=file.Chmod(0664);err!=nil{
log.Print(err)
return err
}
go to
goto
跳转必须在函数内定义的标签
func main(){
i:=0
Here:
fmt.Printf(i)
i++
goto Here
}
For
可以循环又可以控制逻辑作为while使用
for expression1;expression2;expression3{}
e1\e3均为变量声明 函数调用返回值 e2判断条件 e1在循环开始前调用 e3在每轮循环结束后调用
e1 e3可省略 for e2{}即为仅仅判断e2的条件 相当于while使用 break
跳出所有循环 continue
结束本次循环 继续下一次
无 ,
操作符 多变量使用平行赋值
//for的三种用法
for init;condition;post{} //类似C
for condition {} //类似C中的while
for{} //类似C中for(;;)
sum:=0
for i:=0;i<10;i++{
sum+=i
}
//遍历数组 切片 字符串 映射 信道读取 使用rang
for key,value:=range oldMap {
newMap[key] =value
}
//range在字符串中解析utf-8 独立的Unicode码点分离出来 错误编码占据一个字节 以U+FFFD代替1 `1`
for pos, char := range "日本\x80語" { // \x80 在 UTF-8 编码中是一个非法字符
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
//go无,逗号操作符 在for中多个变量应该采用平行赋值
for i,j:=0,len(a)-1;i<j;i,j=i+1,j-1{
a[i],a[j]=a[j],a[i]
}
Switch:
//使用说明
switch sExpr{
case e1:
s1
case e2:
s2
}
- 其中switch sExpr必须和e1 e2为同一类型 sExpr无表达式为true
- 默认case 带有break;可使用
fallthroug
强制执行后续case
make new使用:
make用于内建类型(map slice channel)内存分配;new可以用于全部内存分配
new(Type) 分配零值的Type类型的内存空间,返回地址为*Type 返回指针,指向新分配的类型Type的零值
New(T) make(T,args)
new返回指针 make返回初始化后非零值
1.5 函数
-
保留函数
init main
不存在参数和返回值 -
import
-
支持相对和绝对路径的导入,
import "./model"
当前文件同目录下import "short/model"
加载footpath/src/short/model -
import ( ."fmt)
使得调用包内函数可以忽略包名fmt.Println("")
——>Println("")
-
// 将包别名化 使用为f.Println() import ( f "fmt" ) // _操作 impot ( "database/sql" _"gihub.com/ziutek/mymysql/godrv" //_操作为引入包但不直接食用包内函数,调用包内init函数 )
-
func关键字声明;
func funcName(input1 type, inpute2 type)(output1 type,output type){
return v1,v2
}
- 多参输入
,
分隔 多个返回值; - 返回值声明变量 output1 output2可忽略
- 可接受变参
func SumAndProduct(A,B int)(int,int) {
return A+B , A*B
}
func main(){
x:=3
y:=5
x1,x2:=SumAndProduct(x,y)
fmt.Printf("%d+%d=%d\n",x,y,x1)
fmt.Printf("%d*%d=%d\n",x,y,x2)
}
-
支持变参输入
func myFunc(arg ...int){}
arg ...int即为不定参数的int输入 arg则为int的slice
延迟 defer:
函数作为值和类型:
在函数中传递函数类型
1.6 Struct 结构体
//定义和使用type person struct { name string age int}var P personfunc main() { /*逐个赋值 */ P.name = "hello" P.age = 22 P.name,P.age="qq",01 /* 直接使用并赋值 */ P1:=person{"world",18} fmt.Printf("%s\n %d\n", P.name, P.age) //错误使用 fmt.Printf("%s\n",P1) // {world %!s (int=18)} /*通过field:value初始化任意顺序 */ P3:=person{age:24,name:"aaa"} /* new函数分配指针 类型为*person */ P4:=new(person)}
匿名字段
将多个结构体中公共部分字段拿出来单独作为结构体,使之嵌套在其他结构体中,可以略过中间结构体名
外在结构体可以直接访问内在结构体的属性 同样可以多重嵌套
type Point struct { x, y int } type Circle struct { Point Radius int } type Wheel struct { Circle spokes int } var w Wheel//多层嵌套,同时赋值 w = Wheel{Circle{Point{1, 1}, 1}, 1} fmt.Println(w)
1.7 面向对象 -method
func (recv ReceiverType) methName(parameters)(results)
可认为是特殊类型的函数 recv.Method()
package mainimport "fmt"//"github.com/sqs/goreturns/returns"// "time"type TwoInts struct { a, b int}func (tn *TwoInts) AddThem() int { return tn.a + tn.b}func (tn *TwoInts) AddtoParam(param int) int { return tn.a + tn.b + param}/*以上需在main之外声明 */func main() { two1 := new(TwoInts) two1.a, two1.b = 12, 10 fmt.Printf("sum %d\n", two1.AddThem()) fmt.Printf("add this to param %d\n", two1.AddtoParam(20)) two2 := TwoInts{3, 4} fmt.Printf("sum %d\n", two2.AddThem())}
- 函数&方法比较:
- 函数参数为变量
fuc(recv)
方法在变量上被调用recv.emthod()
- 接受者为指针时 方法可以改值 函数引用调用亦可
- 接受者必须有显式名称 名称必须被调用 receivertype 必须在方法相同的包内被声明
- 函数参数为变量
1.8 接口 interface
- 利于代码与特定实现分离
1.9 go module
go弃用了gopath模式 转向gomodule
Go mod 常用命令
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
终端查看:
go env
查看go相关环境变量
go env -w
修改 +对应
开关 go env -w GO111MODULE=on
初始化项目
在完成 Go modules 的开启后,我们需要创建一个示例项目来进行演示,执行如下命令:
$ mkdir -p $HOME/eddycjy/module-repo $ cd $HOME/eddycjy/module-repo
然后进行Go modules的初始化,如下:
$ go mod init github.com/eddycjy/module-repogo: creating new go.mod: module github.com/eddycjy/module-repo
在执行 go mod init
命令时,我们指定了模块导入路径为 github.com/eddycjy/module-repo
。接下来我们在该项目根目录下创建 main.go 文件,如下:
package mainimport ( "fmt" "github.com/eddycjy/mquote")func main() { fmt.Println(mquote.GetHello())}
然后在项目根目录执行 go get github.com/eddycjy/mquote
命令,如下:
$ go get github.com/eddycjy/mquote go: finding github.com/eddycjy/mquote latestgo: downloading github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6fgo: extracting github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
查看go.mod 文件
在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。
在我们刚刚进行了初始化和简单拉取后,我们再次查看go.mod文件,基本内容如下:
module github.com/eddycjy/module-repogo 1.15.2require ( github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f)
为了更进一步的讲解,我们模拟引用如下:
module github.com/eddycjy/module-repogo 1.15.2require ( example.com/apple v0.1.2 example.com/banana v1.2.3 example.com/banana/v2 v2.3.4 example.com/pear // indirect example.com/strawberry // incompatible)exclude example.com/banana v1.2.4replace example.com/apple v0.1.2 => example.com/fried v0.1.0 replace example.com/banana => example.com/fish
- module:用于定义当前项目的模块路径。
- go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识作用。
- require:用于设置一个特定的模块版本。
- exclude:用于从使用中排除一个特定的模块版本。
- replace:用于将一个模块版本替换为另外一个模块版本。
另外你会发现 example.com/pear
的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get
拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。
查看go.sum文件
在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=...
我们可以看到一个模块路径可能有如下两种:
github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。
而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。
查看全局缓存
我们刚刚成功的将 github.com/eddycjy/mquote
模块拉取了下来,其拉取的结果缓存在 $GOPATH/pkg/mod
和 $GOPATH/pkg/sumdb
目录下,而在mod
目录下会以 github.com/foo/bar
的格式进行存放,如下:
mod├── cache├── github.com├── golang.org├── google.golang.org├── gopkg.in...
需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache
命令。
1.10 error接口
error为预定义的类型 interface
type error interface { Error() string}
调用errors.new函数即可利用传入的错误信息返回新的error
整个errors包仅只有4行:
package errorsfunc New(text string) error { return &errorString{text} }type errorString struct { text string }func (e *errorString) Error() string { return e.text }
承载errorString的类型是一个结构体
package syscalltype Errno uintptr // operating system error codevar errors = [...]string{ 1: "operation not permitted", // EPERM 2: "no such file or directory", // ENOENT 3: "no such process", // ESRCH // ...}func (e Errno) Error() string { if 0 <= int(e) && int(e) < len(errors) { return errors[e] } return fmt.Sprintf("errno %d", e)}
1.11 reflect
反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个Go类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。唯一能反映 reflect.Type 实现的是接口的类型描述信息(§7.5),也正是这个实体标识了接口值的动态类型。
函数 reflect.TypeOf 接受任意的 interface{} 类型,并以 reflect.Type 形式返回其动态类型:
t := reflect.TypeOf(3) // a reflect.Typefmt.Println(t.String()) // "int"fmt.Println(t) // "int"
其中 TypeOf(3) 调用将值 3 传给 interface{} 参数。回到 7.5节 的将一个具体的值转为接口类型会有一个隐式的接口转换操作,它会创建一个包含两个信息的接口值:操作数的动态类型(这里是 int)和它的动态的值(这里是 3)。
因为 reflect.TypeOf 返回的是一个动态类型的接口值,它总是返回具体的类型。因此,下面的代码将打印 "*os.File" 而不是 "io.Writer"。稍后,我们将看到能够表达接口类型的 reflect.Type。
var w io.Writer = os.Stdoutfmt.Println(reflect.TypeOf(w)) // "*os.File"
要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的。因为打印一个接口的动态类型对于调试和日志是有帮助的, fmt.Printf 提供了一个缩写 %T 参数,内部使用 reflect.TypeOf 来输出:
fmt.Printf("%T\n", 3) // "int"
reflect 包中另一个重要的类型是 Value。一个 reflect.Value 可以装载任意类型的值。函数 reflect.ValueOf 接受任意的 interface{} 类型,并返回一个装载着其动态值的 reflect.Value。和 reflect.TypeOf 类似,reflect.ValueOf 返回的结果也是具体的类型,但是 reflect.Value 也可以持有一个接口值。
v := reflect.ValueOf(3) // a reflect.Valuefmt.Println(v) // "3"fmt.Printf("%v\n", v) // "3"fmt.Println(v.String()) // NOTE: "<int Value>"
和 reflect.Type 类似,reflect.Value 也满足 fmt.Stringer 接口,但是除非 Value 持有的是字符串,否则 String 方法只返回其类型。而使用 fmt 包的 %v 标志参数会对 reflect.Values 特殊处理。
对 Value 调用 Type 方法将返回具体类型所对应的 reflect.Type:
t := v.Type() // a reflect.Typefmt.Println(t.String()) // "int"
reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法。它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值:
v := reflect.ValueOf(3) // a reflect.Valuex := v.Interface() // an interface{}i := x.(int) // an intfmt.Printf("%d\n", i) // "3"
reflect.Value 和 interface{} 都能装载任意的值。所不同的是,一个空的接口隐藏了值内部的表示方式和所有方法,因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样),内部值我们没法访问。相比之下,一个 Value 则有很多方法来检查其内容,无论它的具体类型是什么。让我们再次尝试实现我们的格式化函数 format.Any。
我们使用 reflect.Value 的 Kind 方法来替代之前的类型 switch。虽然还是有无穷多的类型,但是它们的 kinds 类型却是有限的:Bool、String 和 所有数字类型的基础类型;Array 和 Struct 对应的聚合类型;Chan、Func、Ptr、Slice 和 Map 对应的引用类型;interface 类型;还有表示空值的 Invalid 类型。(空的 reflect.Value 的 kind 即为 Invalid。)
package formatimport ( "reflect" "strconv")// Any formats any value as a string.func Any(value interface{}) string { return formatAtom(reflect.ValueOf(value))}// formatAtom formats a value without inspecting its internal structure.func formatAtom(v reflect.Value) string { switch v.Kind() { case reflect.Invalid: return "invalid" case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(v.Uint(), 10) // ...floating-point and complex cases omitted for brevity... case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: return strconv.Quote(v.String()) case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16) default: // reflect.Array, reflect.Struct, reflect.Interface return v.Type().String() + " value" }}
到目前为止,我们的函数将每个值视作一个不可分割没有内部结构的物品,因此它叫 formatAtom。对于聚合类型(结构体和数组)和接口,只是打印值的类型,对于引用类型(channels、functions、pointers、slices 和 maps),打印类型和十六进制的引用地址。虽然还不够理想,但是依然是一个重大的进步,并且 Kind 只关心底层表示,format.Any 也支持具名类型。例如:
var x int64 = 1var d time.Duration = 1 * time.Nanosecondfmt.Println(format.Any(x)) // "1"fmt.Println(format.Any(d)) // "1"fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
1.12单元测试
go test
使用该命令时以 _test.go
后缀的源文件在gobuild
时不会成为包的一部分
遍历所有 *_test.go
的文件
测试函数
func TestName (t *testing.T){}
必须以Test开头 后缀名可选且必须大写字母
e.g. func TestSin(t *testing.T){}
t参数用于报告测试失败和附加日志