系列
0. 官方教程
- 离线教程(docker)
1docker 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 []intmake函数创建切片
make函数可以创建一个元素为类型零值的数组,并返回引用切片。容量为可选参数创建len=5的int类型切片,
a := make([]int, 5)创建len=0,cap=5的int类型切片,
a := make([]int, 0, 5)切片的切片
b := [][]string切片追加元素
appendnew := append(a, 66)切片复制
copy1 2new := 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).xp是执行结构体的指针, 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 3for _, value := range pow { fmt.Printf("%d\n", value) }1 2 3for 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 9i := 1 // := 左侧必须要有,至少一个新变量 // 会报错no new variable on left side of := i := 22 // 以下场景也是相同道理 func test(i int) { i := 111 }- 多变量声明,可能会重新赋值如果存在新变量(field2),变量
1 2 3field1, 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 7func 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 的文章和翻译。