3个环境变量
- GOROOT、GOPATH、GOBIN
- 环境变量GOPATH的值可以是一个目录的路径,也可以包含多个目录路径,每个目录都代表GO语言的一个工作区(workspace)。这些工作区用于放置Go语言的源码文件(source file),以及安装后的归档文件(archinvefile 也就是以.a为扩展名的文件)和可执行文件
Go语言源码的组织方式
- Go语言的源码是以代码包为基本组织单位的
- 一个代码包中可以包含多个以
.go
为扩展名的源码文件,这些源码文件都需要被声明为属于同一个代码包。代码包的名称一般会与这些源码文件所在的目录同名,如果不同名那么在构建、安装的过程中会以代码包名称为准 - 导入代码包
import "github.com/labstack/echo"
,在工作区中一个代码包导入的路径实际上就是从src子目录,到该包的实际存储位置的相对路径
源码安装后的情况
- go源码文件通常会被放在某个工作区的src子目录下
- 安装后产生了归档文件,就会被放进该工作区的pkg子目录;
- 如果产生了可执行文件,就可能会放进该工作区的bin子目录。
- 小结:源码文件会以代码包的形式组织起来,一个代码包其实就是对应一个目录。安装某个代码包而产生的归档文件是与这个代码包同名的
- 执行命令
go install github.com/labstack/echo
- 生成的归档文件相对目录就是github.com/labstack,文件名为echo.a
- 注意:归档文件的箱规目录与pkg的目录之间还有一级目录,叫做平台相关目录(由操作系统_目标计算架构代号如:linux_amd64)
构建和安装的GO的过程
- 构建命令:
go build
;安装命令go install
; - 构建和安装代码包都会执行编译、打包等操作,
- 构建和安装操作生成的任何文件都会被先保存到某个临时的目录中。
- 如果构建的是库源码文件,操作的结果文件只会存在于临时目录中,这里的构建的主要意义在于检查和验证
- 如果构建的是命令源码文件,那么操作的结果文件会被搬运到那个源码文件所在的目录中
- 安装操作会先执行构建,然后还会进行连接操作,`
且把结果文件搬运到指定目录
。进一步说,如果安装的是库源码文件,那么结果文件会被搬运到它所在的工作区的pkg目录下的某个子目录中 - 安装的是命令源码文件,那么结果文件会被搬运到它所在的工作区的bin目录中,或者环境变量GOBIN指向的目录中。
- 小结:构建和安装的不同之处,以及执行相应命令后得到的结果文件都会出现在哪里。
go build 命令
- go build 默认不会编译目标代码包所依赖的那些代码包,当然如果被依赖的代码包的归档文件不存在,或者源码文件有了变化,那它还是会被编译。如果需要强制编译 使用 go build -a
- go build -x 这样可以看到go build 命令都执行了哪些操作;使用 -n 可以只查看具体操作而不执行他们。
- go build -v 这样可以看到go build 命令编译的代码包的名称。它在与-a 标记搭配使用时很有用
- go get
- 命令go get会自动从一些主流公用的代码仓库下载目标代码包,并把他们安装到环境变量GOPATH包含的第1工作区的相应目录中。
- 常用标记
- go get -u:下载并安装代码包,不论工作区中是否已经存在它们
- go get -d:只下载代码包不安装
- go get -fix:下载代码包之后先运行一个用户根据当前go版本修正代码的工具,然后再安装代码包
- go get -t :同时下载测试所需的代码包
- go get -insecure: 允许通过非安全的网络协议下载和安装代码包
- github上的三方工具:glide,gb;官方的dep、vgo
GO命令源码文件
- 命令源码文件
- 库源码文件
- 测试源码文件
- 命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或者安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。
- 如果以个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件:
- package main
- import "fmt"
- func main() {
fmt.Println("Hello,world!")
}
命令源码文件如何接收参数
- GO语言标准库有一个代码包专门用于接收和解析命令参数:flag包。
- flag.StringVar(&name, "name", "everyone", "The greeting object.")
- &name: 存储该命令参数的值的地址
- name:参数的名称
- everyone:默认值
- 第4个参数是命令参数的简短说明
- 使用:go run demo.go -name="Robert"
库源码文件
库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用。
tips:程序实体在Go语言中是指变量、常亮、函数、结构体和接口的统称。程序实体的名字被统称为标识符。
- 这里讲的就是在一个go源码文件中怎么样使用别的文件里面的方法
- 1 把文件放到同一个文件夹下,声明同样名字的包
- 2 函数首字母大写
go语言类型推断
-
重构:不改变某个程序的与外界的任何交互方式和规则,而只改变其内部实现的代码修改方式,叫做对该程序的重构。
-
GO语言类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易,同时又不会给代码的维护带来额外负担,更不会损失程序的运行效率
-
变量重声明:
对已经声明过的变量再次声明。 -
再次声明时赋予的类型必须与其原本的类型相同
-
只看发生在某一个代码块中
-
使用短变量声明时才会发生【name := "jack"】
-
被声明并赋值的变量必须是多个,并且至少一个新的变量。这时我们才可以说对其中的旧变量进行了重声明。
-
var err error
-
n,err := io.WriteString(os.Stdout, "Hello,everyone!\n")
我使用短变量声明对新变量n和旧变量err进行“声明并赋值”,这时也是对后者的重声明。判断变量的类型
答:类型断言表达式
类型断言表达式的语法形式是x.(T)。其中x代表的是要被判断类型的那个值。 -
value,ok := interface{}(container).([]string)
interface{} 达标了不包含任何方法定义的、空的接口类型
[]string 是一个类型字面量:用来表示数据类型本身的若干个字符 -
等号的右边就是类型断言表达式。
-
value和ok 分别代表表达式的返回值,ok是布尔类型的,代表类型判断的结果,true或者false
如果是true,那么被判断的值将会被自动转换为[]string类型的值,并赋值给变量value,否则value将会赋予为nil。 -
x被称为源值,它的类型就是源类型,而那个T代表的类型就是目标类型。
-
类型转换注意
- 对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可以表示范围内就是合法的
- 特别注意,源类型范围大于目标类型范围的时候,如
- var srcInt = int16(-255)
- dstInt := int8(srcInt)
- dstInt的值为
1
- 解析:整数在go语言是以补码的形式存储的,主要是为了简化计算机对整数的运算过程。补码其实就是原码各位求反再加1。
- int16类型的-255的补码是
1111111100000001
转为int8类型,那么Go语言会把在较高位置上的8位二进制数直接截掉,从而得到00000001
,由于最左边符号位是0,表示它是个正整数,正整数的补码等于它的源码所以结果dstInt的值是1; - 知识点:整数值类型的有效范围由宽变窄,只需要在补码形式下截掉一定数量的高位二进制即可; 浮点数类型的值转为整数类型值时,前者的小数部分会被全部截掉
- 知识点2:把整数转为以个string类型的值是可行的,被转换整数值应该可以代表一个有效的Unicode代码点,否则转换的结果将会乱码(菱形当中有个问号);这个Unicode代码点的
U+FFFD
是Unicode标准中定义的Replacement Character 专门用于替换哪些未知的,不被认可的以及无法展示的字符。
数组和切片
- array:值长度是固定的,值类型
- slice:值是可变长的,引用类型【通道,字典,函数】
- 传引用:传递的值是引用类型
- 传值:传递的值是值类型
- 传递成本上引用类型的值往往要比值类型的值低很多
- len()和cap() 分别可以获取到长度和容量
- 数组的容量永远都等于长度,而切片的容量却不是这样。
- 举例:
- s3 := []int{1,2,3,4,5,6,7,8}
- s4 := s3[3:6]
- s3的容量是和长度都是8;s3[3:6]代表从索引为3的位置开始截取直到索引为6-1=5的位置,那么s4的长度为3(6-3),但是它的cap是5,简单来说是8-3(起始索引)=5
- 怎么正确估算切片的长度和容量?
- 一旦切片容纳不了更多的元素,go就会扩容,它不是改变原来的切片,而是会生成一个容量更大的切片,然后将原来的元素和新元素一并拷贝到新切片中。
- 一般新切片是原来的切片容量的2倍;当原切片的长度大于等于1024时,GO会将原容量的1.25倍作为新容量的基准(不断的*1.25直到能容纳下所有的元素),如果这个值超过了原切片长度的2倍,那么以新长度为基准
链表container/list
这个包包含了两个公开的程序实体:List和Element
前者实现了一个双向链表,后者代表了链表中元素的结构。
- 链表的元素不是连续存储的,所以相邻的元素之间需要互相保存对方的指针,每个元素还要保存它所属那个链表的指针
- List有MoveBefore和MovaAfter方法,把给定元素移动到另一个元素的前面和后面。
- MoveToFront和MoveAfter,给定元素移动到链表的最前面或者最后面
- Front()和Back()可以获取最前和最后的元素
- InsertBefore()和InsertAfter()在指定的元素之前和之后插入新元素
- PushFront()和PushBack()方法则用于最前端和最后端插入新元素
map 字典
- 字典类型是哈希表的特定实现,键和元素的最大不同在于,前者类型是受限的,而后者却可以是任意类型的。
- 字典键类型不能是函数类型、字典类型和切片类型
因为go语言规范规定,在键类型的值之间必须可以试驾操作符===和!= 以上3种类型不支持判等等操作
这个具体之前的blog有讲。 - 这里说一点就是键的哈希值相等的时候,会跟键再次比较一下。
通道和goroutine
Don't communicate by sharing memory; share memory by communicating
- 通道可以用来在多个goroutine之间传递数据
- 通道类型的值本身就是并发安全的
- ch1 := make(chan int,3)
- 3代表通道的容量,所谓容量就是指通道最多可以缓存多少个元素值。这个3是int型,但是不能小于0
- 容量为0的通道称之为非缓冲通道
- 一个通道相当于一个先进先出的队列,各个元素严格的按照发送的顺序排列
<-
这个符号叫接送操作符- 通道接收和发送的基本特性
- 对于同一个通道,发送操作之间是互斥的,接收操作之间是也互斥
- 发送操作和接收操作中对元素值的处理都是不可分割的
- 发送操作在完全完成之前会被阻塞,接收操作也是如此
- 针对缓冲通道的情况,如果通道已满,对它的所有发送操作都会被阻塞,直到通道中有元素值被接走。这时通道会优先通知最早因此而等待的,那个发送操作所在的goroutine,它会再次执行发送操作;相对的接收操作也这样,如果通道空了,那么它的所有接收操作都会被阻塞,直到有新元素出现,通道会通知最早的goroutine,并使它再次执行接收操作。
- 数据直接从发送方复制到接收方,中间并不会用费缓冲通道做中转。相比之下,缓冲通道则是在用异步的方式传递数据。注意,如果发送操作执行的时候发现空的通道中,正好有等待的接收操作,那么它就会直接把元素值复制给接收方
- 通道是一个
引用类型
- 通道关闭之后再进行发送操作会引发panic
- 试图关闭一个已经关闭了的通道会panic
通道的高级玩法***
- 单向通道:只能发不能收或者只能收不能发的通道
- 只发不收(发送通道):var uselessChan = make(chan<- int,1)
- 只收不发(接收通道):var uselessChan = make(<-chan int,1)
- 单向通道的应用价值在于约束其他的代码行为
- type Notifier interface {
SendInt(ch chan<- int)
} - 一个类型如果想成为一个接口类型的实现类型,那么就必须实现这个借口中定义的所有方法。(类似php的interface所有继承接口的类必须实现这个接口的所有方法)因此,如果我们在某个方法的定义中使用了单向通道类型,那么就相当于在对它的所有实现做出约束。在合理Notifier接口中的SendInt方法只会接受一个发送通道作为参数,如果传递了元素类型匹配的双向通道,GOlang会自动把双向通道转换为函数所需的单向通道。
- type Notifier interface {
- 通道取值
- for elem := range intChan2 {}
- 不断的从intChan2中取出元素值,即使intChan2被关闭,也会再取出所有剩余元素值之后再结束执行
- 当intChan2中没有元素值时,它会被阻塞在for关键字那一行,知道有新元素可取
- 假设intChan2的值为nil 那么它会被永远阻塞在有for关键字的那一行
- select 语句
- 分支有2种:候选分支,静默分支
- 候选分支:case开头+表达式+冒号
- 静默分支:default+冒号
- select {
case <-intChan2:
case elem :=<-intChan2:
default:
} - 静默分支,不论涉及通道操作的表达式是否阻塞,select 语句都不会被阻塞,如果那几个表达式都阻塞了,或者没有满足求值条件,那么静默分支就会被选中执行
- 如没有静默分支,所有的case都不满足的情况下,select语句会被阻塞直到至少一个case表达式满足条件
- select 语句只能对其中的每一个case表达式各求职一次。
- 分支选择规则
- case表达式中有表达式的时候,从左到右的顺序被求值
- case表达式中在该语句执行开始时先被求值
- case表达式操作处于阻塞状态,那么对case表达式求值就是不成功,这样是不满足选择条件的
- 仅当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。
- select 语句发现多个候选分支满足选择条件时,那么它会用一种伪随机的算法在这些分支中选择一个并执行。
- select 只能有一个default,无论位置写在哪都是在无候选分支时候才被执行
- select语句的每次执行,包括case 表达式求值和分支选择,都是独立。不过至于它的执行是否并发安全,就要看其中的case表达式以及分支中,是否包含并发不安全的代码了。