如题目>▽<
第 1 章 开篇综述
1.1 语言介绍
Go 语言由谷歌(Google)公司于 2009 年正式对外发布,设计 Go 语言的初衷都是为了满足 Google 公司的需求。主要目标是“兼具 Python 等动态语言的开发速度和 C/C++等编译型语言的性能与安全性”,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、执行性能好”等优势。最主要还是为了并发而生,并发是基于
goroutine
的,goroutine
类似于线程,但并非线程,可以将goroutine
理解为一种轻量级线程。Go 语言运行时会参与调度goroutine
,并将goroutine
合理地分配到每个 CPU 中,最大限度地使用 CPU 性能。
1.2 Go 语言特点
拥有编译器很方便
自带编译器可以检测出你犯的所有低级错误,如:变量名拼错,不要小看这种问题,没有编译器情况下,很可能浪费掉很长时间去排查,并且非常不容易发现,而且Go语言也是跨平台编译的,你可以在 Mac 电脑上,编译出linux
或者windows
的目标程序。开发速度
Go 是一个非常简单的语言,上手容易,无论你是小白还是老鸟,都会比其他语言 C/C++ 和 Java 等语言要容易很多,这点在做项目中体现得尤其明显。很多人可能会说那 python 呢,php 呢?从语言上说 python 和 php 他们没有编译检查,同样也会像上面说的出一些小的低级错误,或者运行时错误,这都给 php 和 python 带来了隐患,所以 Go 语言虽然牺牲了一点点代码书写的时间,但是从项目运行安全角度来说,已经非常值得了。天生高并发
Go 语言就是为高并发而生的。当你需要使用并发场景,如果你有其他语言基础,第一反应是用到锁,但是 Go 语言提供了更加方便的方式协程+通道
,在 Go 语言中代码不用修改就能直接多协程运行,只要在调用的时候加入 go 关键字,就可以了,非常方便。这与其他语言截然不同,你要考虑哪里开辟新的线程,哪里是代码执行逻辑。
- 部署简单
Go 语言最终执行就是一个二进制文件,包括了它所依赖的程序包,这让开发者不用考虑部署环境的问题,例如,如果你是 java 程序,要考虑执行你的程序,对方机器是否安装了 java 的运行环境,其他语言同理,如果对方机器没有安装,无法运行你的程序,Go 语言可以在 Mac 和 Linux 上交叉编译你的代码,将其拷贝到远程服务器上,然后就可以任其运行了。
1.3 Go 语言能做什么
Go 的优点:实现快 + 资源占用低 + 任意环境随便跑,综合考虑在很多场景十分好用 。
Go 适合造轮子,哪个库不好用就自己造。
Go 适合写工具,比如 hugo 、hub,还有国人写的 linux 下的百度 pan client 都是 go 实现的。
Go 适合实现 C/C++ 一部分业务,Java 的大部分业务。
Go 适合做最外层的胶水,通过 RPC/REST/CGO 粘合不同语言的模块,而在这个胶水层还可以实现各种业务逻辑,又不用像 shell/python/node 有诸多顾虑和局限。
Go 提供了协程、指针、 unsafe, cgo 加上 C/C++ 兼容的内存布局和跨平台的汇编,有了这些你能做很多事情了。
第 2 章 开发环境搭建
第 3 章 第一个 Go 程序
3.1 Hello Go
1 | package main // 声明 main 包 |
3.1.1 package(创建包)
Go 语言以“包”作为管理单位,每个 Go 源文件必须先声明它所属的包,所以我们会看到每个 Go 源文件的开头都是一个 package 声明,格式如下:
1 | package name // 其中 package 是声明包名的关键字, name 为包的名字。 |
Go 语言的包与文件夹是一一对应的,它具有以下几点特性:
- 一个目录下的同级文件属于同一个包。
- 包名可以与其目录名不同。
- main 包是 Go 语言程序的入口包,一个 Go 语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。
3.1.2 import(导入包)
在包声明之后,是 import 语句,用于导入程序中所依赖的包,导入的包名使用双引号""
包围,格式如下:
1 | import "name" // 其中 import 是导入包的关键字,name 为所导入包的名字。 |
代码第 4 行导入了 fmt 包,这行代码会告诉 Go 编译器,我们需要用到 fmt 包中的函数或者变量等,fmt 包是 Go 语言标准库为我们提供的,用于格式化输入输出的内容,类似的还有 os 包、io 包等。
另外有一点需要注意,导入的包中不能含有代码中没有使用到的包,否则 Go 编译器会报编译错误,例如 imported and not used: "xxx"
,”xxx” 表示包名。
也可以使用一个 import 关键字导入多个包,此时需要用括号( )将包的名字包围起来,并且每个包名占用一行,也就是写成下面的样子:
1 | import( |
3.1.3 main 函数
第 5 行代码创建了一个 main 函数,它是 Go 语言程序的入口函数,也即程序启动后运行的第一个函数。main 函数只能声明在 main 包中,不能声明在其他包中,并且,一个 main 包中也必须有且仅有一个 main 函数。
main 函数是自定义函数的一种,在 Go 语言中,所有函数都以关键字 func 开头的,定义格式如下所示:
1 | func 函数名 (参数列表) (返回值列表){ |
注意:Go 语言函数的左大括号{必须和函数名称在同一行,否则会报错。
第 4 章 变量与常量
4.1 变量
作用:给一段指定的内存空间起名,方便操作这段内存。
声明变量的一般形式是使用 var 关键字。
1 | package main |
4.2 常量
常量(constant)表示固定的值。在计算机程序运行时,不会被程序修改的。
- 定义一个常量,使用 const 关键字。常量定义的时候就要赋值。
1 | package main |
- 使用 const 来定义枚举类型
1 | package main |
- iota
iota 是 Go 语言的常量计数器,只能在常量的表达式中使用。
iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。使用 iota 能简化定义,在定义枚举时很有用
1 | package main |
第 5 章 基础数据类型
第 6 章 容器类型
6.1 数组
数组 是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 slice(切片),它是可以动态的增长和收缩的序列, slice
功能也更灵活,下面我们再讨论 slice
。
6.1.1 数组声明
可以使用 [n]Type
来声明一个数组。其中 n
表示数组中元素的数量, Type
表示每个元素的类型。
1 | package main |
6.1.2 数组长度
使用内置的 len
函数将返回数组中元素的个数,即数组的长度。
1 | func arrLength() { |
6.1.3 数组遍历
使用 for range
循环可以获取数组每个索引以及索引上对应的元素。
1 | func showArr() { |
6.2 切片(Slice)
切片是对数组的一个连续片段的引用,所以切片是一个引用类型。切片 本身不拥有任何数据,它们只是对现有数组的引用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。
6.2.1 创建切片
使用 []Type
可以创建一个带有 Type
类型元素的切片。
1 | // 声明整型切片 |
你也可以使用 make 函数构造一个切片,格式为 make([]Type, size, cap) 。
1 | numList := make([]int, 3, 5) |
当然,我们可以通过对数组进行片段截取创建一个切片。
1 | arr := [5]string{"Go1", "Go2", "Go3", "Go4", "Go5"} |
6.2.2 切片元素的修改
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
1 | func modifySlice() { |
这里的 arr[:]
没有填入起始值和结束值,默认就是 0
和 len(arr)
。
6.2.3 追加切片元素
使用 append
可以将新元素追加到切片上。append
函数的定义是 func append(slice []Type, elems ...Type) []Type
。其中 elems ...Type
在函数定义中表示该函数接受参数 elems
的个数是可变的。这些类型的函数被称为可变函数。
1 | func appendSliceData() { |
当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。
6.2.4 多维切片
类似于数组,切片也可以有多个维度。
1 | func mSlice() { |
6.3 Map
在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型
,字符串
等,都可以作为 key
。
6.3.1 创建 Map
使用 make
函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType)
。
1 | // 创建一个键类型为 string 值类型为 int 名为 scores 的 map |
我们也可以用 map 字面值的语法创建 map ,同时还可以指定一些最初的 key/value :
1 | steps3 := map[string]string{ |
6.3.2 Map 操作
- 添加元素
1 | // 可以使用 `map[key] = value` 向 map 添加元素。 |
- 更新元素
1 | // 若 key 已存在,使用 map[key] = value 可以直接更新对应 key 的 value 值。 |
- 获取元素
1 | // 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。 |
- 删除元素
1 | //使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。 |
- 判断 key 是否存在
1 | // 如果我们想知道 map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key] |
这个语句说明map
的下标读取可以返回两个值,第一个值为当前 key
的 value
值,第二个值表示对应的 key
是否存在,若存在 ok
为 true
,若不存在,则 ok
为 false
。
- 遍历 map
1 | // 遍历 map 中所有的元素需要用 for range 循环。 |
- 获取 map 长度
1 | // 使用 len 函数可以获取 map 长度 |
6.3.3 map 是引用类型
当 map
被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
当 map
作为函数参数传递时也会发生同样的情况。