声明:内容来自《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函数")
} 
京公网安备 11010502036488号