Skip to content

关键字与标识符

本文档详细介绍Go语言的关键字、标识符命名规则和变量声明方式,是学习Go语法的基础。

📋 目录

Go语言关键字

25个保留关键字

Go语言共有25个关键字,这些关键字不能用作标识符:

类别关键字用途说明
程序结构package, import包声明和导入
声明定义var, const, type, func变量、常量、类型、函数声明
流程控制if, else, for, range条件和循环控制
switch, case, default, fallthrough分支控制
break, continue, goto跳转控制
return, defer函数控制
并发编程go, chan, select协程和通道
数据结构struct, interface, map结构体、接口、映射

关键字详细说明

程序结构关键字

go
package main        // 声明包名
import "fmt"        // 导入包

声明关键字

go
var name string     // 变量声明
const pi = 3.14     // 常量声明
type Person struct  // 类型声明
func hello()        // 函数声明

流程控制关键字

go
if condition {      // 条件判断
    // 执行代码
} else {
    // 其他情况
}

for i := 0; i < 10; i++ {  // 循环控制
    if i == 5 {
        break       // 跳出循环
    }
    if i == 3 {
        continue    // 继续下一次循环
    }
}

switch value {      // 分支选择
case 1:
    // 处理情况1
    fallthrough     // 继续执行下一个case
case 2:
    // 处理情况2
default:
    // 默认情况
}

并发编程关键字

go
go func() {         // 启动协程
    // 并发执行的代码
}()

ch := make(chan int)  // 创建通道

select {            // 多路选择
case data := <-ch:
    // 处理接收到的数据
default:
    // 默认情况
}

标识符命名规则

基本规则

标识符用于命名变量、函数、类型等程序实体,必须遵循以下规则:

  1. 组成字符:字母、数字、下划线 _
  2. 开头字符:必须是字母或下划线,不能是数字
  3. 大小写敏感Namename 是不同的标识符
  4. 长度限制:无限制,但建议简洁明了

可见性规则

Go语言通过标识符的首字母大小写控制可见性:

首字母可见性说明示例
大写公开(Public)包外可访问Name, GetAge()
小写私有(Private)包内可访问name, getAge()
go
package user

// 公开的结构体和字段
type User struct {
    Name string    // 公开字段
    age  int       // 私有字段
}

// 公开的方法
func (u *User) GetName() string {
    return u.Name
}

// 私有的方法
func (u *User) setAge(age int) {
    u.age = age
}

命名约定

1. 变量命名

go
// 推荐:驼峰命名法
var userName string
var userAge int
var isActive bool

// 不推荐:下划线命名
var user_name string
var user_age int

2. 常量命名

go
// 普通常量:驼峰命名
const maxRetries = 3
const defaultTimeout = 30

// 枚举常量:通常使用iota
const (
    StatusPending = iota
    StatusRunning
    StatusCompleted
    StatusFailed
)

3. 函数和方法命名

go
// 函数:动词开头,驼峰命名
func calculateTotal() int { }
func getUserById(id int) *User { }

// 布尔返回值:Is/Has/Can开头
func isValid() bool { }
func hasPermission() bool { }
func canAccess() bool { }

4. 类型命名

go
// 结构体:名词,首字母大写(如果需要导出)
type UserManager struct { }
type httpClient struct { }

// 接口:通常以-er结尾
type Reader interface { }
type Writer interface { }
type UserService interface { }

变量声明

声明方式

Go语言提供多种变量声明方式:

1. 使用var关键字

基本语法:

go
var variableName type

示例:

go
var name string        // 声明字符串变量
var age int           // 声明整数变量
var isActive bool     // 声明布尔变量
var score float64     // 声明浮点数变量

定义多个变量

go
//定义三个类型都是“type”的变量
var vname1, vname2, vname3 type

定义变量并初始化值

go
//初始化“variableName”的变量为“value”值,类型是“type”
var variableName type = value

同时初始化多个变量

go
/*
    定义三个类型都是"type"的变量,并且分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
*/
var vname1, vname2, vname3 type= v1, v2, v3

是不是觉得上面这样的定义有点繁琐?有一种写法可以让它变得简单一点。可以直接忽略类型声明,那么上面的代码变成这样了:

go
/*
    定义三个变量,它们分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
    然后Go会根据其相应值的类型来初始化它们
*/
var vname1, vname2, vname3 = v1, v2, v3

觉得上面的还是有些繁琐,继续简化:

go
/*
    定义三个变量,它们分别初始化为相应的值
    vname1为v1,vname2为v2,vname3为v3
    编译器会根据初始化的值自动推导出相应的类型
*/
vname1, vname2, vname3 := v1, v2, v3

现在是不是看上去非常简洁了?:=这个符号直接取代了vartype,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。

_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,将值35赋予b,并同时丢弃34

go
_, b := 34, 35

Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了i但未使用。

go
package main
func main() {
    var i int
}

常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。

它的语法如下:

go
const constantName = value
//如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926

下面是一些常量声明的例子:

go
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"

Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位),若指定给float32自动缩短为32bit,指定给float64自动缩短为64bit,详情参考 http://golang.org/ref/spec#Constants (需科学上网)

内置基础类型

Boolean

在Go中,布尔值的类型为bool,值是truefalse,默认为false

go
//示例代码
var isActive bool  // 全局变量声明
var enabled, disabled = true, false  // 忽略类型的声明
func test() {
    var available bool  // 一般声明
    valid := false      // 简短声明
    available = true    // 赋值操作
}

数值类型

整数类型有无符号和带符号两种。Go同时支持intuint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune, int8, int16, int32, int64byte, uint8, uint16, uint32, uint64。其中runeint32的别称,byteuint8的别称。

需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。

如下的代码会产生错误:invalid operation: a + b (mismatched types int8 and int32)

var a int8

var b int32

c:=a + b

另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。

浮点数的类型有float32float64两种(没有float类型),默认是float64

Go还支持复数。它的默认类型是complex128(64位实数+64位虚数)。如果需要小一些的,也有complex64(32位实数+32位虚数)。复数的形式为RE + IMi,其中RE是实数部分,IM是虚数部分,而最后的i是虚数单位。下面是一个使用复数的例子:

go
var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Value is: %v", c)

字符串

Go中的字符串都是采用UTF-8字符集编码。字符串是用一对双引号("")或反引号()括起来定义,它的类型是string

go
//示例代码
var frenchHello string  // 声明变量为字符串的一般方法
var emptyString string = ""  // 声明了一个字符串变量,初始化为空字符串
func test() {
    no, yes, maybe := "no", "yes", "maybe"  // 简短声明,同时声明多个变量
    japaneseHello := "Konichiwa"  // 同上
    frenchHello = "Bonjour"  // 常规赋值
}

在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0]

go
var s string = "hello"
s[0] = 'c'

但如果真的想要修改怎么办呢?下面的代码可以实现:

go
s := "hello"
c := []byte(s)  // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c)  // 再转换回 string 类型
fmt.Printf("%s\n", s2)

Go中可以使用+操作符来连接两个字符串:

go
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)

修改字符串也可写为:

go
s := "hello"
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
fmt.Printf("%s\n", s)

如果要声明一个多行的字符串怎么办?可以通过```来声明:

go
m := `hello
    world`

``` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。例如本例中会输出:

go
hello
    world

错误类型

Go内置有一个error类型,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误:

go
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
    fmt.Print(err)
}

分组声明

在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。

例如下面的代码:

go
import "fmt"
import "os"
const i = 100
const pi = 3.1415
const prefix = "Go_"
var i int
var pi float32
var prefix string

可以分组写成如下形式:

go
import(
    "fmt"
    "os"
)
const(
    i = 100
    pi = 3.1415
    prefix = "Go_"
)
var(
    i int
    pi float32
    prefix string
)

iota枚举

Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,const中每增加一行加1:

go
package main
import (
    "fmt"
)
const (
    x = iota // x == 0
    y = iota // y == 1
    z = iota // z == 2
    w        // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
const (
    h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)
const (
    a       = iota //a=0
    b       = "B"
    c       = iota             //c=2
    d, e, f = iota, iota, iota //d=3,e=3,f=3
    g       = iota             //g = 4
)
func main() {
    fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}

除非被显式设置为其它值或iota,每个const分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota

Go程序设计的一些规则

Go之所以会那么简洁,是因为它有一些默认的行为:

  • 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
  • 大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。

arrayslicemap

array

array就是数组,它的定义方式如下:

go
var arr [n]type

[n]type中,n表示数组的长度,type表示存储元素的类型。对数组的操作和其它语言类似,都是通过[]来进行读取或赋值:

go
var arr [10]int  // 声明了一个int类型的数组
arr[0] = 42      // 数组下标是从0开始的
arr[1] = 13      // 赋值操作
fmt.Printf("The first element is %d\n", arr[0])  // 获取数据,返回42
fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0

由于长度也是数组类型的一部分,因此[3]int[4]int是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的slice类型了。

数组可以使用另一种:=来声明

go
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度

Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组:

go
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// 上面的声明可以简化,直接忽略内部的类型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

slice

在很多应用场景中,数组并不能满足需求。在初始定义数组时,并不知道需要多大的数组,因此就需要“动态数组”。在Go里面这种数据结构叫slice

slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层arrayslice的声明也可以像array一样,只是不需要长度。

go
// 和声明array一样,只是少了长度
var fslice []int

接下来可以声明一个slice,并初始化数据,如下所示:

go
slice := []byte {'a', 'b', 'c', 'd'}

slice可以从一个数组或一个已经存在的slice中再次声明。slice通过array[i:j]来获取,其中i是数组的开始位置,j是结束位置,但不包含array[j],它的长度是j-i

go
// 声明一个含有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和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内没有任何字符。

slice有一些简便的操作

  • slice的默认开始位置是0,ar[:n]等价于ar[0:n]

  • slice的第二个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar)]

  • 如果从一个数组里面直接获取slice,可以这样ar[:],因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0:len(ar)]

下面这个例子展示了更多关于slice的操作:

go
// 声明一个数组
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,len=4,cap=7
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是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的aSlicebSlice,如果修改了aSlice中元素的值,那么bSlice相对应的值也会改变。

从概念上面来说slice像一个结构体,这个结构体包含了三个元素:

  • 一个指针,指向数组中slice指定的开始位置

  • 长度,即slice的长度

  • 最大长度,也就是slice开始位置到数组的最后位置的长度

go
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
    Slice_a := Array_a[2:5]

slice有几个有用的内置函数

  • len 获取slice的长度

  • cap 获取slice的最大容量

  • appendslice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice

  • copy 函数copy从源slicesrc中复制元素到目标dst,并且返回复制的元素的个数

注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice

但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。

从Go1.2开始slice支持了三个参数的slice,之前一直采用这种方式在slice或者array基础上来获取一个slice

go
var array [10]int
slice := array[2:4]

这个例子里面slice的容量是8,新版本里面可以指定这个容量

go
slice = array[2:4:7]

上面这个的容量就是7-2,即5。这样这个产生的新的slice就没办法访问最后的三个元素。

如果slice是这样的形式array[:i:j],即第一个参数为空,默认值就是0。

map

go
map`也就是Python中字典的概念,它的格式为`map[keyType]valueType

看下面的代码,map的读取和设置也类似slice一样,通过key来操作,只是sliceindex只能是`int`类型,而map多了很多类型,可以是int,可以是string及所有完全定义了==!=操作的类型。

go
// 声明一个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

这个map就像平常看到的表格一样,左边列是key,右边列是值

使用map过程中需要注意的几点:

  • map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取

  • map的长度是不固定的,也就是和slice一样,也是一种引用类型

  • 内置的len函数同样适用于map,返回map拥有的key的数量

  • map的值可以很方便的修改,通过numbers["one"]=11可以很容易的把key为one的字典值改为11

  • map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制

map的初始化可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式

通过delete删除map的元素:

go
// 初始化一个字典
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同时指向一个底层,那么一个改变,另一个也相应的改变:

go
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut"  // 现在m["hello"]的值已经是Salut了

makenew操作

make用于内建类型(mapslicechannel)的内存分配。new用于各种类型的内存分配。

内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:

new返回指针。

内建函数make(T, args)new(T)有着不同的功能,make只能创建slicemapchannel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slicenil。对于slicemapchannel来说,make初始化了内部的数据结构,填充适当的值。

make返回初始化后的(非零)值。

零值

关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。

此处罗列 部分类型 的 “零值”

go
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  ""

流程控制

Go中流程控制分三大类:条件判断,循环控制和无条件跳转。

if

if也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。

Go里面if条件判断语句中不需要括号,如下代码所示

go
if x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

Go的if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示

go
// 计算获取值x,然后根据x返回的大小,判断是否大于10。
if x := computedValue(); x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}
//这个地方如果这样调用就编译出错了,因为x是条件里面的变量
fmt.Println(x)

多个条件的时候如下所示:

go
if integer == 3 {
    fmt.Println("The integer is equal to 3")
} else if integer < 3 {
    fmt.Println("The integer is less than 3")
} else {
    fmt.Println("The integer is greater than 3")
}

goto

Go有goto语句——请明智地使用它。用goto跳转到必须在当前函数内定义的标签。例如假设这样一个循环:

go
func myFunc() {
    i := 0
Here:   //这行的第一个词,以冒号结束作为标签
    println(i)
    i++
    goto Here   //跳转到Here去
}

标签名是大小写敏感的。

for

Go里面最强大的一个控制逻辑就是for,它既可以用来循环读取数据,又可以当作while来控制逻辑,还能迭代操作。它的语法如下:

go
for expression1; expression2; expression3 {
    //...
}

expression1expression2expression3都是表达式,其中expression1expression3是变量声明或者函数调用返回值之类的,expression2是用来条件判断,expression1在循环开始之前调用,expression3在每轮循环结束之时调用。

一个例子比上面讲那么多更有用,看看下面的例子吧:

go
package main
import "fmt"
func main(){
    sum := 0;
    for index:=0; index < 10 ; index++ {
        sum += index
    }
    fmt.Println("sum is equal to ", sum)
}
// 输出:sum is equal to 45

有些时候需要进行多个赋值操作,由于Go里面没有,操作符,那么可以使用平行赋值i, j = i+1, j-1

有些时候如果忽略expression1expression3

go
sum := 1
for ; sum < 1000;  {
    sum += sum
}

其中;也可以省略,那么就变成如下的代码了,这就是while的功能。

go
sum := 1
for sum < 1000 {
    sum += sum
}

在循环里面有两个关键操作breakcontinue ,break操作是跳出当前循环,continue是跳过本次循环。当嵌套过深的时候,break可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子:

go
for index := 10; index>0; index-- {
    if index == 5{
        break // 或者continue
    }
    fmt.Println(index)
}
// break打印出来10、9、8、7、6
// continue打印出来10、9、8、7、6、4、3、2、1

breakcontinue还可以跟着标号,用来跳到多重循环中的外层循环

for配合range可以用于读取slicemap的数据:

go
for k,v:=range map {
    fmt.Println("map's key:",k)
    fmt.Println("map's val:",v)
}

由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃不需要的返回值

例如

go
for _, v := range map{
    fmt.Println("map's val:", v)
}

switch

有些时候需要写很多的if-else来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候switch就能很好的解决这个问题。它的语法如下

go
switch sExpr {
case expr1:
    some instructions
case expr2:
    some other instructions
case expr3:
    some other instructions
default:
    other code
}

sExprexpr1expr2expr3的类型必须一致。Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果switch没有表达式,它会匹配true

go
i := 10
switch i {
case 1:
    fmt.Println("i is equal to 1")
case 2, 3, 4:
    fmt.Println("i is equal to 2, 3 or 4")
case 10:
    fmt.Println("i is equal to 10")
default:
    fmt.Println("All I know is that i is an integer")
}

在第5行中,把很多值聚合在了一个case里面,同时,Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。

go
integer := 6
switch integer {
case 4:
    fmt.Println("The integer was <= 4")
    fallthrough
case 5:
    fmt.Println("The integer was <= 5")
    fallthrough
case 6:
    fmt.Println("The integer was <= 6")
    fallthrough
case 7:
    fmt.Println("The integer was <= 7")
    fallthrough
case 8:
    fmt.Println("The integer was <= 8")
    fallthrough
default:
    fmt.Println("default case")
}

上面的程序将输出

The integer was <= 6
The integer was <= 7
The integer was <= 8
default case

反射

Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,laws of reflection 链接地址为 http://golang.org/doc/articles/laws_of_reflection.html

使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:

go
t := reflect.TypeOf(i)    //得到类型的元数据,通过t能获取类型定义里面的所有元素
v := reflect.ValueOf(i)   //得到实际的值,通过v获取存储在里面的值,还可以去改变值

转化为reflect对象之后就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如

go
tag := t.Elem().Field(0).Tag  //获取定义在struct里面的标签
name := v.Elem().Field(0).String()  //获取存储在第一个字段里面的值

获取反射值能返回相应的类型和数值

go
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

最后,反射的话,那么反射的字段必须是可修改的,前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误

go
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

如果要修改相应的值,必须这样写

go
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

总结

在程序设计中,容错是相当重要的一部分工作,在Go中它是通过错误处理来实现的,error虽然只是一个接口,但是其变化却可以有很多,可以根据自己的需求来实现不同的处理。

基于 MIT 许可发布