《Go语言实战》笔记 持续更新中 - AaronPei/blog GitHub Wiki
关于Go语言介绍
Go语言的优点
开发速度
- 语法简单,可以概括为几个关键字。不支持三元表达式
- GO的编译器速度非常快,即使编译整个GO语言源码树也只需要20s
- GO语言虽然是静态类型语言,但具有动态语言的
- 语言层面支持并发
- 跨平台编译,可以直接编译成二进制文件
并发
Goroutine
goroutine 是可以与其他 goroutine 并行执行的函数,同时也会和主程序并行运行的函数 在Go语言中,net/http 库直接使用了 内置的 goroutine 默认Go语言分配的Goroutine内存只有2kb,Go语言运行时会自动在配置的一组逻辑处理器上调度执行goroutine。每个逻辑处理器绑定到一个操作系统线程上
Channel
通道是一种数据结构,可以让 goroutine 之间进行安全的数据通信。通道可以帮用户避免其他语言里常见的共享内存访问的问题。保证了同一时刻只有一个Goroutine在修改数据
Go语言的类型系统
类型简单
Go接口对一组行为建模
Go语言使用接口作为代码复用的基础模块
内存管理
GO的编译器负责Go的内存管理
快速一个Go程序
- 程序中每个代码文件里的 init 函数都会在 main 函数执行前调用
- main 函数保存在名为 main 的包里。如果 main 函数不在 main 包里,构建工 具就不会生成可执行的文件
包
一个包定义一组编译过的代码,包的名字类似命名空间,可以用来间接访问包内声明的标识符。这个特性可以把不同包中定义的同名标识符区别开
调用下划线"_"
_ "github.com/goinaction/code/chapter2/sample/matchers"
这个技术是为了让 Go 语言对包做初始化操作,但是并不使用包里的标识符。为了让程序的 可读性更强,Go 编译器不允许声明导入某个包却不使用。下划线让编译器接受这类导入,并且 调用对应包内的所有代码文件里定义的 init 函数
公开标示符与非公开标示符
在 Go 语言里,标识符要么从包里公开,要么不从包里公开。当代码导入了一个包时,程序可以直接访问这个包中任意一个公开的标识符。这些标识符以大写字母开头。以小写字母开头的标识符是不公开的,不能被其他包中的代码直接访问
- 大写字母开头的:公开
- 小写字母开头:非公开
Map数据类型
map 是 Go 语言里的一个引用类型,需要使用 make 来构造。如果不先构造 map 并将构造后的值赋值给变量,会在试图使用这个 map 变量时收到出错信息。这是因为 map 变量默认的零值是nil。
查找 map 里的键时有两个选择:要么赋值给一个变量,要么为了精确查找,赋值给两个变量。赋值给两个变量时第 一个值和赋值给一个变量时的值一样,是 map 查找的结果值。如果指定了第二个值,就会返回 一个布尔标志,来表示查找的键是否存在于 map 里。如果这个键不存在,map 会返回其值类型 的零值作为返回值,如果这个键存在,map 会返回键所对应值的副本
切片数据类型
切片是一种实现了一个动态数组的引用类型。在 Go 语言里可以用切片来操作一组数据
Go语言数据类型所对应的零值
在 Go 语言中,所有变量都被初始化为其零值。对于数值类型,零值是 0;对于字符串类型, 零值是空字符串;对于布尔类型,零值是 false;对于指针,零值是 nil。对于引用类型来说, 所引用的底层数据结构会被初始化为对应的零值。但是被声明为其零值的引用类型的变量,会返 回 nil 作为其值
简化声明运算符
简化变量声明运算符(:=)。这个运算符用于声明一个变量,同时给这个变量赋予初始值
根据经验,如果需要声明初始值为零值 的变量,应该使用 var 关键字声明变量;如果提供确切的非零值初始化变量或者使用函数返回 值创建变量,应该使用简化变量声明运算符
通道、映射、切片
通道(channel)和映射(map)与切片(slice)一样,也是引用类型,不过 通道本身实现的是一组带类型的值,这组值用于在 goroutine 之间传递数据。通道内置同步机制, 从而保证通信安全
main函数终止
在 Go 语言中,如果 main 函数返回,整个程序也就终止了。Go 程序终止时,还会关闭所有 之前启动且还在运行的 goroutine。写并发程序的时候,最佳做法是,在 main 函数返回前,清理 并终止所有之前启动的 goroutine。
WaitGroup 是一个计数信号量,我们可以利用它来统计所有的 goroutine 是不是都完成了工作。
匿名函数
匿名函数是指没有明确声明名字的函数
指针变量
指针变量可以方便地在函数之间共享数据。使用指针变量可以让函数访问并修改一个变 量的状态,而这个变量可以在其他函数甚至是其他 goroutine 的作用域里声明
闭包
Go 语言支持闭包,因为有了闭包,函数可以直接访问到那些没有作为参数传入的变量。匿名函数并没有拿到这些变量的副本,而是直接访问外层函数作用域中声明的这些变量本身。
var searchTerm string
results := make(chan *Result)
go func(matcher Matcher, feed *Feed) {
Match(matcher, feed, searchTerm, results)
waitGroup.Done()
}(matcher, feed)
defer
关键字 defer 会安排随后的函数调用在函数返回时才执行。
Go 语言的命名惯例
命名接口的时候,也需要遵守 Go 语言的命名惯例。如果接口类型只包含一个方法,那么这 个类型的名字以 er 结尾。 Golang命名规范
数组、切片和映射
数组的内部实现和基础功能
在 Go 语言里,数组是一个长度固定的数据类型
其占用的内存是连续分配的,由于内存连续:
- CPU 能把正在使用的数据缓存更久的时间
- 而且内存连续很容易计算索引,可以快速迭代数组里的所有元素。数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离。
- 既然数组的每个元素类型相同,又是连续分配,就可以以固定速度索引数组中的任意数据,速度非常快。
声明和初始化
var array [5]int
array := [5]int{10,20,30,40,50}
array := [...]int{10,20,30,40,50}
array :=[5]int{1:20,2:30}
array[2]=35 > {0,20,35,0,0}
// 声明包含 5 个元素的指向整数的数组
// 用整型指针初始化索引为 0 和 1 的数组元素
array := [5]*int{0: new(int), 1: new(int)}
fmt.Printf("array: %x\n", array)
*array[0] = 10
*array[1] = 20
fmt.Printf("array: %x,array[0]: %d\n", array, *array[0])
多维数组
var arrayA [4][2]int
arrayB := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
arrayC := [4][2]int{0: {10, 11}, 1: {20, 21}}
arrayD := [4][2]int{0: {1:11}, 1: {0:20}}
数组赋值
只有相同类型的数组才可以相互赋值
把一个指针数组赋值给另一个
// 声明第一个包含 3 个元素的指向字符串的指针数组
var array1 [3]*string
// 声明第二个包含 3 个元素的指向字符串的指针数组
// 使用字符串指针初始化这个数组
array2 := [3]*string{new(string), new(string), new(string)}
// 使用颜色为每个元素赋值
*array2[0] = "Red"
*array2[1] = "Blue"
*array2[2] = "Green"
// 将 array2 复制给 array1
array1 = array2
函数间传递数组指针
//声明一个需要8 MB的数组
var array [1e6]int
// 将数组传递给函数 foo
foo(array)
// 函数 foo 接受一个 100 万个整型值的数组
func foo(array [1e6]int){
...
}
// 用指针方式
var array [1e6]int
foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针
func foo(* array[1e6]int){
...
}