环境搭建
- mac下,下载go安装包直接安装
- linux下,下载go源码自行编译或直接下载对应版本的预编译版本
- 安装VScode 以及 Go 插件
- 设置 GO111MODULE="on", 设置 GOPROXY="https://goproxy.io" => 用于代理安装依赖
变量
申明:
申明变量的格式为 var name type
,举个例子,想申明一个变量为年龄,则使用 var age int
初始化:
在Go中,每申明一个变量时,会被初始化赋值为该类型的基础值,例如,int 则为 0,float 则为 0.0,bool 为 false, string 为 "",指针(函数、切片)为 nil 等。
一半的初始化写法不同语言都基本一致: var age int = 18
, 其实,这里可以采用类似于动态语言的写法,不申明变量类型,形如 var age = 18
,但实际上是静态的,此处Go会判断右侧值得基本类型,并给左侧变量确定基本类型。
同时,在Go中,可以在申明时直接初始化,格式为 age := 18
,这个的好处就是简洁。
赋值:
初始赋值在初始化中已经说过了,另外就是在赋值语句中,将赋值号右侧表达式的返回值赋值给左侧变量。大体而言是这样的 age = 19
。
在Go语言中,表达式支持返回多个值,因此,赋值语句的左侧也支持放多个变量,格式为 name, age := "zhangsan", 19
, 多重赋值的用处,在错误处理机制和条件判断时非常有用,可以人为约定返回的第一个值为错误码,只有错误码为空时,才能继续执行后续的代码。
匿名变量:
由于可以多重赋值,因此在接收表达式的值时,就必须有一个变量去接收所赋的值,例如 neednot, name := "xxx", "long"
, 但实际我们在后面的代码中,不会用到前面传回的值,而 go 的语法检查,又不允许我们初始化一个我们不会使用的变量,因此,这个 匿名变量 就派上用处了,只需要用一个特定的变量名去接收,就可以不去使用这个变量,这个变量名为 _
,也就是可以这样: _, name = "xxx", "long"
。
作用域:
变量都有其生效的范围,变量的作用域可以分为3种,其一, 局部变量
,也就是在函数体内的变量,举个例子:
package main
import "fmt"
func main () {
// 申明并初始化一个 string 类型的局部变量 name
var name string = "long"
fmt.Println("my name is ", name) // my name is long
}
在这个例子中,name 就是一个局部变量,仅有在main函数中,才能使用这个变量。
ps: 这里的 “在函数中”,指的是“在函数中定义的”,有些编程语言的变量是动态的,只有在运行时才能判断是否存在该变量,因此有些时候可以在申明函数时不申明该变量,而是在实际运行时才会有,例如 shell 以及 python, 拿python举个例子:
def myfunc () {
print(myname) # longalong
}
if __name__ == "__main__":
myname = "longalong"
myfunc()
如果按照在“函数定义处确定变量”这个规则,那么 myfunc 中的 myname 变量则未定义,但实际,这里会输出 “longalong”。
需要注意的是,Go 和大多数语言一样,局部变量的范围为 “在函数体定义的地方”。
除了局部变量,还有一个叫做 全局变量
,顾名思义,就是在全局中申明的变量。举个例子:
package main
import "fmt"
var name string = "longalong"
func main () {
fmt.Println("my name is : ", name) // longalong
}
虽然叫做 全局变量,但其实和局部变量是大概一个意思,只是之前的 局部
指的是 函数体
,而这里的 全局
指的是 当前文件
。
除了上述两者,还有一个比较特殊的变量,叫做 形参变量
,它所对应的范围,和局部变量一样,都是函数体内,大致可以理解成,传形参时,Go语言主动申明了一个局部变量去接收这个参数,举个例子:
package main
import "fmt"
func main () {
var nameInMain string = "longalong"
myfunc(nameInMain)
}
func myfunc (name string) {
fmt.Println("name in myfunc is : ", name) // longalong
}
这里有一点值得说明的,那就是:在 Go 中,所有的传参都是传的 “形参”,没有在一些语言中的 传值 与 传址 之分(例如 vb)。但这并不意味着所有传入函数的参数,都不会影响到原来的参数,因为,在Go中,有“指针”这个概念,因此,若传的是指针,传入函数时,传的是 “指针所指地址的形参”,因此,在函数内部改变了该地址的值时,函数以外是会受到影响的。举个例子:
package main
import "fmt"
func main () {
name := "longalong"
fmt.Println("before change : ", name) // longalong
changeName(name)
fmt.Println("after change : ", name) // longalong
}
func changeName (name string) {
name = "mylong"
fmt.Println("name in changeName : ", name) // mylong
}
可以发现,传入的基本类型不受影响。
package main
import "fmt"
func main() {
oriArr := [3]string{"long", "long2", "longalong3"}
fmt.Println("before change : ", oriArr) // [long, long2, longalong3]
changeArr(&oriArr)
fmt.Println("after change : ", oriArr) // [ahahahaha, long2, longalong3]
}
func changeArr(arr *[3]string) {
arr[0] = "ahahahaha"
}
可以发现,传入指针后,原来的变量发生了改变。
类型
在了解了 变量
之后,下一步就是理解 类型
, Go是一门强类型语言,也就是和 js 不同,所有的类型均是确定的,需要明确申明。ps:需要说明一下,动态类型和静态类型相对应,强类型和弱类型相对应,前者指的是“是否需要明确申明变量的类型”,后者指的是“是否可以隐式改变变量类型”,js是弱类型也是动态类型,而python仅是动态类型,是强类型。
因此,我们需要非常清楚地认识Go语言中的类型。
一般来说,几乎所有的语言都会分为两种类型,其一 基础类型
,其二 复杂类型
,在基础类型中,基本都会包括: 数字类型
、 字符串类型
、 bool类型
,在复杂类型中,基本都会有: 数组类型
、 字典类型
、字节类型
。 若语言支持指针,则基础类型还包括 指针类型
。 ps:在传统js中,没有 字节类型
,取而代之的是用对象数组进行模拟(例如 new ArrayBuffer(8)),在nodejs中,则提供了 Buffer 类。
因此,我们在申明一个变量时,就得确定清楚,我们需要一个什么样的变量,例如,我们需要一个存放名字的变量,则需要 var name string
这样的方式;又如,我们需要一个存放金额的变量,则需要 var amount float64
这样的方式;再如,我们需要存放一个星期中我们每天写代码的时长,则可以 var codingTimes [7]float32 = [7]float32{}
。
特殊的:
在Go中,有一种特殊的类型 复数
,这是数学中的概念,除了在一些科学计算方面会使用,一般是用不到的。
在Go中,主要有这些类型:
- bool类型。
var isSuccess bool = true
- 数字类型。
var age uint8 = 18
var amount float64 = 121.221
- 字符串类型。
var title string = "this is title"
- 指针类型。
intVal := 8; var ptr *int = &intVal
- 数组类型。
var arr [3]int = [3]int{2, 21, 32}
- 切片类型。
- 结构体类型。
- 字典类型。
- 接口类型。
- 通道类型。
- 函数类型。
运算符
运算符在程序中起着操作变量以及表达式值得作用,所以,实际上,所有值的变换规则,都是有运算符来决定的。
一半来说,编程语言都会有的运算符类型:
- 算数运算符。 数字的各种运算,四则运算、取余、取整、自增、自减。
- 关系运算符。 数字键的比较。 大于、小于、等于、不等于、不大于、不小于。
- 逻辑运算符。 bool类型的关系。 与、或、非、异或。
- 位运算符。 对于每一位进行的逻辑运算,然后再汇总。
- 赋值运算符。 各种类型的赋值。直接赋值、增加赋值、减少赋值等算数运算或位运算后赋值。
- 三目运算符。 简化版的if判断。(Go当中没有)
- 字符串拼接运算符。 字符串拼接,一半为
+
, php中为.
, 也有不需要的,例如 python。 - 取值与取地址运算符。 * 取值,&取地址。
流程控制语句
流程控制语句是用来控制程序的执行结构的,也就是说,程序的执行逻辑,是由这类语句控制的。流程控制,有三种,顺序结构
分支结构
循环结构
。
分支结构
if语句
if语句是最基础的分支结构控制语句,根据判断条件,可以形成两个路径。举个例子:
package main
import "fmt"
func main () {
isSuccess := true
if isSuccess {
fmt.Println("条件为真") // 条件为真
} else {
fmt.Println("条件为假")
}
}
其实,在Go语言中,if语句是有隐藏的用法的,也就是在 if 的条件判断之前,可以执行一句代码,因此,很多时候可以在第一步执行一个函数,然后根据返回情况决定下一步的操作,举个例子:
package main
import "fmt"
func main () {
if result := doSomething(); result {
fmt.Println("操作成功")
} else {
fmt.Println("操作失败")
}
}
func doSomething () bool {
isSuccess := true
return isSuccess
}
很多时候,上面这种结构用来判断一个函数是否执行无误,也就是看返回是否有 err 来判断是否继续执行。
switch语句
几乎在所有的编程语言中,都只有这两种分支结构语句,if 用来做两条分支的分流,switch 用来做多种分支的分流。
举个例子:
package main
import "fmt"
func main () {
days := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
today := weekdays[1]
switch today {
case "Mon":
fmt.Println("today is monday")
case "Tue":
fmt.Println("today is tuesday")
case "Wed":
fmt.Println("today is wednesday")
case "Thu":
fmt.Println("today is thusday")
case "Fri":
fmt.Println("today is friday")
case "Sat", "Sun":
fmt.Println("today is not weekday, ahhhhhh")
default:
fmt.Println("you must be kidding, no such a day !")
}
}
其实,case后的这块儿内容,可以是逗号分隔的多个目标,也可以是一个表达式。
值得注意的是,有些语言的 case ,其实不是具有独占性的,例如 c 、js等,这意味着他们在进行了第一个case匹配后,若没有遇到 break、continue、return、goto 等关键词时,是会继续进行下一个 case 的匹配的,而在Go中,默认是仅进行一次匹配的,若希望继续向下匹配case,需要使用 fallthrough
关键词。
循环结构
计算机的强大之处,其一就在于能够重复执行大量的运算逻辑,体现在编程语言上,就是循环结构。
在各类编程语言中,几乎都有两类循环控制语句,一类是 for 循环,另一类的 while 循环。而在 Go 语言中,是没有 while 循环的,因为 for 循环能搞定一切。举个例子:
package main
import "fmt"
func main () {
for round := 1; round <= 10; round++ {
fmt.Println("in round ", round)
}
round2 := 1
for ; round2 <= 10; round2++ {
fmt.Println("round2 in round : ", round2)
}
round3 := 1
for round3 <= 10 {
fmt.Println("round3 in round : ", round3)
round3++
}
round4 := 1
for {
if round4 > 10 {
break
}
fmt.Println("round4 in round : ", round4)
round4++
}
fmt.Println("finally get out of for loop")
}
这里面的三个语句块,可以进行很多的省略,例如 round := 1
其实可以是放在外面的,for 语句中可以直接空着; round++
是可以放在循环体的最后的,for 语句中也是可以直接空着的; 若第一句和第三句均空着的话,那么可以直接省略前后两个分号; 如果三句均为空着的话,那么完全可以不写分号,这表示为一个无限循环语句,或者是一个在程序块内判断是否结束循环,然后用break、goto去控制何时结束循环。
循环控制语句中的另外两个关键字 break
和 continue
,这两者的用法和其他语言的用法一样。
迭代器
在各类语言中,都有可迭代对象,例如 数组、切片、字典、字符串等等,遍历这些可迭代对象是非常常见的操作,因此,需要有支持迭代的语句。在Go语言中,这个迭代语句为 for range
, 举个例子:
package main
import "fmt"
func main () {
loopTar := [...]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
for key, val := range loopTar {
fmt.Println("key is :", key, " value is : ", val)
}
}
这是一个数组的迭代,可以迭代的类型有很多,例如 切片、字符串、字典、通道(在Go语言中独有的),这里再举个字典的迭代例子:
package main
import "fmt"
func main () {
loopTar := map[string]string {
"Monday": "星期一",
"Tuesday": "星期二",
"Wednesday": "星期三",
"Thusday": "星期四",
"Friday": "星期五",
}
for key, val := range loopTar {
fmt.Println("today is : ", key, " 今天是 : ", val)
}
}