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语法表示。%tbool类型的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 | } |