【文档】
https://golang.google.cn/doc
http://docscn.studygolang.com/doc
http://c.biancheng.net/golang
PS:学习一门语言最好的教程是官方文档
【特性】
接口:非侵入式,隐式满足
可执行命令必须使用 package main。
某个名称在包外是否可见,就取决于其首个字符是否为大写字母
一个类型无需显式地声明它实现了某个接口。取而代之,该类型只要实现了某个接口的方法, 其实就实现了该接口。
由于几乎任何类型都能添加方法,因此几乎任何类型都能满足一个接口。
内建函数 make(T, args) 的目的不同于 new(T)。它只用于创建切片、映射和信道,并返回类型为 T(而非 *T)的一个已初始化 (而非置零)的值。
函数 + 引用环境 = 闭包。函数是编译期静态的概念,而闭包是运行期动态的概念。
一个类型断言表达式的语法为 i.(T),其中 i 为一个接口值, T 为一个类型名或者类型字面表示。 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
【golang】类型转换和类型断言 [assert 值断言通常用于测试用例]
接口被实现的条件一:接口的方法与实现接口的类型方法格式一致
在 Go语言中,不仅结构体与结构体之间可以嵌套,接口与接口间也可以通过嵌套创造出新的接口。
不要通过共享内存来通信,而应通过通信来共享内存。
单核CPU系统里没有真正的并行
【语法】
var 变量名 类型 = 表达式
Go 支持以下 2 种形式的字面值:
- 解释字符串:
该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\:反斜杠自身 - 非解释字符串:
该类字符串使用反引号“”括起来,支持换行,例如:This is a raw string \n中的\n` 会被原样输出。
Go语言的字符有以下两种:
一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
另一种是 rune 类型,代表一个 UTF-8 字符。当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型实际是一个 int32。
Go 语言中使用&作符放在变量前面对变量进行“取地址”操作。
ptr := &v // v的类型为T
其中 v 代表被取地址的变量,被取地址的 v 使用 ptr 变量进行接收,ptr 的类型就为*T,称做 T 的指针类型。*代表指针。
字符串拼接符“+”
不支持三元运算符
指针(pointer)概念在 Go 语言中被拆分为两个核心概念:
类型指针,允许对这个指针类型的数据进行修改。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。
切片,由指向起始元素的原始指针、元素数量和容量组成。
var 数组变量名 [元素数量]T
var array […]T = []int{1,2,3,4,5}
var slice []T = []int{}
表达式 new(File) 和 &File{} 是等价的
从数组或切片生成新的切片:slice [开始位置:结束位置] 包含开始位置不包含结束位置
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:make( []T, size, cap )
Go语言中可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。通过 for range 遍历的返回值有一定的规律:1. 数组、切片、字符串返回索引和值。2. map 返回键和值。3. 通道(channel)只返回通道内的值。
var map1 map[keytype]valuetype
var map1 map[string]int
make(map[keytype]valuetype, cap)
不要使用 new,永远用 make 来构造 map
Go语言中的循环语句只支持 for 关键字,而不支持 while 和 do-while 结构。
如果循环被 break、goto、return、panic 等语句强制退出,结束语句不会被执行。
形如…type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。
区分类型别名与类型定义,类型别名的写法为:type TypeAlias = Type。差别一个“等号”
func 函数名(形式参数列表)(返回值列表){
函数体
}
函数变量 f:var f func()
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型
…
}
基本的实例化形式:var ins T ins 的类型为 T
创建指针类型的结构体:ins := new(T) ins 的类型为 *T
取结构体的地址实例化:ins := &T{} ins 的类型为 *T
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
var 通道变量 chan 通道类型
通道实例 := make(chan 数据类型)
goroutine:go 函数名( 参数列表 )
【摘要】
Go语言和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。
当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。
变量声明以关键字 var 开头,后置变量类型,行尾无须分号。
匿名变量的特点是一个下画线“”,“”本身就是一个特殊的标识符,被称为空白标识符。
匿名变量不占用命名空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
一般情况下,局部变量的作用域可以通过代码块(用大括号括起来的部分)判断。
不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
和 for 循环类似,if 和 switch 语句也会在条件部分创建隐式词法域,还有它们对应的执行体词法域。
布尔型无法参与数值运算,也无法与其他类型进行转换。
一般的比较运算符(==、!=、<、<=、>=、>)通过在内存中按字节比较来实现字符串的对比,因此比较的结果是字符串自然编码的顺序。可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)。
注意:获取字符串中某个字节的地址的行为是非法的,例如:&str[i]。
提示:由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行。
感觉我们通过代码达成了修改字符串的过程,但真实的情况是:Go 语言中的字符串和其他高级语言(Java、C#)一样,默认是不可变的(immutable)。
和 C/C++ 不一样,Go 中的字符串是根据长度限定,而非特殊字符 \0。string 类型的零值为长度为零的字符串,即空字符串 “”。
字符串不可变有很多好处,如天生线程安全,大家使用的都是只读对象,无须加锁;再者,方便内存共享,而不必使用写时复制(Copy On Write)等技术;字符串 hash 值也只需要制作一份。
所以说,代码中实际修改的是 []byte,[]byte 在 Go 语言中是可变的,本身就是一个切片。
bytes.Buffer 是可以缓冲并可以往里面写入各种字节数组的。字符串也是一种字节数组,使用 WriteString() 方法进行写入。
由于 Go语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数)
非本地类型不能定义方法
不能在一个非本地的类型 time.Duration 上定义新方法。非本地方法指的就是使用 time.Duration 的代码所在的包,也就是 main 包。因为 time.Duration 是在 time 包中定义的,在 main 包中使用。time.Duration 包与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法。
函数局部变量尽量使用栈;全局变量、结构体成员使用堆分配等
变量逃逸(Escape Analysis)——自动决定变量分配方式,提高运行效率
那么 Go语言的自动垃圾收集器是如何知道一个变量是何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。
其它语言中的容器:
C语言没有提供容器封装,开发者需要自己根据性能需求进行封装,或者使用第三方提供的容器。
C++ 语言的容器通过标准库提供,如 vector 对应数组,list 对应双链表,map 对应映射等。
C# 语言通过 .NET 框架提供,如 List 对应数组,LinkedList 对应双链表,Dictionary 对应映射。
Lua 语言的 table 实现了数组和映射的功能,Lua 语言默认没有双链表支持。
在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。
我们将会发现,数组、slice、map 和结构体字面值的写法都很相似
如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过 == 比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。
在 Go语言中 Slice 代表变长的序列,序列中每个元素都有相同的类型。一个 slice 类型一般写作 []T,其中 T 代表 slice 中元素的类型;slice 的语法和数组很像,只是没有固定长度而已。
除了可以从原有的数组或者切片中生成切片,你也可以声明一个新的切片。每一种类型都可以拥有其切片类型,表示多个类型元素的连续集合
切片是动态结构,只能与nil判定相等,不能互相判等。
声明新的切片后,可以使用 append() 函数来添加元素。
温馨提示:使用 make() 函数生成的切片一定发生了内存分配操作。但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
切片不一定必须经过 make() 函数才能使用。生成切片、声明后使用 append() 函数均可以正常使用切片。
要注意的是,在容量不足的情况下, append 的操作会导致重新分配内存(扩容),可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用 append 函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
切片在扩容时,容量的扩展规律按容量的 2 倍数扩充,例如 1、2、4、8、16……
通过循环复制元素更直接,不过内置的 copy() 函数使用起来更加方便。copy 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,目标和源的位置顺序和 dst = src 赋值语句是一致的
copy 函数将返回成功复制的元素的个数,等于两个 slice 中较小的长度,所以我们不用担心覆盖会超出目标 slice 的范围。
两个特殊的内置函数 len 和 cap,可以用于处理数组、切片和通道。对于切片,函数 len 返回切片的长度,函数 cap 返回切片的容量
从内部实现机理上来说,类型…type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数 args 可以用 for 循环来获得每个传入的参数。
之前的例子中将可变参数类型约束为 int,如果你希望传任意类型,可以指定类型为 interface{}。
可变参数变量是一个包含所有参数的切片,如果要在多个可变参数中传递参数,可以在传递时在可变参数变量中默认添加…,将切片中的元素进行传递,而不是传递可变参数变量本身。
有意思的是,Go 语言中并没有为 map 提供任何清空所有元素的函数、方法。清空 map 的唯一办法就是重新 make 一个新的 map。不用担心垃圾回收的效率,Go 语言中的并行垃圾回收效率比写一个清空函数高效多了。
列表是一种非连续存储的容器,由多个节点组成,节点通过一些变量记录彼此之间的关系。列表有多种实现方法,如单链表、双链表等。
几乎在任何情况下,传递指针(一个 32 位或者 64 位的值)的消耗都比传递副本来得少。在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显示的指出指针)。
Go语言的匿名函数就是一个闭包,闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
Go语言的错误处理思想及设计包含以下特征:
一个可能造成错误的函数,需要返回值中返回一个错误接口(error)。如果调用是成功的,错误接口将返回 nil,否则返回错误。
在函数调用后需要检查错误,如果发生错误,进行必要的错误处理。
Go语言的设计者认为其他语言的异常机制已被过度使用,上层逻辑需要为函数发生的异常付出太多的资源。同时,如果函数使用者觉得错误处理很麻烦而忽略错误,那么程序将在不可预知的时刻崩溃。
Recover 是一个 Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来。recover 仅在延迟函数 defer 中有效。在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果。如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
Go 语言中的类型可以被实例化,使用new或&构造的类型实例的类型是类型的指针。
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。
结构体成员中只能包含结构体的指针类型,包含非指针类型会引起编译错误。
结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。
在 Go语言中,结构体就像是类的一种简化形式,那么面向对象程序员可能会问:类的方法在哪里呢?在 Go语言中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收器(receiver)上的一个函数,接收器是某种类型的变量。因此方法是一种特殊类型的函数。
接收器类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收器不能是一个接口类型,因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:invalid receiver type…。
最后接收器不能是一个指针类型,但是它可以是任何其他允许类型的指针。一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go语言中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收器类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收器类型上存在,比如在同一个包里这么做是允许的。
在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器。大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。(数值类型没有必要使用指针接收器)
在 Go语言中,访问结构体指针的成员变量时可以继续使用.。这是因为 Go语言为了方便开发者访问结构体指针的成员变量,使用了语法糖(Syntactic sugar)技术,将 ins.Name 形式转换为 (*ins).Name
【结构体指针隐式解析,统一没有 -> 操作符】
Go语言的结构体内嵌有如下特性:
- 内嵌的结构体可以直接访问其成员变量
- 内嵌结构体的字段名是它的类型名
Go 语言的包与文件夹一一对应,所有与包相关的操作,必须依赖于工作目录(GOPATH)。
深度优先顺序初始化:包引用关系:main→A→B→C,那么这些包的 init() 函数调用顺序为:C.init→B.init→A.init→main
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
两个基本概念 Type 和 Value,它们也是 Go语言包中 reflect 空间里最重要的两个类型。
【环境】
brew install go
go env
export GOROOT="/usr/local/Cellar/go/1.12.7/libexec" #go安装目录
export GOPATH="/Users/yuanliu/work" #go工作目录
export GOBIN="KaTeX parse error: Expected 'EOF', got '#' at position 13: GOPATH/bin" #̲go可执行文件目录
expor…PATH:GOBIN:GOBIN:GOBIN:GOROOT/bin
GOPATH:为我们开发常用的目录,建议不要和Go的安装目录一致,在该文件夹下又有三个文件夹:src、pkg、bin,这里src是自己新建的,pkg和bin是后面生成的。怎么生成的,后面会说到。
src:主要存放我们的源代码
bin:存放编译后生成的可执行文件,可以自己执行(go build生成)
pkg: 编译后生成的文件(.a文件)(非main函数的文件在go install后生成)
GOBIN:是GOPATH下的bin目录
PATH:环境变量,需要go-bin目录加入到path路径下,生成可执行文件就可以直接运行了。
https://www.jianshu.com/p/8a87eeec15f2。
【命令】
godoc 工具一般有一下几种用法:
go doc package:获取包的文档注释,例如:go doc fmt 会显示使用 godoc 生成的 fmt 包的文档注释。
go doc package/subpackage:获取子包的文档注释,例如:go doc container/list。
go doc package function:获取某个函数在某个包中的文档注释,例如:go doc fmt Printf 会显示有关 fmt.Printf() 的使用说明。
1072

被折叠的 条评论
为什么被折叠?



