Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言,好称“大道至简”,是新时代的“C语言”。
Go语言基本语法
变量
go语言语法规定,定义的局部变量若没有被调用会发生编译错误。
变量声明格式如下:
1 | //var 变量名 变量类型 |
2 | var a int |
3 | //批量声明 |
4 | var( |
5 | a int |
6 | b string |
7 | c []float32 |
8 | d func() bool |
9 | e struct { |
10 | x int |
11 | y string |
12 | } |
13 | ) |
未初始化的变量将被默认初始化:
- 整型和浮点型变量默认值为0。
- 字符串默认值为空字符串。
- 布尔型默认值为
false
。 - 函数,指针变量,切片默认值为
nil
。
go语言拥有多种初始化方式:
1 | //标准格式 |
2 | var 变量名 变量类型 = 表达式 |
3 | //编译器自动推断类型格式 |
4 | var 变量名 = 表达式 |
5 | //短变量声明格式 |
6 | 变量名 := 表达式 |
7 | |
8 | var a int = 10 |
9 | var b = 10 |
10 | b := 10 |
短变量声明模式只能用于函数体内,该变量名必须是没有定义过的变量,若定义过,将发生编译错误。多个短变量声明和赋值时,至少有一个新声明的变量出现在左侧,那么即便其他变量可能是重复声明的,编译器也不会报错价格。
1 | var a = 10 |
2 | a := 20 //重复定义变量a |
3 | |
4 | var a = 10 |
5 | a, b := 100, 200 //ok |
go语言提供了多重赋值功能,可以实现变量交换,需要注意的是,多重赋值时,左值和右值按照从左到右的顺序赋值。
1 | //传统写法 |
2 | var a int = 10 |
3 | var b int = 20 |
4 | vat tmp int |
5 | tmp = a |
6 | a = b |
7 | b = tmp |
8 | |
9 | //一种取代中间变量的算法 |
10 | a = a ^ b |
11 | b = b ^ a |
12 | a = a ^ b |
13 | |
14 | //go的多重赋值 |
15 | b, a = a, b |
匿名变量_
既不占用命名空间,也不会分配内存。
1 | func GetData() (int, int) { |
2 | return 10, 20 |
3 | } |
4 | |
5 | a, _ = GetData() |
6 | _, b = GetData() |
数据类型
- 基本数据类型:整型,浮点型,复数型,布尔型,字符串,字符(byte,rune)。
- 复合数据类型: 数组,切片(slice),映射(map),函数,结构体,通道(channel),接口(interface),指针。
整型类型 | 字节数 | 取值范围 | 说明 |
---|---|---|---|
int8 | 1 | -128~127 | 有符号8位整型 |
uint8 | 1 | 0~255 | 无符号8位整型 |
int16 | 2 | -32768~32767 | |
uint16 | 2 | 0~65535 | |
int32 | 4 | ||
uint32 | 4 | ||
int64 | 8 | ||
uint64 | 8 | ||
int | 4/8 | 取决于平台 | |
uint | 4/8 | 取决于平台 | |
uintptr | 4/8 | 取决于平台 | 用于存放一个指针 |
符号类型 | 字节数 | 说明 |
---|---|---|
float32 | 4 | 32位浮点型 |
float64 | 8 | 64位浮点型 |
复数类型 | 字节数 | 说明 |
---|---|---|
complex64 | 8 | 64位的复数型,由float32类型的实部和虚部联合表示 |
complex128 | 16 | 128位的复数类型,由float64类型的实部和虚部联合表示 |
字符类型 | 字节数 | 说明 |
---|---|---|
byte | 1 | 表示utf-8字符串的单个字节的值,uint8的别名类型 |
rune | 4 | 表示单个unicode字符,int32的别名类型 |
打印格式通常使用 fmt
包,通用的打印格式有:
%v
值的默认格式表示。%T
值的类型的Go语法表示。%t
bool类型的true/false。%b
整型的二进制。
类型转换
Go语言采用数据类型前置加括号的方式进行类型转换,T(表达式)
。Go语言中不允许字符串转 int
。
1 | var a int = 100 |
2 | b := float64(a) |
3 | c := string(a) |
指针
指针存储另一个变量的内存地址的变量。一个指针变量可以指向任何一个值的内存地址。go语言中使用取地址符 &
来获取变量的地址,一个变量前使用 &
,会返回该变量的内存地址。go语言的指针不能运算。
1 | var 指针变量名 *指针类型 |
2 | var ip *int |
3 | var fp *float32 |
当一个指针被定义后没有分配到任何变量时,它的值为 nil
。nil指针也称为空指针。
1 | if(ptr != nil) {} |
2 | |
3 | var ptr [3]*string //指针数组就是元素为指针类型的数组。 |
4 | |
5 | var ptr **int //指向指针的指针 |
常量
常量是一个简单的标示符,在程序运行时,不会被修改。常量中的数据只可以是布尔类型,数字型(整型,浮点型和复数型)和字符串。常量定义和未被使用,不会在编译时报错。
1 | const 标示符 [类型] = 值 //可以省略说明符[type] |
2 | |
3 | const B string = "hello" |
4 | const C = "world" |
5 | const C, D, E = value1, value2 |
6 | |
7 | const ( |
8 | a = 10 |
9 | b = 11 |
10 | c //常量组中如果不指定类型和初始值,则与上一个非空常量的值相同 |
11 | d |
12 | ) |
iota常量
iota
特殊常量值,是一个系统定义的可以被编译器修改的常量值。可以理解为常量组中的常量计数器,只要有一个常量,则 iota
就加1,即可以被用作枚举值。
1 | const( |
2 | a = iota //第一个iota等于0 |
3 | b = iota |
4 | c = iota |
5 | d //和上一个一样是iota,自增1 |
6 | ) |
类型别名与类型定义
1 | //type 新的类型名 类型 |
2 | //type 类型别名 = 类型 |
3 | |
4 | type byte uint8 |
5 | type rune int32 |
6 | |
7 | type byte = uint8 |
8 | type rune = int32 |
运算符
算术运算符
+
-
*
/
%
++
--
关系运算符
==
!=
>
<
>=
<=
逻辑运算符
&&
||
!
位运算符
位运算符 | 说明 |
---|---|
& | |
| | |
^ | |
<< | 左移运算符,高位移出,低位补0。 |
>> | 右移运算符,低位移出,高位补符号位,即正数补0,负数补1。 |
赋值运算符==
+=
-=
*=
/=
%=
<<=
>>==
&=
^=
|=
其他运算符&
*
运算符优先级
- ^!
- */%<<>>&&^
- +-|^
- ==!=<<=>=>
- <-
- &&
- ||
流程控制
if条件判断语句
在go语言中,左括号必须在if或else的同一行。在if之后,条件语句之前,可以添加变量初始化语句,使用";"
进行分割。
1 | if 布尔表达式 { |
2 | /* 布尔表达为 true 时执行 */ |
3 | } |
4 | |
5 | if 布尔表达式 { |
6 | |
7 | } else { |
8 | |
9 | } |
10 | |
11 | if 布尔表达式 { |
12 | |
13 | } else if { |
14 | |
15 | |
16 | } else { |
17 | |
18 | } |
19 | |
20 | if statement; condition { |
21 | |
22 | } |
switch分支语句
Go语言中的switch默认给每个case自带break,因此匹配成功后不会向下执行其他的case分支,而是跳出整个switch。可以添加fallthrough
强制执行后面的case分支。fallthrough
必须放在case分支的最后一行。
case后的值不能重复,但可以有多个值,这些值之间用,
隔开,同时测试多个符合条件的值。
switch后的表达式可以省略,默认是switch true
。
switch类型转换
switch语句还可以被用于 type switch
(类型转换)来判断某个interface变量中实际存储的变量类型。
1 | package main |
2 | import "fmt" |
3 | func main() { |
4 | var x interface{} |
5 | switch i := x.(type) { |
6 | case nil: |
7 | fmt.Printf(" x 的类型 :%T",i) |
8 | case int: |
9 | fmt.Printf(" x 是 int 型") |
10 | case float64: |
11 | fmt.Printf(" x 是 float64 型") |
12 | case func(int) float64: |
13 | fmt.Printf(" x 是 func(int) 型") |
14 | case bool, string: |
15 | fmt.Printf(" x 是 bool 或 string 型") |
16 | default: |
17 | fmt.Printf("未知型") |
18 | } |
19 | } |
for循环语句
for是go语言中唯一的循环语句,go没有while,do…while循环。
1 | for 初始语句init; 条件表达式condition; 结束语句post { |
2 | |
3 | } |
4 | |
5 | for 循环条件condition { |
6 | |
7 | } |
8 | |
9 | |
10 | for { |
11 | |
12 | } |
13 | |
14 | for key, value := range oldMap { |
15 | newMap[key] = value |
16 | } |
循环控制语句
break
continue
goto
1 | LABEL: statement |
2 | goto LABEL |
函数
go语言的函数可以返回多个值,返回值可以是返回数据的数据类型,也可以是变量名+变量类型的组合。return后的数据,要保持和声明的返回值类型,数量,顺序一致。
1 | func 函数名 (参数列表) (返回参数列表) { |
2 | // |
3 | } |
4 | |
5 | func funcName (param type1, param type2...) (output1 type1, output2 type2..){ |
6 | // |
7 | return value1, value2... |
8 | } |
在参数列表中,如果相邻变量是同类型,则可以将类型省略。go语言支持可变参数,在函数体中变参是一个切片。一个函数最多有一个可变参数。若参数列表中还有其他类型参数,则可变参数写在所有参数最后。
go语言支持递归函数,注意防止栈溢出。
1 | func add(a, b int) {} |
2 | |
3 | func myfunc(arg ...int) {} |
在go语言中,函数也是一种类型,可以和其他类型一样保存在变量中。可以通过关键字type
来自定义类型。
1 | package main |
2 | import "fmt" |
3 | |
4 | type processFunc func(int) bool //声明一个函数类型 |
5 | |
6 | func main() { |
7 | slice := []int{1,2,3,4,5,7} |
8 | fmt.Println("slice = ",slice) |
9 | odd := filter(slice, isOdd) |
10 | fmt.Println("odd num: ",odd) |
11 | even := filter(slice, isEven) |
12 | fmt.Println("even num: ",even) |
13 | } |
14 | |
15 | func isEven(integer int) bool { |
16 | if integer % 2 == 0 { |
17 | return ture |
18 | } |
19 | return false |
20 | } |
21 | |
22 | func isOdd(integer int) bool { |
23 | if integer % 2 == 0 { |
24 | return false |
25 | } |
26 | return true |
27 | } |
28 | |
29 | func filter(slice []int, f processFunc) []int { |
30 | var result []int |
31 | for _, value := range slice { |
32 | if f(value) { |
33 | result = append(result, value) |
34 | } |
35 | } |
36 | return result |
37 | } |
匿名函数
Go语言支持匿名函数,即在需要使用函数时再定义函数。匿名函数没有函数名,只有函数体,函数可以作为一种类型被赋值给变量,匿名函数也往往以变量方式被传递。
1 | func(参数列表) (返回参数列表) { |
2 | |
3 | } |
4 | |
5 | package main |
6 | import "fmt" |
7 | func main() { |
8 | func(data int) { |
9 | fmt.Println("hello", data) |
10 | }(100) |
11 | } |
12 | |
13 | func main() { |
14 | f := func(data string) { |
15 | fmt.Println(data) |
16 | } |
17 | f("hello world") |
18 | } |
19 | |
20 | func main() { |
21 | arr := []float{1,9,16,25,30} |
22 | visit(arr, func(v float64){ |
23 | v = math.Sqrt(v) |
24 | fmt.Printf("%.2f \n", v) |
25 | }) |
26 | } |
27 | |
28 | func visit(list []float64, f func(float64)) { |
29 | for _, value := range list { |
30 | f(value) |
31 | } |
32 | } |
闭包
闭包(Closure)是词法闭包(Lexical Closure)的简称。闭包是由函数和其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显示表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。函数 + 引用环境 = 闭包。
闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定来,不会在执行时发生变化,所以函数是一个实例。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。闭包在某些编程语言中被称为Lambda
表达式。
函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有”记忆性”。函数是编译器静态的概念,而闭包是运行期动态的概念。对象是附有行为的数据,而闭包是附有数据的行为。
1 | package main |
2 | import "fmt" |
3 | func main(){ |
4 | for i := 0; i < 5; i++ { |
5 | fmt.Printf("i=%d \t",i) |
6 | fmt.Println(add2(i)) |
7 | } |
8 | } |
9 | |
10 | func add2(x int) int { |
11 | sum := 0 |
12 | sum += x |
13 | return sum |
14 | } |
1 | package main |
2 | import "fmt" |
3 | func main() { |
4 | pos := adder() |
5 | for i := 0; i < 10; i++ { |
6 | fmt.Printf("i=%d \t", i) |
7 | fmt.Printf(pos(i)) |
8 | } |
9 | |
10 | for i := 0; i < 10; i++ { |
11 | fmt.Printf("i=%d \t", i) |
12 | fmt.Printf(pos(i)) |
13 | } |
14 | } |
15 | |
16 | func adder() func(int) int { |
17 | sum := 0 |
18 | return func(x int) int { |
19 | fmt.Printf("sum1=%d \t", sum) |
20 | sum += x |
21 | fmt.Printf("sum2=%d \t", sum) |
22 | return sum |
23 | } |
24 | } |
闭包捕获来和它在同一作用域的其他常量和变量,所以当闭包在任何地方被调用,闭包都可以使用这些常量或变量。只要闭包还在使用这些变量,这些变量就依然存在,不关心这些变量是否已经超出作用域。
go语言中所有的传参都是值传递,都是一个副本。副本的内容有的是值类型,有的是引用类型。
Go语言容器
数组
数组是相同类型的一组数据构成的长度固定的序列,其中数据类型包含了基本数据类型,复合数据类型和自定义类型。
1 | var 变量名 [数组长度] 数据类型 |
2 | |
3 | var nmus = [5]int{1,2,3,4,5} |
4 | |
5 | var nums = [...]int{1,2,3,4,5} |
数组的长度是数组的一个内置常量,通过将数组作为参数传递给 len()
函数,可以获得数组的长度。
1 | var arrayName [x][y] variable_type |
go语言中数组并非引用类型,而是值类型。将数组作为函数参数传递,是通过值传递,原始数组保持不变。
切片
切片是可变长度的序列,序列中每个元素都是相同类型。切片的数据结构可以理解为一个结构体,这个结构体包含三个元素:
- 指针,指向数组中切片指定的开始位置。
- 长度,即切片的长度。
- 容量,也就是切片开始的位置到数组的最后位置的长度。
声明一个未指定长度的数组来定义切片。切片不需要说明长度,采用该声明方式且未初始化的切片为空切片。默认长度为nil,且长度为0。
1 | var identifier [] type |
使用 make()
函数来创建切片。其中 capacity
为可选参数: make([]T, length, capacity)
。
1 | var slice []type = make([]type, len) |
2 | |
3 | slice := make([]type, len) |
s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1下的元素创建为一个新的切片,长度为endIndex-startIndex。缺省endIndex时,表示一直到arr的最后一个元素。缺省startIndex时,表示从arr的第一个元素开始。
1 | s := [] int {1,2,3} |
2 | |
3 | arr := [5] int {1,2,3,4,5} |
4 | s := arr[:] |
切片的长度是切片中元素的数量。切片的长度可以通过 len()
方法获取,切片的容量可以通过 cap()
函数获取。数组计算cap()与len()的结果相同。
切片没有自己的数据,它是底层数组的一个引用。对切片所做的任何修改都将反应在底层数组中。数组是值类型,切片是引用类型。
函数 append()
用于向切片中追加新元素。可以向切片里追加一个或者多个元素,也可以追加一个切片。append()
会改变切片所引用的数组的内容,会影响到引用同一个数组的其他切片。当容量不够时,会新建一个内存地址来存储元素。
函数 copy()
会复制切片元素,将源切片中的元素复制到目标切片中,返回复制元素的个数。复制前后的切片不存在联系。
map
map
是引用类型,map
是由hash表实现的,所以对 map
的读取顺序不固定。map
是无序的,不能通过index获取,而必须通过key获取。map
长度不是固定的,和切片一样可以扩展。内置的 len()
函数同样适用于map,返回map拥有的键值对的数量,但 map
不能通过 cap()
函数计算容量。
1 | var 变量名 map[key类型]value类型 //未初始化的map的默认值是nil |
2 | |
3 | 变量名 := make(map[key类型]value类型) //该声明方式不初始化map,map也不等于nil |
value, ok := map[key]
获取key/value是否存在。
delete(map, key)
函数用于删除集合中的某个元素,删除函数不返回任何值。
结构体
类型名是标识结构体的名称,在同一个包内不能重复。同类型的成员属性可以写在一行。结构体只定义了一种内存布局,只有当结构体实例化时,才分配内存。
1 | type 类型名 struct { |
2 | 成员属性1 类型1 |
3 | 成员属性2 类型2 |
4 | 成员属性3, 成员属性4 类型3 |
5 | ... |
6 | } |
内置函数 new()
对结构体进行实例化后形成结构体指针。结构体是值类型。值类型是深拷贝,为新对象分配内存,引用类型是浅拷贝,只是复制了对象的指针。
结构体的语法糖
语法糖(Syntactic Sugar)指计算机中添加某种语法,对语言的功能没有影响,但是更方便程序员使用。
1 | type Emp struct { |
2 | name string |
3 | age int8 |
4 | sex byte |
5 | } |
6 | |
7 | func main() { |
8 | emp := make(Emp) |
9 | (*emp).name = "hello" |
10 | (*emp).age = 30 |
11 | (*emp).sex = 1 |
12 | //语法糖写法 |
13 | emp.name = "world" |
14 | emp.age = 30 |
15 | emp.sex = 1 |
16 | } |
匿名结构体与匿名字段
匿名结构体就是没有名字的结构体,无须通过type
关键字定义就可以直接使用。匿名结构体由结构体定义和键值对初始化两部分组成。
1 | 变量名 := struct { |
2 | |
3 | }{ } |
匿名字段就是在结构体中没有名字的字段,只包含一个没有字段名的类型。同一个类型只有一个匿名字段。结构体采用匿名结构体字段可以模拟继承关系。
结构体嵌套可以模拟聚合关系(一个类作为另一个类的属性)和继承关系(一个类作为另一个类的子类)。
方法
方法有接受者,函数无接受者。接受者类似与this
和self
。接受者可以是struct和非struct类型,可以是指针类型或非指针类型。若方法的接受者不是指针,实际获取的是一份拷贝。只要接受者不同,方法名可以相同。
1 | func (接受者变量 接受者类型) 方法名(参数列表) (返回值列表) { |
2 | |
3 | } |
方法的继承
匿名字段实现了一个方法,那么包含该匿名字段的struct也能调用该方法。
1 | type Human struct { |
2 | name, phone string |
3 | age int |
4 | } |
5 | |
6 | type Student struct{ |
7 | Human |
8 | school string |
9 | } |
10 | |
11 | type Employee struct { |
12 | Human |
13 | company string |
14 | } |
15 | |
16 | func main() { |
17 | s1 := Student{Human{"hello", "123*****"}, "school"} |
18 | e1 := Employee{Human{"world", "133****"}, "company"} |
19 | s1.SayHi() |
20 | e1.SayHi() |
21 | } |
22 | |
23 | func (h *Human) SayHi() { |
24 | fmt.Printf("hey,my name is %s, %d old, tell me %s\n", h.name, h.age, h.phone) |
25 | } |
方法的重写
方法重写指一个包含了匿名字段的struct也实现了该匿名字段实现的方法。
1 | type Human struct { |
2 | name, phone string |
3 | age int |
4 | } |
5 | |
6 | type Student struct{ |
7 | Human |
8 | school string |
9 | } |
10 | |
11 | type Employee struct { |
12 | Human |
13 | company string |
14 | } |
15 | |
16 | func main() { |
17 | s1 := Student{Human{"hello", "123*****"}, "school"} |
18 | e1 := Employee{Human{"world", "133****"}, "company"} |
19 | s1.SayHi() |
20 | e1.SayHi() |
21 | } |
22 | |
23 | func (h *Human) SayHi() { |
24 | fmt.Printf("hey,my name is %s, %d old, tell me %s\n", h.name, h.age, h.phone) |
25 | } |
26 | |
27 | func (h *Student) SayHi() { |
28 | fmt.Printf("hey,my name is %s, %d old, tell me %s\n", h.name, h.age, h.phone) |
29 | } |
30 | |
31 | func (h *Employee) SayHi() { |
32 | fmt.Printf("hey,my name is %s, %d old, tell me %s\n", h.name, h.age, h.phone) |
33 | } |
接口
在go语言中,接口是一组方法的签名,接口指定了类型应该具有的方法,类型决定了如何实现这些方法。当某个类型为接口中的所有方法提供了具体的实现,这个类型就被称为实现了该接口。go语言的类型都是隐式实现接口的,任何定义了接口中所有方法的类型都被称为隐式的实现了该接口。
1 | type 接口名字 interface { |
2 | 方法1 ([参数列表]) [返回值] |
3 | 方法2 ([参数列表]) [返回值] |
4 | ... |
5 | 方法3 ([参数列表]) [返回值] |
6 | } |
1 | type Phone interface { |
2 | call() |
3 | } |
4 | |
5 | type AndroidPhone struct { |
6 | |
7 | } |
8 | |
9 | type IPhone struct { |
10 | |
11 | } |
12 | |
13 | func (a AndroidPhone) call() { |
14 | fmt.Printf("I am Android Phone\n") |
15 | } |
16 | |
17 | func (a IPhone) call() { |
18 | fmt.Printf("I am IPhone\n") |
19 | } |
20 | |
21 | func main() { |
22 | var phone Phone //定义接口类型 |
23 | phone = new(AndroidPhone) |
24 | phone.call() |
25 | phone = AdnroidPhone{} |
26 | phone.call() |
27 | phone = new(IPhone) |
28 | phone.call() |
29 | phone = IPhone{} |
30 | phone.call() |
31 | } |
duck typing
go语言没有implements
或extends
关键字,这类编程语言叫做duck typing编程语言。duck typing是描述事物的外部行为而非内部结构。使用duck typing的编程语言往往被归为”动态语言”或”解释型语言”。
go语言采取的方式为:
- 结构体类型T不需要显示地声明它实现了接口I。只要类型T实现了接口I规定的所有方法,它就自动的实现了接口I。
- 将结构体类型的变量显示或隐式地转换为接口I类型的变量i。可以在编译时检查参数的合法性。
1 | type SayHello interface { |
2 | SayHello() string |
3 | } |
4 | |
5 | type Duck struct { |
6 | name string |
7 | } |
8 | |
9 | type Person struct { |
10 | name string |
11 | } |
12 | |
13 | func (d Duck) SayHello() string { |
14 | return d.name + "ga ga ga !" |
15 | } |
16 | |
17 | func (p Person) SayHello() string { |
18 | return p.name + "hello!" |
19 | } |
20 | |
21 | func main() { |
22 | |
23 | } |
多态
go语言的多态是在接口的帮助下实现的-定义接口类型,创建实现该接口的结构体对象。
空接口
空接口没有任何方法。任意类型都可以实现该接口。空接口表示任意数据类型。例如,定义一个map,key是string,value是任意数据。定义一个切片,其中存储任意类型的数据。
1 | map := make(map[string]interface{}) |
2 | |
3 | slice := make([]interface{}, 0, 10) |
接口对象转型
1 | instance, ok := 接口对象.(实际类型) |
2 | |
3 | 接口对象.(type) |
包(package)
异常处理
error
1 | type error interface { |
2 | Error() string |
3 | } |
error
本质是一个接口类型,包含一个 Error()
方法,错误值存储在变量中,通过函数返回。
1 | res, err := Sqrt(-100) |
2 | if err != nil { |
3 | |
4 | } |
结构体只要实现了Error() string
这种格式的方法,就代表实现了该错误接口,返回值为错误的具体描述。go语言errors
包对外提供了可供用户自定义的方法,errors
包下的New()
函数返回error
对象,errors.New()
函数创建新的错误。
1 | package errors |
2 | |
3 | func New(text string) error { |
4 | return &errorString{text} |
5 | } |
6 | |
7 | type errorString struct { |
8 | s string |
9 | } |
10 | |
11 | func (e *errorString) Error() string { |
12 | return e.s |
13 | } |
14 | |
15 | func Errorf(format string, a ...interface{}) error { |
16 | return errors.New(Sprintf(format, a...)) |
17 | } |
自定义错误
- 定义一个结构体,表示自定义错误的类型。
- 让自定义错误类型实现
error
接口:Error() string
。 - 定义一个返回
error
的函数。
1 | package main |
2 | import ( |
3 | "time" |
4 | "fmt" |
5 | ) |
6 | |
7 | type MyError struct { |
8 | When time.time |
9 | What string |
10 | } |
11 | |
12 | func (e MyError) Error() string { |
13 | return fmt.Sprintf("%v : %v", e.When, e.What) |
14 | } |
defer
defer
用于延迟一个函数或者方法的执行,defer
语句只能出现在函数或方法的内部。在函数中可以存在多个defer
语句,defer
语句会按照逆序执行。
defer
语句常被用来处理成对操作,例如:打开-关闭,链接-断开,加锁-释放锁。
延迟函数的参数在执行延迟语句时被调用,而不是执行实际的函数被调用时执行。
1 | func main() { |
2 | str := "hello world!" |
3 | fmt.Printf("原始字符串:\n %s\n", str) |
4 | fmt.Println("反转后的字符串: ") |
5 | ReverseString(str) |
6 | } |
7 | func ReverseString(str string) { |
8 | for _, v := range []rune(str) { |
9 | defer fmt.Printf("%c", v) |
10 | } |
11 | } |
panic与recover
panic()
内建函数,中断程序的执行。recover()
仅在延迟函数中有效。
并发
- 并发(Concurrency)在微观层面上,任务不会同时进行。
- 并行(Parallelism)多个任务一定是同时运行的。
Goroutine
Go语言通过go
关键字来启动一个goroutine
。
- go的执行是非阻塞的,不会等待。
- go后面的函数的返回值会被忽略。
- 调度器不保证多个
goroutine
的执行次序。 - 没有父子
goroutine
的概念,所有的goroutine
是平等地被调用和执行。 - go程序在执行时会单独为
main
函数创建一个goroutine
,遇到其他go关键字时在创建其他的goroutine
。
1 | package main |
2 | |
3 | import ( |
4 | "runtime" |
5 | "time" |
6 | ) |
7 | |
8 | func main() { |
9 | go func() { |
10 | sum := 0 |
11 | for i := 0; i < 10000; i++ { |
12 | sum += 1 |
13 | } |
14 | println(sum) |
15 | time.Sleep(1 * time.Second) |
16 | }() |
17 | |
18 | println("NumGoroutine=", runtime.NumGoroutine()) |
19 | |
20 | time.Sleep(5 * time.Second) |
21 | } |