声明:内容来自《Go语言编程》
[TOC]
1 变量
1.1 声明
var v1 int var v2 string var v3 [10]int //数组 var v4 []int //数组切片 var v5 struct{ f int } var v6 *int //指针 var v7 map[string] int //map key为string value为int var v8 func(a int) int //? //将若干个变量声明在一起,避免了重复写var var ( v1 int v2 string )
1.2 初始化
var v1 int = 10 // 正确的使用方式1 var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型 v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型
PS:出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误。
1.3 赋值
支持多重赋值功能:
i,j=j,i
1.4 匿名变量
在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。这个时候就可以使用匿名变量来接收不需要的值。eg:
func GetName() (firstName, lastName, nickName string) { return "May", "Chan", "Chibi Maruko" } _,_,nickName := GetName()
2 常量
Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、 浮点型和复数类型)、布尔类型、字符串类型等。
2.1 字面常量
所谓字面常量(literal),是指程序中硬编码的常量。eg:
-12 3.14159265358979323846 // 浮点类型的常量 3.2+12i // 复数类型的常量 true // 布尔类型的常量 "foo" // 字符串常量
Go语言的字面常量更接近我们自然语言中的常量概念,它是无类型的。只要这个常量在相应类型的值域 范围内,就可以作为该类型的常量,比如上面的常量-12,它可以赋值给int、uint、int32、 int64、float32、float64、complex64、complex128等类型的变量。
2.2 常量的定义
通过const关键字,给字面常量指定一个友好的名字。
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // 无类型浮点常量 const ( size int64 = 1024 eof = -1 // 无类型整型常量 ) const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值 const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", 无类型整型和字符串常量
Go的常量定义可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它 与字面常量一样,是无类型常量。
常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达 式。
2.3 预定义常量
Go语言预定义了这些常量:true、false和iota。
- iota : iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被 重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。
const ( // iota被重设为0 c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota在每个const开头被重设为0) b = 1 << iota // b == 2 c = 1 << iota // c == 4 ) const ( u = iota * 42 // u == 0 v float64 = iota * 42 // v == 42.0 w = iota * 42 // w == 84 ) const x = iota // x == 0 (因为iota又被重设为0了) const y = iota // y == 0 (同上) const ( // iota被重设为0 c0 = iota // c0 == 0 c1 // c1 == 1 c2 // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota在每个const开头被重设为0) b // b == 2 c // c == 4 )
2.4 枚举
在const后跟一对圆括号的方式定义一组常量,这种定义法在Go语言中通常用于定义枚举值。
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday numberOfDays // 这个常量没有导出 包内私有 )
3 类型
内置基础类型
- 布尔类型:bool
- 整型: int8、byte、int16、int、uint、uintptr等
- 浮点类型:float32、float64
- 复数类型:complex64、complex128
- 字符串:string
- 字符类型:rune
- 错误类型:error
复合类型
- 指针(pointer)
- 数组(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 结构体(struct)
- 接口(interface)
3.1 布尔类型
布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。
3.2 整型
类型 | 字节 |
---|---|
int8 | 1 |
int16 | 2 |
int64 | 8 |
int | 平台相关 |
uint8 | 1 |
uint16 | 2 |
uint32 | 4 |
uint64 | 8 |
uint | 平台相关 |
uintptr | 同指针(32位平台为4字节,63位平台下位8字节) |
int32 | 4 |
整形之间不可以直接值转换,需要做强制类型转换。
var value2 int32 value1:=64 value2=int32(value1)
3.3 浮点型
类型 | 字节 |
---|---|
float32 | 4 |
float64 | 8 |
3.4 复数类型
复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示 虚部(imag)。
var value1 complex64 // 由2个float32构成的复数类型 value1 = 3.2 + 12i value2 := 3.2 + 12i // value2是complex128类型 value3 := complex(3.2, 12) // value3结果同 value2
3.5 字符串
字符串不可以被修改,是常量。字符串操作包括:+、len(s)、s[i]等,更多操作,见strings包。
str := "Hello,世界" for i, ch := range str { fmt.Println(i, ch)//ch的类型为rune } str := "Hello,世界" n := len(str) for i := 0; i < n; i++ { ch := str[i] // 依据下标取字符串中的字符,类型为byte fmt.Println(i, ch) }
3.6 字符类型
类型 | 备注 |
---|---|
byte | UTF-8字符串的单个字节的值 |
rune | 代表单个Unicode字符 |
3.7 数组
数组就是指一系列同一类型数据 的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数 组的长度。
//声明方式 [32]byte // 长度为32的数组,每个元素为一个字节 [2*N] struct { x, y int32 } // 复杂类型数组 [1000]*float64 // 指针数组 [3][5]int // 二维数组 [2][2][2]float64 // 等同于[2]([2]([2]float64))
需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。数组的长度在定义之后无法再次修改。
3.8 数组切片
与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的 值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。
数组切片的数据结构可以抽象为以下3个变量:
- 一个指向原生数组的指针
- 数组切片中的元素个数
- 数组切片已分配的存储空间
数组切片添加了一系 列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复 复制。
创建方法有两种:
- 基于数组
- 直接创建
// 先定义一个数组 var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 基于数组创建一个数组切片 var mySlice []int = myArray[:5] fmt.Println("Elements of myArray: ") for _, v := range myArray { fmt.Print(v, " ") } fmt.Println("\nElements of mySlice: ") for _, v := range mySlice { fmt.Print(v, " ") } //直接创建 //创建一个初始元素个数为5的数组切片,元素初始值为0: mySlice1 := make([]int, 5) //创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间: mySlice2 := make([]int, 5, 10) //直接创建并初始化包含5个元素的数组切片: mySlice3 := []int{1, 2, 3, 4, 5}
cap()函数,返回的是数组切片分配的空间大小
len()函数,返回的是数组切片中当前所存储的元素个数。
append()函数,继续新增元素。
mySlice2 := []int{8, 9, 10} myslice := make([]int, 5, 10) myslice = append(myslice, 8, 9, 10) // 给mySlice后面添加另一个数组切片 mySlice = append(mySlice, mySlice2...) //上句等价于 mySlice = append(mySlice, 8, 9, 10)
注意:在第二个参数mySlice2后面加了三个点,即一个省略号,如果没有这个省 略号的话,会有编译错误,因为按append()的语义,从第二个参数起的所有参数都是待附加的 元素。因为mySlice中的元素类型为int,所以直接传递mySlice2是行不通的。加上省略号相 当于把mySlice2包含的所有元素打散后传入。
copy()函数,将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行 复制。
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
3.9 map
map是一堆键值对的未排序集合。
package main import "fmt" // PersonInfo是一个包含个人详细信息的类型 type PersonInfo struct { ID string Name string Address string } func main() { var personDB map[string]PersonInfo personDB = make(map[string]PersonInfo) // 往这个map里插入几条数据 personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,..."} personDB["1"] = PersonInfo{"1", "Jack", "Room 101,..."} // 从这个map查找键为"1234"的信息 person, ok := personDB["1234"] // ok是一个返回的bool型,返回true表示找到了对应的数据 if ok { fmt.Println("Found person", person.Name, "with ID 1234.") } else { fmt.Println("Did not find person with ID 1234.") } }
变量声明
var myMap map[string] PersonInfo
创建
myMap = make(map[string] PersonInfo) myMap = make(map[string] PersonInfo, 100)//存储能力为100 //创建并初始化 myMap = map[string] PersonInfo{ "1234": PersonInfo{"1", "Jack", "Room 101,..."}, }
元素赋值
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}
元素删除
delete(myMap, "1234")
元素查找
value, ok := myMap["1234"] if ok { // 找到了 // 处理找到的value }
4 流程控制
流程控制语句的作用:
- 选择
- 循环
- 跳转
go语言支持的流程控制语句
- 条件语句
- 选择语句
- 循环语句
- 跳转语句
为了满足更丰富的控制需求,Go语言还添加了如下关键字:break、 continue和fallthrough。
4.1 条件语句
if a < 5 { return 0 } else { return 1 }
- 条件语句不需要使用括号将条件包含起来()
- 无论语句体内有几条语句,花括号{}都是必须存在的
- 左花括号{必须与if或者else处于同一行
- 在if之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
- 在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中, 否则会编译失败
4.2 选择语句
switch i { case 0: fmt.Printf("0") case 1: fmt.Printf("1") case 2: fallthrough case 3: fmt.Printf("3") case 4, 5, 6: fmt.Printf("4, 5, 6") default: fmt.Printf("Default") } switch { case 0 <= Num && Num <= 3: fmt.Printf("0-3") case 4 <= Num && Num <= 6: fmt.Printf("4-6") case 7 <= Num && Num <= 9: fmt.Printf("7-9") }
- 左花括号{必须与switch处于同一行;
- 条件表达式不限制为常量或者整数;
- 单个case中,可以出现多个结果选项;
- 与C语言等规则相反,Go语言不需要用break来明确退出一个case;
- 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;
- 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个 if...else...的逻辑作用等同。
4.3 循环语句
Go语言中的循环语句只支持for关键字。
a := []int{1, 2, 3, 4, 5, 6} for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 { a[i], a[j] = a[j], a[i] }
左花括号{必须与for处于同一行。
Go语言中的for循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环,如下例( 本例中,break语句终止的是JLoop标签处的外层循环。):
for j := 0; j < 5; j++ { for i := 0; i < 10; i++ { if i > 5 { break JLoop } fmt.Println(i) } } JLoop: // ...
4.4 跳转语句
oto语句的语义非常简单,就是跳转到本函数内的某个标签,如:
func myfunc() { i := 0 HERE: fmt.Println(i) i++ if i < 10 { goto HERE } }
5 函数
5.1 不定参数
- 指定类型的不定参数
func myfunc(args ...int) { for _, arg := range args { fmt.Println(arg) } }
形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一 个语法糖(syntactic sugar)。其本质是数组切片。
不定类型的不定参数
如果你希望传任意类型,可以指定类型为 interface{}。用interface{}传递任意类型数据是Go语言的惯例用法。
func Printf(format string, args ...interface{}) { // ... }
func MyPrintf(args ...interface{}) { for _, arg := range args { switch arg.(type) { case int: fmt.Println(arg, "is an int value.") case string: fmt.Println(arg, "is a string value.") case int64: fmt.Println(arg, "is an int64 value.") default: fmt.Println(arg, "is an unknown type.") } } }
5.2 匿名函数与闭包
匿名函数是指不需要定义函数名的一种函数实现方式。Go语言支持随时在代码里定义匿名函数。匿名函数可以直接赋值给一个变量或者直接执行。
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK } (reply_chan) // 花括号后直接跟参数列表表示函数调用
Go的匿名函数是一个闭包。
基本概念
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。
闭包的价值
闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示 数据还要表示代码。
Go中的闭包
闭包的实现确保只要闭包还被使用,那么 被闭包引用的变量会一直存在。
package main import ( "fmt" ) func main() { var j int = 5 a := func() func() { var i int = 10 return func() { fmt.Printf("i, j: %d, %d\n", i, j) } }() a() j *= 2 a() } // 结果: // i, j: 10, 5 // i, j: 10, 10
6 错误处理
6.1 error接口
Go语言引入了一个关于错误处理的标准模式,即error接口。
type error interface { Error() string }
处理错误的方式:
if err != nil { // 错误处理 } else { // 使用返回值n }
如何使用自定义的error类型?
定义一个用于承载错误信息的类型。Go语言中接口的灵活性,你根本不需要从 error接口继承。
type PathError struct { Op string Path string Err error }
实现Error()方法
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
6.2 defer
函数在退出前执行defer指定的操作。一个函数中可以存在多个defer语句,因此需要注意的是,defer语句的调用是遵照 先进后出的原则,即最后一个defer语句将最先被执行。
*6.3 panic 、recover
Go语言引入了两个内置函数panic()和recover()以报告和处理运行时错误和程序中的错误场景。
panic和recover相当于python中的try except
。
当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中 之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致 逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。
recover()函数用于终止错误处理流程。一般情况下,recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。
package main import ( "fmt" ) func divide() { defer func() { if err := recover(); err != nil { fmt.Printf("Runtime panic caught: %v\n", err) } }() var i = 1 var j = 0 k := i / j fmt.Printf("%d / %d = %d\n", i, j, k) } func main() { divide() fmt.Println("divide方法调用完毕,回到main函数") }