教程文档
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/

Go Tools

image

GOROOT、GOPATH目录

GOROOT目录是go的安装目录,含有标准库的包;GOPATH是第三方库和开发的项目代码的目录(相当于工作区)。

GOPATH可以设置多个,go get下载的包会放置道第一个GOPATH目录下的src目录中。go install安装的包会放到GOPATH/bin下; 使用 go install时,在哪个**GOPATH**中找到了这个包,就会在哪个GOPATH下的bin目录生成可执行文件

import查找包的顺序是先GOROOT,后GOPATH。

假设引入下面的包

import "Go-Player/src/chapter17/models"

第一步:Go会先去GOROOT的src目录中查找,很显然它不是标准库的包,没找到。
第二步:继续在GOPATH的src目录去找,准确说是GOPATH/src/Go-Player/src/chapter17/models这个目录。如果该目录不存在,会报错找不到package。在使用GOPATH管理项目时,需要按照GO寻找package的规范来合理地保存和组织Go代码。
目前个人理解是$GOPATH/src下创建不同的目录,每个目录就是一个项目。

GoLand中可以设置项目GOPATH,与全局GOPATH区分。


GO111MODULE参数设置说明

用环境变量 GO111MODULE 开启或关闭模块支持,它有三个可选值:off、on、auto,默认值是 auto。

  • GO111MODULE=off 无模块支持,go 会从 GOPATH 和 vendor 文件夹寻找包。

    go在GOROOT和GOPATH中搜索所需的模块

  • GO111MODULE=on 模块支持,go 会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod 下载依赖。

    此时,即使在GOPATH下有模块,也不能导入,go只会在GOROOT中搜索

  • GO111MODULE=auto 在 $GOPATH/src 外面且根目录有 go.mod 文件时,开启模块支持。

在使用模块的时候,GOPATH 是无意义的,不过它还是会把下载的依赖储存在 $GOPATH/src/mod 中,也会把 go install 的结果放在 $GOPATH/bin 中。

模块的根目录和子目录所有的包组成一个模块,在根目录下存在go.mod文件,子目录会一直向上寻找go.mod。

模块路径指模块根目录的导入路径,是子目录路径的前缀

go.mod文件的第一行定义了模块路径,模块子目录都相对于该路径,若需要import子模块, 直接导入go.mod中模块地址+子目录 ,代码为:

import "mymod/sub_mod"

这个mymod是通过go mod init mymod指定的:

image

此时,mymod这个标识符就代表了模块路径,如果go.mod文件所在的目录下,有一个子模块test,则在go文件中的导入代码为:

import "mymod/test"

go.mod文件中会定义当前模块所需的依赖,以及可以排除和替换依赖:

module example.com/m 

require ( //模块所需依赖
    golang.org/x/text v0.3.0
    gopkg.in/yaml.v2 v2.1.0 
)

replace ( //替换依赖
    golang.org/x/text => github.com/golang/text v0.3.0
)

go.mod不用手写,可以用 go mod init example.com/m 生成 go.mod 的第一行,文件的剩余部分也不用担心,在执行 go build、go test、go list 命令时会根据需要的依赖自动生成 require 语句。


Go语言结构

https://cloud.tencent.com/developer/doc/1101 go语言手册

基本的组成包含:

1.包声明

​ 关键字 package

​ 每个go文件的第一行都要声明该文件是哪个包,要进行包声明(e.x. package main)

​ package main 表示一个可独立执行的应用程序,每个go程序都包含一个main包(类似int main() 只有一个,程序的入口函数)

Go语言的代码通过包(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。**一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。**每个源文件都以一条package声明语句开始,这个例子里就是package main,表示该文件属于哪个包。

2.引入包

​ import 包名

​ 类似python的import,引入包后使用包的函数和变量

​ 可以import一个目录,或者目录中的目录都可以

3.函数

​ func main() {
​ /* code */

​ }

​ main函数,程序的入口,要注意函数的左大括号一定要和函数名在同一行。

4.变量

5.语句与表达式

​ 与python一样,句尾无需加分号,若想一行写多个语句,则需要加分号进行分割

6.注释

Note:标识符(函数名、变量名等)如果首字母大写,表示该对象是全局可见的,是已导出的标识符,其他文件引入该包体后可以使用该标识符,相当于public;而如果是小写开头,则不可见,为未导出的标识符,相当于private,无法在包外被访问。

文件名和包名没有直接关系,不需要一致。

同一目录下的多个go文件,只能有一个包名。 import引用的是目录名,而不是包名,因此一个目录就代表了一个包, 对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。


Go语言关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

Go语言基本类型

Go语言支持复数类型:

序号 类型和描述
1 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。strings包的api可用于处理字符串
4 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型
序号 类型和描述
1 float32 IEEE-754 32位浮点型数
2 float64 IEEE-754 64位浮点型数
3 complex64 32 位实数和虚数
4 complex128 64 位实数和虚数

浮点数的相等比较是比较危险的,因为存在精度问题。

尽量使用float64,因为float32的表示范围不是很大,在计算时累积后可能导致溢出,( 因为float32的有效bit位只有23个,其它的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现误差 )

小数点前面或后面的数字都可能被省略(例如.707或1.)。很小或很大的数最好用科学计数法书写,通过e或E来指定指数部分:

const Avogadro = 6.02214129e23  // 阿伏伽德罗常数
const Planck   = 6.62606957e-34 // 普朗克常数

用Printf函数的**%g参数打印浮点数**,将采用更紧凑的表示形式打印,并提供足够的精度,但是对应表格的数据,使用%e(带指数)或%f的形式打印可能更合适。所有的这三个打印形式都可以指定打印的宽度和控制打印精度。

函数math.IsNaN用于测试一个数是否是非数NaN,math.NaN则返回非数对应的值。虽然可以用math.NaN来表示一个非法的结果,但是测试一个结果是否是非数NaN则是充满风险的,因为NaN和任何数都是不相等的(译注:在浮点数中,NaN、正无穷大和负无穷大都不是唯一的,每个都有非常多种的bit模式表示):

nan := math.NaN()
fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"

其他数字类型

以下列出了其他更多的数字类型:

序号 类型和描述
1 byte 类似 uint8
2 rune 类似 int32
3 uint 32 或 64 位
4 int 与 uint 一样大小
5 uintptr 无符号整型,用于存放一个指针

变量

如果声明了一个局部变量,但没有使用(即使赋值了也不行,一定要使用),则会编译失败。。。

全局变量允许只声明不适用

1.普通声明变量的方式

var name type

一次声明多个命令:

var name1,name2 int = 1,2

在变量声明时,若没有进行初始化,则默认为零值,对于不同的类型,零值的值不同:

  • 数值类型(包括complex64/128)为 0

  • 布尔类型为 false

  • 字符串为 ""(空字符串)

  • 以下几种类型为 nil ,只有chan、interface、func、slice、map、pointer可直接与nil比较和用nil赋值。 :

    var a *int
    var a []int
    var a map[string] int
    var a chan int
    var a func(string) int
    var a error // error 是接口
    

2.也可通过初始化的对象来推测类型

var name = "helloworld"

3.省略var,通过:=声明(这种不带声明格式的,只能在函数体内出现)短变量声明

声明变量+初始化

要注意这是一个声明语句,如果同一变量已经声明了,使用:=会编译失败,后续可用=进行赋值,修改变量的值

name:=1

name2:="helloworld"

正常来说,在同一个作用域中,是不允许对同一变量进行重复声明的,也即不允许redefinition的存在

在:=声明中,v即使已经声明了变量,也可以再对该变量进行一次短变量声明,但前提条件是:

  • 此声明与的现有声明在同一范围内v (如果v已经在外部范围中声明,则该声明将创建一个新变量§),
  • 初始化中的对应值可分配给v和
  • **声明创建了至少一个其他变量。**也即必须同时声明了别的变量。

所以以下这种情况是允许的:

a1,b1:=123,456
a1,b2:=777,999

上面第三点的反例:

a1,b2:=123,456
a1:=777 //wrong

这种重复声明的方式常见于获取函数多个返回值,例如获取错误信息:

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}

4.多重变量声明

var name1,name2,name3 type = val1,val2,val3

var name1,name2,name3 = val1,val2,val3

var (
	name1 type
	name2 type
) //这种因式分解关键子的变量声明一般用在声明全局变量

还可以使用:=对多个变量声明不同的类型

aa,bb,cc:=1,"hello",true 这称作并行声明

aa,bb,cc=1,"hello",true这称为并行赋值

并行赋值可用于接收多个函数返回值,类似于python

可用下划线来表示抛弃值,这种用法一方面可以用于保证声明的变量被使用,另一方面,有时函数的多个返回值我们并不需要每个值可以用这种方式来接收返回值。

_=aa使用了aa这个变量,保证编译能过

_,need=func(a,b)假设func有两个返回值,但不需要第一个返回值,可以这么写

交换两个变量的值,在go中不需要借助辅助变量:

a,b=b,a前提必须保证两个变量的类型相同


常量

http://books.studygolang.com/gopl-zh/ch3/ch3-06.html

Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如int或float64,或者是类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。

通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。 更像是字面值常量。。

var f float64 = 3 + 0i // untyped complex -> float64
f = 2                  // untyped integer -> float64
f = 1e123              // untyped floating-point -> float64
f = 'a'                // untyped rune -> float64

如果对象的类型是确定的,则不会有隐式转换,必须显示转换。

对于一个没有显式类型的变量声明(包括简短变量声明),常量的形式将隐式决定变量的默认类型,就像下面的例子:

i := 0      // untyped integer;        implicit int(0)
r := '\000' // untyped rune;           implicit rune('\000')
f := 0.0    // untyped floating-point; implicit float64(0.0)
c := 0i     // untyped complex;        implicit complex128(0i)

当尝试将这些无类型的常量转为一个接口值时(例如将这些对象传给interface{}),这些默认类型将显得尤为重要,因为要靠它们明确接口对应的动态类型(用接口来判断承载的类型)。

无类型整数常量转换为int,它的内存大小是不确定的,但是无类型浮点数和复数常量则转换为内存大小明确的float64和complex128。 如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样:

var i = int8(0)
var i int8 = 0

**不管是全局常量还是在函数中声明的常量,常量是在编译器创建的,而且只能是数字类型、字符、字符串或布尔类型。**因为编译时的限制,定义常量的表达式必须是常量表达式,这样编译器在编译时才能计算常量的值,而不能是一些例如函数计算值,因为调用函数是在代码运行阶段才执行的。

与变量声明相似,但不使用:=进行声明

const identifier [type] = value

一个未指定类型的常量(也即省略了[type]的声明),其类型由上下文决定:

const c1=100
func test1(a int) int{
	return a*10
}
func test2(a float32) float32{
	return a*1.2
}
func main(){
    fmt.Println(test1(c1))
    fmt.Println(test2(c1))
}
//output
1000
120.00001

常量也可用下列方式声明

const (
	a = 1
	b = 2
	c = 3
)

常量还可使用内置的函数进行计算,注意必须为内置函数,否则编译不过

const (
	a = "hello"
	b = len(a)
	c = unsafe.Sizeof(b)
)

在定义常量组时,如果不提供初始值,则后续的常量会使用上一行的值

const(
	a = 1
	b
	c
	d
)
//则a,b,c,d均为1

iota 特殊常量

这是一个特殊的常量, 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

const (
    a = iota
    b = iota
    c = iota
)// a=0 b=1 c=2
const (
    a = iota
    b
    c
) //a=0 b=1 c=2

搭配常量组不初始化时的特性,有

package main

import "fmt"

func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
} //输出结果 0 1 2 ha ha 100 100 7 8
//一个有趣的例子,k和l分别为数值3左移3和4位
const (
    i=1<<iota
    j=3<<iota
    k
    l
)// 输出结果 i= 1 j= 6 k= 12 l= 24

const(
	a = 5*iota
    b
    c
) //输出结果 a=0 b=5 c=10

这里其实是后续没有给初始化的常量保持了上一行的运算规则

iota只在同一个常量组中计数自增;别的常量中的iota重置,重新计数。


运算符

各类符号、优先级与C++相同

要注意自增、自减只有后置,没有前置用法,且自增与赋值不能同时使用:

a=b++这是错误的语法,编译不过

++ --都是声明,不是表达式,所以只能够单独a++或a--,不存在其与其他运算符搭配在一起形成表达式

Go语言中的逗号跟C++中的不一样,C++中的是一个表达式,例如1+2,3+4得到的是3(计算完两侧的表达式后,返回左表达式的值),而Go中的逗号就是一个分隔作用,没有表达式的含义。

&      位运算 AND
|      位运算 OR
^      位运算 XOR
&^     位清空(AND NOT)
<<     左移
>>     右移

其中&的含义是按位置零,例如x&y,若y中某位为1,则x的相应位置为0后作为返回值。


命令行参数

通过 程序名 arg1 arg2 arg3 来输入命令函参数的,Go语言提供了os包,命令函参数存在了os.Args切片中,这是一个string类型的切片,第一个元素为程序名,后面的则为参数的值。

import "os"
func main(){
	for i:=1;i<len(os.Args);i++{
		fmt.Println(os.Args[i])
	}
}
go build hello.go
./hello nihao rex
nihao
rex