GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。
我们需要利用这些工作区,去放置 Go 语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。“.a”文件相当于静态库文件。
源码安装后的结果
在安装后如果产生了归档文件(以“.a”为扩展名的文件),就会放进该工作区的 pkg 子目录;如果产生了可执行文件,就可能会放进该工作区的 bin 子目录。
构建和安装 Go 程序的过程
构建使用命令go build,安装使用命令go install。构建和安装代码包的时候都会执行编译、打包等操作,并且,这些操作生成的任何文件都会先被保存到某个临时的目录中。
如果构建的是库源码文件,那么操作后产生的结果文件只会存在于临时目录中。这里的构建的主要意义在于检查和验证。
如果构建的是命令源码文件,那么操作的结果文件会被搬运到源码文件所在的目录中。
安装操作会先执行构建,然后还会进行链接操作,并且把结果文件搬运到指定目录。
进一步说,如果安装的是库源码文件,那么结果文件会被搬运到它所在工作区的 pkg 目录下的某个子目录中。
如果安装的是命令源码文件,那么结果文件会被搬运到它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。
课后习题
1.Go 语言在多个工作区中查找依赖包的时候是以怎样的顺序进行的?
例如 a 依赖 b ,b依赖c
那么 会先查找c包,那在工作区是如何查找这个依赖包c的呢?
首先在查找依赖包的时候,总是会先查找 GOROOT目录,也就是go语言的安装目录,如果没有找到依赖的包,才到工作区去找相应的包。
在工作区中是按照设置的先后顺序来查找的,也就是会从第一个开始,依次查找,如果找到就不再继续查找,如果没有找到,就报错了。
go get 会下载代码包到src目录,但是只会下载到第一个工作区目录。
在Go语言程序中,每个包都有一个全局唯一的导入路径。导入语句中类似"github.com/xxxx/tem"的字符串对应包的导入路径。
Go语言的规范并没有定义这些字符串的具体含义或包来自哪里,它们是由构建工具来解释的。
一个导入路径代表一个目录中的一个或多个Go源文件。
除了包的导入路径,每个包还有一个包名,包名一般是短小的名字(并不要求包名是唯一的),包名在包的声明处指定。
2.如果在多个工作区中都存在导入路径相同的代码包会产生冲突吗?
不冲突,因为按顺序找到所需要的包就不往后找了。
环境变量 GOPATH 指向的是一个或多个工作区,每个工作区中都会有以代码包为基本组织形式的源码文件。这里的源码文件又分为三种,即:命令源码文件、库源码文件和测试源码文件,它们都有着不同的用途和编写规则。
命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。有且仅有一个。
1.命令源码文件接收参数
极客时间版权所有: https://time.geekbang.org/column/article/13159
flag代码包:用于接收和解析命令参数。
函数flag.StringVar接受 4 个参数。
第 1 个参数是用于存储该命令参数值的地址,具体到这里就是在前面声明的变量name的地址了,由表达式&name表示。
第 2 个参数是为了指定该命令参数的名称,这里是name。
第 3 个参数是为了指定在未追加该命令参数时的默认值,这里是everyone。
至于第 4 个函数参数,即是该命令参数的简短说明了,这在打印命令说明时会用到。
flag.StringVar(&name, "name", "everyone", "The greeting object.") 另一种方式:var name = flag.String("name", "everyone", "The greeting object.")函数flag.Parse用于真正解析命令参数,并把它们的值赋给相应的变量。
对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量name的声明)和设置(这里是对flag.StringVar函数的调用)之后,并且在读取任何命令参数值之前进行。正因为如此,我们最好把flag.Parse()放在main函数的函数体的第一行。
2. 在运行命令源码文件时传入参数,查看参数的使用说明
如果我们把上述代码存成名为 demo2.go 的文件,那么运行如下命令就可以为参数name传值:
go run demo2.go -name="Robert"
运行后,打印到标准输出(stdout)的内容会是:
Hello, Robert!
另外,如果想查看该命令源码文件的参数说明,可以这样做:
$ go run .\golang-core-36\2-cmdsrcfile\cmdsrcfile.go --help
其中的$表示我们是在命令提示符后运行go run命令的。运行后输出的内容会类似:
Usage of C:\Users\xxx\AppData\Local\Temp\go-build3644541283\b001\exe\cmdsrcfile.exe://go run命令构建上述命令源码文件时临时生成的可执行文件的完整路径 极客时间版权所有: https://time.geekbang.org/column/article/13159 The greeting object. (default "everyone") -name1 string The greeting object. (default "everyone3") -name3 string The greeting object. (default "everyone2")示例代码:
package main import ( "flag" "fmt" ) var name string var name3 = flag.String("name1", "everyone3", "The greeting object.") func init() { flag.StringVar(&name, "name", "everyone", "The greeting object.") } func main() { var name2 = flag.String("name3", "everyone2", "The greeting object.") flag.Parse() fmt.Printf("Hello, %s!\n", name) fmt.Printf("Hello, %s!\n", *name2) fmt.Printf("Hello, %s!\n", *name3) } ------控制台输出------ PS D:\private\Golang_workspace> go run .\golang-core-36\2-cmdsrcfile\cmdsrcfile.go -name3="Alan" Hello, everyone! Hello, Alan! Hello, everyone3! PS D:\private\Golang_workspace> go run .\golang-core-36\2-cmdsrcfile\cmdsrcfile.go -name1="Alan" Hello, everyone! Hello, everyone2! Hello, Alan!课后习题:
1. 默认情况下,我们可以让命令源码文件接受哪些类型的参数值?
答:前面讲过`flag`是专门用来处理命令行参数的包,所以我们只需要看`flag`这个包支持哪些数据结构就行了。结果如下:
* int(int|int64|uint|uint64),
* float(float|float64),
* string,
* bool,
* time.duration(时间),
* var(自定义,如FlagSet、Flag、实现Value接口等)
2. 我们可以把自定义的数据类型作为参数值的类型吗?如果可以,怎样做?
答:显然是可以的,从上面的回答来看就知道可以。实现方式可以参考源码中`flag.CommandLine.Var(value Value,name string,usage string)`的实现,核心在于自定义的数据类型需要实现`Value`接口,只要实现了`Value`接口,这个问题也就解决了。
重点:
1.同一个文件夹下,包的声明语句需要相同,代表同一个包。
2.包名不需要和其所在的文件夹名相同。
3.首字母大小写来代表可见性,大写public/小写private。
4.模块级私有internal的使用姿势。
课后习题:
1.import后路径最后一级相同,不一定会冲突。
分为两种情况:
a.如果文件夹下文件声明的包名相同,则肯定冲突,会报错redeclared。
b.如果文件夹下文件声明的包名不同,也不会冲突。
2.如果冲突,我能想到的解决方式:
a.给包设置别名,调用的时候来区分开不同的package,比如:import(b "bbbb")
b.导入的点操作,import(. "bbbb")。这样就可以直接调用bbbb下面的函数而不用再bbbb.funcname的方式调用。
c.如果只是想引入某包并没有在代码中实际调用则可以这么处理来避免冲突:import(_ "bbbb")
d.像第一问一样采取不同的包名声明,毕竟包名不一定要和文件夹名一样
不过总的推荐还是方法a设置包别名。
Go 语言中的程序实体包括变量、常量、函数、结构体和接口。
声明变量有几种方式:
- 类型推断。一种编程语言在编译期自动解释表达式类型的能力。
- 短变量声明。name := "Tom"
1.Go 语言的类型推断可以带来哪些好处?
Go 语言的类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易,同时又不会给代码的维护带来额外负担(实际上,它恰恰可以避免散弹式的代码修改),更不会损失程序的运行效率。
2. 变量的重声明是什么意思?
其含义是对已经声明过的变量再次声明。其实算是一个语法糖(或者叫便利措施)。它允许我们在使用短变量声明时不用理会被赋值的多个变量中是否包含旧变量。
var err error n, err := io.WriteString(os.Stdout, "Hello, everyone!\n") //我使用短变量声明对新变量n和旧变量err进行了“声明并赋值”,这时也是对后者的重声明。变量重声明的前提条件如下:
- 变量重声明时类型必须与其原本的类型相同。
- 变量的重声明只可能发生在某一个代码块中。
- 变量的重声明只有在使用短变量声明时才会发生。
- 被“声明并赋值”的变量必须是多个,并且其中至少有一个是新的变量。
代码块和作用域。
这个命令源码文件中有3个代码块:全域代码块、main包代表的代码块、main函数代表的代码块。
全域代码块:一个Go程序可以访问到的所有代码共同组成的代码块,也可以理解为Go标准库和所有工作区中的代码共同组成的代码块。
package main import "fmt" var block = "package" func main() { block := "function" fmt.Printf("The block is %s.\n", block) }可重名变量所在的代码块之间,存在直接或间接的嵌套关系,那么它们之间一定会存在“屏蔽”的现象。
变量重声明是对同一个变量的多次声明,这里的变量只有一个。而可重名变量中涉及的变量肯定是有多个的。
panic,翻译为运行时恐慌。它是一种在 Go 程序运行期间才会被抛出的异常。
interface{}代表空接口,任何类型都是它的实现类型。
别名类型——主要是为了代码重构而存在的。
type MyString = string //MyString是string类型的别名类型
Go 语言内建的基本类型中就存在两个别名类型。byte是uint8的别名类型,而rune是int32的别名类型。
潜在类型:某个类型在本质上是哪个类型。
即使两个不同类型的潜在类型相同,它们的值之间也不能进行判等或比较,它们的变量之间也不能赋值。
数组和切片
数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。
通过调用内建函数len,得到数组和切片的长度。通过调用内建函数cap,我们可以得到它们的容量。
新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。
但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。
一个切片的底层数组永远不会被替换。虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。
切片长度和容量的计算,切片扩容与缩容。
首先总结今天课程内容
1. 数组和切片的区别与联系
1.1数组是有长度的并且长度是类型的组成部分之一 所以[1]string!=[2]string 长度固定不可变
1.2切片实际上是对底层数组的一层封装,通过切片的容量和长度 我们可以访问到底层数组中对应的元素,
1.2.1如果切片是从底层数组下标为0处开始引用 那个切片的第一个元素(下标为0时)引用的是数组下标为0的元素
1.2.2如果切片是从底层数组下标为3处开始引用那么切片的第一个元素(下标为0时)引用的是数组下标为3的元素
2. 数组和切片的共同点
它们都是集合类型
3. 值传递和引用传递
如果实参是值类型 就是值传递 如果实参为引用类型则是引用传递 一般来说引用传递更快更好
go语言中值类型 : 数组,和内置的数据类型 以及结构体
go语言中引用类型: 切片(slice) 字典(map) 通道(channel) 函数(func) 是引用类型 引用类型一般使用make创建和初始化
4. 关于切片长度和容量的计算
切片长度一般是对底层数组的引用范围 比如s1=s2[3:6] [3,6)引用范围为3-5所以长度为6-3=3,但是切片可以向右扩展而不能向左扩展 所以 s1的容量就 = s2的容量-3 3是对数组引用的起始下标 6是对数组引用的结束下标
5. 关于append和切片扩容
一般使用append对切片进行追加元素 分为以下两种情况
1. 追加过后元素长度小于容量
append返回原切片
2. 追加过后元素长度超过了容量
2.1 如果长度小于1024
则扩容机制为 新切片容量 = 原切片容量*2
返回新切片地址
2.2 如果长度大于1024
则扩容机制为 新切片容量 = 原切片容量*1.25
返回 新切片地址
2.3 如果要追加的元素过多 比切片容量的两倍还多
则再进行前面 2.1 2.2的操作
重点 因为切片必定引用一个底层数组 所以数组也不会是原来的数组了
5. 切片的缩容
回答到思考题当中
思考题答案
1. 如果多个切片引用到同一个数组应该注意什么
这个问题 就像并发问题 多个线程同时操作一块内存区域 所以要注意的是 读写顺序 及读写过后的更新问题 避免本来想读老数据 却被另外一个切片给写入数据了
2. 切片缩容问题
其实可以反向思考 扩容问题
当切片的容量小于等于一定比例后 有大量的空间被浪费 所以新弄一个新切片 容量为原切片按比列缩小并返回新的切片。
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)