系列
0. 官方教程
- 离线教程(docker)
1
docker run -d -p 12345:9999 hunterhug/gotourzh
- 官网教程
GO 语言之旅
1. 数据类型
基本类型
布尔类型、数值类型(整型、浮点型、复数型)
|
|
int 、 uint 和 uintptr 在 32 位系统上通常为 32 位宽, 在 64 位系统上则为 64 位宽
通常使用int作为整数类型, 如果需要使用固定大小, 则使用int8、int16、int32、int64; 如果需要无符号整数(比如位运算), 则使用unit、unint8、uint16、uint32、uint64;
uintptr类型: 专门用于存储指针的整数表示形式。在进行底层编程, 例如与 C 语言代码交互或者进行内存操作时, uintptr可以用来存储指针值。
复合类型
数组、切片、函数、指针、接口、映射、通道为复合类型,可由类型字面构造
array 数组
Go中
array
是长度固定的单一类型元素组成的编号序列。数组类型总是一维的,可通过组合构成多维的类型。
类型 [n]T
表示拥有n个T类型元素的数组
表达式为: var a [10]int
数组不能改变大小
数组通过a[n]
读取相应索引的值, 0 < n取值范围 < len(a)
, 因此 Go 数组和切片都不支持反向索引(例如 a[-1])。
- 数组声明
数组声明的语法参考变量声明,支持 var name [len]type
也支持 name := [len]type
短声明
- 数组声明的同时,初始化数组
|
|
还可以省略长度让编译器自动计算 nums := [...]{1, 2, 3}
如果指定索引赋值,中间的元素会置为0值 nums := [...]{11, 4:44, 55}
数组内容为 [11 0 0 44 55]
多维数组。array本身是一维的,通过组合类型实现多维数组。var twoD [2][3]int
slice 切片
切片 = 对数组连续段的
引用
,slice
本身不存储数据。更改切片的元素会使底层数组的对应元素发生改变,并被其他共享此数组的slice观察到这些修改。
类型 []T
表示一个元素类型为T的切片
切片通过两个下标来界定,两者以冒号:
分隔
sliceA = a[low:high]
, 此为半开区间,含首不含尾( 左闭右开 ). 例如 a[1:4]
包含 a[1] a[2] a[3]
|
|
输出为
|
|
切片文法
slice[low:high] 左闭右开[low: high)
切片省略写法(省略边界,取边界的默认值。上界默认值: 0; 下界默认值: len(slice))
slice[:] == slice[:10] == slice[0:] == slice[0:10]切片长度、容量capacity
|
|
nil切片
零值为nil,长度容量皆为0,没有底层数组的切片var s []int
make函数创建切片
make函数可以创建一个元素为类型零值的数组,并返回引用切片。容量为可选参数创建len=5的int类型切片,
a := make([]int, 5)
创建len=0,cap=5的int类型切片,
a := make([]int, 0, 5)
切片的切片
b := [][]string
切片追加元素
append
new := append(a, 66)
切片复制
copy
1 2
new := make([]string, len(s)) copy(a, new)
内置包
slices
功能slices.Equal(t, t2)
用来比较两个切片是否相同,相同返回 true
map 映射
键值对结构,使用前必须初始化,否则map是nil,无法往map中增加键值对,会报错。
- 定义
|
|
- 映射修改
插入、修改元素 m[key] = elem
读取元素 elem = m[key]
删除元素 delete(m, key)
检查键存在(通过双赋值) elem, ok = m[key]
key存在: ok为true
key不存在: ok为false, elem为元素类型的零值
注意:当 elem k 未声明, 可以使用短变量声明elem, ok := m[key]
struct 结构体
struct 是一个字段的集合, 结构体字段是通过结构体指针进行访问,比如结构体的指针p, 可以通过
(*p).X
访问字段X,可隐式解引用,简写为p.X
访问
- 声明( 定义 )
|
|
- 赋值( 初始化 )
|
|
- 取值(读): 结构体字段通过
点号 .
访问
|
|
- 取值(通过struct指针访问)
结构体指针引用完整写法应为
(*p).x
p是执行结构体的指针, go通过语法糖,可简写为p.x
|
|
零值
未明确初始值的变量声明, 均会被赋予该变量的零值
大类 | 包含类型 | 零值 |
---|---|---|
布尔类型 | bool | false |
字符串类型 | string | ’’ (空字符串) |
数值类型 | int* uint* float* 等 | 0 |
复数类型 | complex64 complex128 | (0+0i) 实部为0, 虚部为0 |
类型转换
表达式T(v)可以将变量v转换为T类型
|
|
变量
变量的声明
关键字
var
用来声明一个变量。结构为var [名称] <类型> <=初始值>
,类型在名称之后,类型、初始值可省略一个。
完整语法:var a int = 12
多变量声明:var a, b int = 1, 2
省略-值,声明零值变量:var a int
省略-类型,类型推断:var a = 1
短变量声明: a, b := true, 1
注意: 短变量声明只能在函数内使用
分组声明: 与import
相同, var
也可以使用括号组成一个语法块, 来声明一组变量。
函数内
:=
可在类型明确的地方代替var
声明, 函数外的语句必须以关键字(var func import package 等等)开始, 因此:=
无法在函数外使用。
- 举例: 普通变量声明
|
|
- 举例: 短变量声明
|
|
- 举例:分组声明
|
|
变量作用域
|
|
常量
常量类型有: 字符、字符串、布尔值、数值
语法: const [名称] <类型> = [值]
类型可省略,通过类型推导确定
常量不能使用语法糖:=
流程控制语句
条件、循环、分支和推迟语句来控制代码的流程。
循环: for
for是golang中唯一的循环结构
基本语法
基本的 for 循环由三部分组成,用分号
隔开:
|
|
初始化语句
:在第一次迭代前执行(可省略)条件表达式
:在每次迭代前求值后置语句
: 在每次迭代的结尾执行
初始化语句通常为一句短变量声明(如 i:=0
),该变量声明仅在 for
语句的作用域中可见。
一旦条件表达式的布尔值为 false,循环迭代就会终止。 注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面没有小括号( )
,而大括号{ }
是必须的。
举例
普通循环
循环10次 的不同写法
- 经典语法
initial;condition;after
|
|
- 可省略
initial
|
|
for 是 Go 中的「while」
go语言没有
while
和do ... while
语法,省略initial
+after
后,for 就是 Go 中的「while」。
|
|
无限循环
for 后无condition即为死循环。再通过搭配
if
和break
来实现跳出死循环。
|
|
for-range 遍历
用来遍历切片、映射。遍历切片时,返回下标、对应元素的副本;遍历映射时,返回key、对应key的value;
|
|
- 省略。通过
_
来省略下标;通过去掉value来省略值
省略下标:省略值:1 2 3
for _, value := range pow { fmt.Printf("%d\n", value) }
1 2 3
for i := range pow { pow[i] = 1 << uint(i) // == 2**i }
判断: if/else
Go 的 if 语句与 for 循环类似,条件表达式外无需小括号 ( ) ,但后面的大括号 { } 是必须的
基本用法
if condition {…} else {…}
|
|
带短变量声明的分支语句
if initial; condition {…}
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
该语句声明的变量作用域仅在 if 之内。
|
|
多分支判断
if condition {…} else if condition {…} else {…}
|
|
三元运算
Go 中没有三目运算。
分支: switch/case
switch的case语句从上到下顺序执行,直到匹配成功停止。
与其他语言的不同:1. 只运行匹配成功的第一个case(相当于在后面每个case中加了break),除非使用fallthrough语句结束,否则case会自动终止;2. case取值不仅限于整数、常量,可以为表达式等。
基本使用
case 后可以跟多个表达式,使用逗号分隔;建议设置 default 。
|
|
表达式带短声明
|
|
switch后省略表达式 == if/else
|
|
switch 判断类型
|
|
|
|
也可以省略os
变量的声明
|
|
当省略条件时,等同于switch true{}
(此格式,比一串if-else更清晰)
|
|
等同于
|
|
逻辑运算符
相等 ==
不等 !=
大于>
大于等于>=
小于<
小于等于<=
且 &&
或 ||
指数运算 e
3. 函数
函数定义
- 基本语法
|
|
- 其他语法
当连续的已命名形参类型相同时, 除最后一个形参的类型以外, 都可以省略。
即x int, y int
可缩写为x, y int
|
|
函数返回值
- 多返回值
|
|
- 命名返回值
函数返回值可在函数顶部被定义, 此时return可以省略参数, 也就是直接返回( 长函数中不建议省略, 影响代码可读性)。
|
|
注意上面示例, 如果
return y, x
时, 返回值为10 7
方法
方法只是个带接收者参数的函数。
|
|
为非struct声明方法
|
|
注意:接收者类型定义 和 方法声明必须在同一包内。(因此无法为其他包中定义的类型声明方法,包括
int
之类的内置类型)
指针类型接收者
指针接收者,每个方法对值的修改都能传递,因此指针接收者比值接收者更常用。
值的类型为大型结构体时,指针接收者可以避免每次调用方法时复制值,更加高效。
接口
接口类型 的定义为一组方法签名
4. 包管理
导入
导入通过关键字
import
基本语法
|
|
导入多个包
|
|
导入多个包(推荐)
用括号组合了导入, 是分组形式的导入语句
|
|
导出
在 Go 中, 如果一个名字以大写字母开头, 那么它就是已导出的。 例如, Pizza 就是个已导出名, Pi 也同样, 它导出自 math 包。
pizza 和 pi 并未以大写字母开头, 所以它们是未导出的。
在导入一个包时, 你只能引用其中已导出的名字。任何未导出
的名字在该包外均无法访问。
其他语法特征
语法糖 - 简短变量声明:=
规则
:=
左侧必须要有新变量:=
只能用于函数内部- 多变量声明,可能会重新赋值
- 作用域
举例说明
:=
左侧必须要有新变量1 2 3 4 5 6 7 8 9
i := 1 // := 左侧必须要有,至少一个新变量 // 会报错no new variable on left side of := i := 22 // 以下场景也是相同道理 func test(i int) { i := 111 }
- 多变量声明,可能会重新赋值如果存在新变量(field2),变量
1 2 3
field1, err := Func1() field2, err := Func2()
err
会被重新赋值;
如果没有新变量,编译报错no new variable on left side of :=
:=
只能用于函数内部:=
不能用来声明、初始化全局变量。函数外使用会编译报错syntax error: non-declaration statement outside function body
(函数外的语句必须以关键字(var func import package 等等)开始, 因此:=
无法在函数外使用。)- 作用域
1 2 3 4 5 6 7
func Test() { field, err:= Func1() // 1号err if field == 1{ field, err:= Func2() // 2号err newField, err := Func3() // 3号err } }
1号err
一个作用域,2号err
和3号err
是相同作用域,因此3号err
是对2号err
的重新赋值,但1号err
并不会因为后续的逻辑发生改变。
语法糖 - 可变参数...
可变参函数...
表示 0 或 多个参数。使用方法:在类型前加...
规则
可变参数
必须位于参数列表的最后(否则编译时,会引起歧义);可变参数
在函数内作为切片
来处理的;- 函数调用时,
可变参数
可以不填(此时被当作切片的空值,即nil
处理); - 函数调用时,
可变参数
可以直接传入切片(函数内的操作,会影响传入切片的值) 可变参数
类型必须相同,(不同类型,通过interface{}处理);
* & 指针
指针: 保存值的内存地址
操作符 * &
变量
前面的 * 与 &是操作符,
&会生成一个指向其操作值的指针; (间接引用)
*表示指针指向的底层值;(重定向)
与C语言不同,Go没有指针运算
类型 *T
*T
表示指向T类型
值的指针, 零值为nil
例如 var p *int
defer
defer语句 会将 函数返回 推迟到外层函数返回之后执行
defer调用的函数,其参数会立即求值压栈。但直到外层函数返回前,该函数都不会被调用。当外层函数返回时,defer函数会按后进先出
的顺序调用返回。
|
|
输出为
|
|
make 与 new 的区别
相同点:都是用于分配内存的内置函数。
new
用于分配内存并返回指向该内存的指针。主要用来创建一个引用类型的结构体,只有结构体可以用。make
用于初始化内置的可变大小数据结构,并返回一个引用。(只用于 切片、映射和通道)
函数闭包
函数整体作为一个代码块,连带其引用额变量被压入栈,就形成了闭包
参考链接
中文资料
首先,安装Go环境。
然后,Go文档作为统一入口,包含
通过Go语言之旅,熟悉Go的语言特征;再通过Go 编程语言规范了解语言本身。
再通过实效Go编程 - 中文,了解Go代码的最佳实践。
想了解包相关,可以阅读包,
进一步探索 Go 的并发模型,参阅 Go高级并发模型 - 视频(幻灯片)以及深入 Go 并发模型(幻灯片)并阅读通过通信共享内存。
函数 - Go语言的第一公民, 中又很多有趣的函数类型。
Go 博客有着众多关于 Go 的文章和信息。
Go 技术论坛有大量关于 Go 的中文文档和 Go 官方博客的翻译。
mikespook 的博客中有大量中文的关于 Go 的文章和翻译。