Go在vscode中的配置以及运行
通过参考Go安装
中(1)(2),以及注意基本能够完成配置。
go的运行可以通过go run xxx.go
来简单地完成。
go run
命令的相关注意:
可以在
go run
后面追加命令行参数,这部分参数会作为代码可以接受的命令行输入提供给程序。该命令会编译源码,并且直接执行源码的
main()
函数,不会在当前目录留下可执行文件,可执行文件被放在临时文件中被执行。go run
不能使用go run+包
的方式进行编译,如需要快速编译,可以用下面步骤来代替:1
21,使用go build生成可执行文件。
2,运行可执行文件。
Go语法速记
基础
包、变量和函数
包
每个Go程序都是由包构成的。程序从main
包开始运行。
能够通过import
导入外部包,可以单独导入,也能以圆括号()
的方式进行分组导入。按照规定,包名与导入路径的最后一个元素一致。例如,“math/rand”
包中的源码均以package rand
语句开始。以一个程序为例子:
1 | package main |
导出名
观察到rand.Intn()
的Intn
方法首字母进行了大写,这是导出名
的规范。在Go中,如果一个名字以大写字母开头,那么它就是已导出的(例如,math.Pi)。在导入一个包时,只能引用其中已导出的名字,任何“未导出”的名字在该包外均无法访问。
函数
【函数形参】对比
add
和main
函数,可知函数可以没有参数或接受多个参数;参数列表中,变量的类型在变量名之后。这样的声明方式有点奇怪,这篇文章中解释了这种类型声明方式出现的原因(Go’s Declaration Syntax - Go 语言博客 (go-zh.org))。同时,连续的相同类型的形参,除最后一个外,它们的类型可以省略(如“x int, y int”->”x,y int”)【函数返回值】
go
中的函数可以返回任意数量的返回值,例如swap
返回了两个字符串:1
2
3func swap(x, y string)(string, string){
return y, x
}【命名返回值】
go
函数的返回值可以被命名,它们会被视为定义在函数顶部的变量。如下例,没有参数的return语句会返回已命名的返回值。返回值的名称应该有一定含义,以便阅读,且命名返回值应该仅用于小函数,否则会影响代码的可读性。1
2
3
4
5func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
变量
【变量声明】
var
语句用于声明一个变量列表,同样,类型放在最后。var
语句可以出现在包或函数级别。【变量初始化】变量声明中可以包含初始值,每个变量对应一个。如果初始化值已存在,那么就可以省略类型,变量会从初始值中获得类型。
1
2
3
4
5
6
7
8
9var c, python bool
var i, j int = 1, 2
var c, python, java = true, false, "no!"
func test(k int) int {
k:=3
return
}【短变量声明】在函数中,可以使用简洁赋值语句“:=”来在类型明确的地方代替var声明。注意,函数外的每个语句都必须以关键词开始(var, func等),因此”:=”不能在函数外使用。
没有初始化的变量声明,会为变量赋予零值。数值类型为
0
,布尔类型为false
,字符串为""
。
基本类型
go
中的基本类型有如下:
1 | bool |
同导入语句一样,变量声明也可以“分组”成一个语法块:
1 | import ( |
int
, uint
和 uintptr
在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 当你需要一个整数值时应使用 int
类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
类型转换
表达式T(v)将值v的类型转化为T。与C不同的是,go在不同类型的项之间赋值时需要显式转换,否则将会报错。一些类型转换的例子:
1 | func main(){ |
常量
常量使用const
关键词声明,且不能用”:=”语法声明。
1 | const Pi = 3.14159 |
流程控制语句:for、if、switch和defer
for
go
中只有一种循环结构,即for
循环。基本for循环由三部分构成:初始化语句,条件表达式,后置语句。初始化语句通常为一句短变量声明,该变量在for作用域中可见。
1 | func main(){ |
注意:go的for语句三个构成部分外没有小括号()
,大括号{}
则是必须的。
与C一样,初始化语句和后置语句是可以省略的。若这两个部分省略了,此时的for就相当于while,可以这么描述:
1 | sum:=1 |
如果再省略条件表达式,那么就会构成一个无限循环语句块:
1 | for { |
if
if
语句与for类似,表达式外没有小括号(),而需要大括号{}。if
语句也可以在条件表达式之前执行一个简单的语句,该语句声明的变量作用域仅在if
内:
1 | func pow(x, n, lim float64) float64 { |
练习
为了掌握for
和if
语句,现在来做一个practise:用牛顿法实现平方根函数。
计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:
1 z -= (z*z - x) / (2*z)重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。
在提供的
func Sqrt
中实现它。无论输入是什么,对 z 的一个恰当的猜测为 1。 要开始,请重复计算 10 次并随之打印每次的 z 值。观察对于不同的值 x(1、2、3 …), 你得到的答案是如何逼近结果的,猜测提升的速度有多快。提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:
1
2 z := 1.0
z := float64(1)
我的一个实现:
1 | package main |
switch
go的switch
语句与C类似,但是go只运行选定的case,而非之后所有的case,即go自动提供了每个case后面的break语句(除非以fallthrough语句结束,否则分支会自动终止)。另一个重要的不同在于go中的switch无需为常量,且取值不必为整数。例如:
1 | package main |
switch顺序地从上往下匹配,直到匹配成功停止。
技巧:没有条件的switch同switch true
一样,这种形式能将一长串if-then-else写的更加清晰。
1 | package main |
defer
defer语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
在一个函数内,推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照LIFO的顺序调用。Defer 通常用于简化执行各种清理操作的功能。例如:
1 | func CopyFile(dstName, srcName string) (written int64, err error) { |
Defer 语句允许我们在打开每个文件后立即考虑关闭它,从而保证无论函数中的 return 语句数是多少,这些文件都将被关闭。
关于defer的更多信息,可以参考Defer,Panic, and Recover这篇文章。
更多类型:struct、slice和映射
指针
go
具有指针,指针保存了值的内存地址。类型*T
是指向T
类型值的指针,其零值为nil
。
&
操作符会生成一个指向其操作数的指针;
*
操作符表示指针指向的底层值。
但是注意,与C不同的是,go没有指针运算。
1 | func main() { |
结构体
一个结构体(struct)就是一组字段。结构体内部的字段使用点号”.”来访问,也可以通过结构体指针来访问(指针访问按道理应该(*p).X来访问字段X,但是这样太啰嗦了,所以也允许隐式间接引用,直接p.X)。
1 | type XiaoBei struct{ |
【结构体文法】能够允许直接列出字段的值来新分配一个结构体,使用Name:
语法可以仅列出部分字段(字段名的顺序无关)。
1 | var ( |
数组
类型[n]T
表示拥有n
个T
类型值的数组。例如,var a [10]int
。这样的数组是固定大小的。
切片
切片为数组元素提供了动态大小的、灵活的视角。
类型[]T
表示一个元素类型为T
的切片。切片通过两个下标来界定,它是一个半开区间 [start, end)。
注意:切片就像数组的引用。切片本身并不存储任何数据,更改切片的元素会修改其底层数组中对应的元素。同时,与它共享的底层数组的切片都会观测到这些修改。
1 | func main() { |
【切片文法】
[]bool{true, true, false}
会创建一个[3]bool
数组,并构建一个引用了它的切片。【切片的默认行为】在进行切片时,可以利用它的默认行为来忽略上下界,切片下界默认为0,而上界是切片的长度。
【切片的长度与容量】长度:切片所包含的元素个数(可以理解成
视图
),用len(s)
来获取;容量:从切片的第一个元素开始数,到其底层数组元素末尾的个数(理解为切片的最大尺寸),用cap(s)
来获取。【nil切片】切片的零值是
nil
,nil
切片的长度和容量为0
,且没有底层数组。【用make创建切片】利用内建函数
make
来创建切片,这也是创建动态数组的方式。1
2
3a := make([]int,5) // len(a)=cap(a)=5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
c := b[2:5] // len(c)=3, cap(c)=3【切片的切片】切片可以包含任何类型,甚至包括它的切片。也就是多维数组的形式。
【向切片追加元素】内建函数
append
能够为切片追加新的元素。1
func append(s []T, vs...T) []T
第一个参数
s
,为类型T
的切片;第二个参数vs
,表示其他类型为T
的值将追加到切片的末尾。当s的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组,返回的切片会指向这个新分配的数组。
【More…】关于切片更多的内容,参考Go 切片:用法和本质 - Go 语言博客 (go-zh.org)
方法和接口
并发
参考
Go安装:
注意,在(2)完成后,vscode会报go mod不存在的问题,原因是go 1.16之后推荐使用go module构建项目,go module可以将某个项目(文件夹)下的所有依赖整理成一个 go.mod 文件,里面写入了依赖的版本等。所以需要执行一步:
1 | >> go init mod <文件名> |
Go语法: