Go的基础语法学习


Go的简介

Go是一门编译语言

它把所有的代码编译成一个可执行文件,在编译的过程中,Go编译器能够捕获一些错误

而Python等语言使用解释器,随着程序的运行,一个语句一个语句的进行翻译,这些是解释型语言

Go的安装

1、go语言中文网下载安装包

2、打开vscode下载Go插件

3、部署Go代理

打开命令行,分别输入

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

4、重启vscode(很重要!!!!不能忘)

5、在vscode中ctrl+shift+p,输入go,点开Go:Install/Update Tools全选所有安装包,点击确认

Go的学习(一)

Print,Println 函数

•可以传递若干个参数,之间用逗号分开。

•参数可以是字符串、数字、数学表达式等等。

//println光标定位在下一行的开头

格式化打印printf函数

Printf 的第一个参数必须是字符串

•这个字符串里包含了像 %v 这样的格式化动词,它的值由第二个参数的值所代替。

•如果指定了多个格式化动词,那么它们的值由后边的参数值按其顺序进行替换。

const:声明常量

var:声明变量

go语言里++运算符只能放在后缀,而不能放在前缀

使用 rand 包,可以生成伪随机数

•例如,Intn 可以返回一个指定范围的随机整数

•import 的路径是 “math/rand”

Boolean类型

只有true和false两种情况

strings.Contains函数

•来自 strings 包的 Contains 函数可以判断某个字符串是否包含另外要给字符串

使用 switch 做分支

有一个 fallthrough 关键字,它用来执行下一个 case 的 body 部分。

变量及作用域

•在 Go 里面,作用域的范围就是 { } 之间的部分

短声明

•count := 10

可以在无法使用 var 的地方使用,如if语句,for语句,switch语句中

package 作用域

若变量是在main函数外声明的,那么它拥有package作用域,如果 main package 有多个函数,那么该变量对它们都可见

注:短声明不可用来声明 package 作用域的变量

Go的学习(二)

实数

声明浮点型变量

只要数字含有小数部分,那么它的类型默认就是 float64

如果你使用一个整数来初始化某个变量,那么你必须指定它的类型为 float64,否则它就是一个整数类型

浮点数类型

Go 语言里有两种浮点数类型:

•默认是 float64

•64 位的浮点类型

•占用 8 字节内存

•某些编程语言把这种类型叫做 double(双精度)

•float32

•占用 4 字节内存

•精度比 float64 低

有时叫做单精度类型

零值

•Go 里面每个类型都有一个默认值,它称作零值。

•当你声明变量却不对它进行初始化的时候,它的值就是零值。

显示浮点类型

•使用 Print 或 Println 打印浮点类型的时候,默认的行为是尽可能的多显示几位小数

如果你不想这样,那么你应该使用 Printf 函数,结合 %f 格式化动词来指定显示小数的位数

%f 格式化动词

•它由两部分组成:

•宽度:会显示出的最少字符个数(包含小数点和小数)

•如果宽度大于数字的个数,那么左边会填充空格

•如果没指定宽度,那么就按实际的位数进行显示

•精度:小数点后边显示的位数

•如果想使用 0 代替空格作为填充:

fmt.Printf("%05.2f\n",third)

浮点类型精度较低,不适合用于金融类计算为了尽量最小化舍入错误,建议先做乘法,再做除法

整数

Go 提供了 10 种整数类型:

•不可以存小数部分

•范围有限

•通常根据数值范围来选取整数类型

最常用的整数类型是 int,无符号整数类型是 uint

打印数据类型

•在 Printf 里使用 %T 就可以打印出数据的类型。

打印十六进制

•打印十六进制的数,使用 %x 格式化动词

打印每个 bit

•使用 %b 格式化动词

math 包里,为与架构无关的整数类型,定义了最大、最小值常量

很大的数

使用big包

如何把 24 x 1018 转化为 big.Int 类型?

•首先 new 一个 big.Int

•再通过 SetString 函数把数值的字符串形式,和几进制传递进行即可。

distance := new(big.Int)
distance.SetString("24000000000000000000",10)
fmt.Println(distance)

在 Go 里面,常量是可以无类型的(untyped),这种无类型的数值字面值就是由 big 包所支持的。这使你可以操作很大的数(超过18的10¹⁸)

const distance = 2400000000000000 #不会报错
fmt.Println(distance)  #会报错,因为println默认传入值为int类型

尽管 Go 编译器使用 big 包来处理无类型的数值常量,但是常量和 big.Int 的值是不能互换的。

多语言文本

字符串字面值/原始字符串字面值

•字符串字面值可以包含转义字符,例如 \n

•但如果你确实想得到 \n 而不是换行的话,可以使用 ` 来代替 “,这叫做原始字符串字面值。

字符,code points,runes,bytes

Unicode 联盟为超过 100 万个字符分配了相应的数值,这个数叫做 code point。

•为了表示这样的 unicode code point,Go 语言提供了 rune 这个类型,它是 int32 的一个类型别名。

•而 byte 是 uint8 类型的别名,目的是用于二进制数据。

•byte 倒是可以表示由 ASCII 定义的英语字符,它是 Unicode 的一个子集(共128个字符)

类型别名

类型别名就是同一个类型的另一个名字,所以,rune 和 int32 可以互换使用。

如果想打印字符而不是数值,使用 %c 格式化动词

string

•可一个给某个变量赋予不同的 string 值,但是 string 本身是不可变的

当字符串需要支持西班牙语,俄语,汉语等,应把字符解码成rune类型,然后再进行操作

•使用 utf-8 包,它提供可以按 rune 计算字符串长度的方法。

DecodeRuneInString 函数会返回第一个字符,以及字符所占的字节数。

RuneCountInString函数会返回按rune计算字符串的长度

使用 range 关键字,可以遍历各种集合

question :="abcdefg"
for i,c :=range question {
    fmt.Printf("%v %c\n",i,c)
}
for _,c:= range question {
    fmt.Printf("%c ", c)
}

类型间转换

连接两个字符串,可以使用 + 运算符,如果想连接字符串和数值,会报错

整型和浮点类型也不能混着用

数值类型间的转换

如果想把 age 转化成 浮点类型,需要使用目标类型将其“包裹”起来

从浮点类型转为整数类型

可以从浮点类型转化为整数类型,小数点后边的部分会被截断,而不是舍入

•无符号和有符号整数类型之间也需要转换

•不同大小的整数类型之间也需要转换

•可以通过 math 包提供的 max、min 常量,来判断转换的时候是否超过最大最小值

字符串转换

把 rune、byte 转化为 string

var pi rune = 960
fmt.Print(string(pi))

想把数值转化为string,它的值必须能转化为 code point

可以用strconv 包的 Itoa 函数 (Integer to ASCII)

str := "Launch in T minus" + strconv.Itoa(countdown) + "seconds."

另外一种把数值转化为 string 的方式是使用 Sprintf 函数,和 Printf 略类似,但是会返回一个 string

countdown := 9
str := fmt.Sprintf("Launch in T minus %v seconds.",countdown)

strconv 包里面还有个 Atoi(ASCII to Integer) 函数

由于字符串里面可能包含任意字符,或者要转换的数字字符串太大,所以 Atoi 函数可能会发生错误

countdown ,err := strconv.Atoi("10")

if err != nil {

}

fmt.Println(countdown)

注:Go 是静态类型语言,一旦某个变量被声明,那么它的类型就无法再改变了

布尔类型的转换

•Print 家族的函数,会把 bool 类型的值打印成 true/false 的文本

注意:如果你想使用 string(false), int(false);bool(1), bool(“yes”) 等类似的方式进行转换,那么 Go 编译器会报错

某些语言里,经常把 1 和 0 当作 true 和 false,但是在 Go 里面不行

函数

声明新类型

关键字 type 可以用来声明新类型

type celsius float64
var temperature celsius = 20

方法

可以将方法与同包中声明的任何类型相关联,但不可以是 int、float64 等预声明的类型进行关联。

type celsius float64
type kelvin float64
func kelvinToCelsius(k kelvin) celsius {
	return celsius(k - 273.15)
}
func (k kelvin) celsius() celsius{    //表示clesius是kelvin的一个方法
	return celsius(k - 273.15)
}

上例中,celsius 方法虽然没有参数。但它前面却又一个类型参数的接收者。

每个方法可以有多个参数,但只能有一个接收者。

在方法体中,接收者的行为和其它参数一样

调用:变量.方法()

闭包和匿名函数

匿名函数就是没有名字的函数,在 Go 里也称作函数字面值。

因为函数字面值需要保留外部作用域的变量引用,所以函数字面值都是闭包的。

闭包(closure)就是由于匿名函数封闭并包围作用域中的变量而得名的。

package main
import "fmt"
type kelvin float64
//sensor function type
type sensor func() kelvin
func realSensor() kelvin {
    return 0
}

fun calibrate(s sensor,offset kelvin) sensor{
    return func() kelvin {
        return s() + offset
    }
}

func main(){
    sensor := calibrate(realSensor, 5)
    fmt.Println(sensor())
}

Go语言学习(三)

数组

var planets[8]string

使用复合字面值初始化数组

复合字面值(composite literal)是一种给复合类型初始化的紧凑语法

dwarfs := [5]string{"Ceres","Pluto","Haumea","Makemake","Eris"}

可以在复合字面值里使用 … 作为数组的长度,这样 Go 编译器会为你算出数组的元素数量

无论哪种方式,数组的长度都是固定的。

切片slice

使用的是半开区间,例如 planets[0:4],包含索引 0、1、2、3 对应的元素,不包括索引 4 对应的元素。

Go 里面很多函数都倾向于使用 slice 而不是数组作为参数。

想要获得与底层数组相同元素的 slice,那么可以使用 [:] 进行切分

切分数组并不是创建 slice 唯一的方法,可以直接声明 slice

dwarfs := []string{"Ceres","Pluto","Haumea"}

append函数为内置函数,可以将元素添加到slice里面

限制新建切片容量的三索引切分操作

terrstrial := planets[0:4:4]
#新建含有四个元素的planets切片

当 slice 的容量不足以执行 append 操作时,Go 必须创建新数组并复制旧数组中的内容。

可以使用 make 函数对 slice 进行预分配

dwarfs := make([]string,0,10)
#新建长度为0,容量为10的切片

•尽量避免额外的内存分配和数组复制操作。

声明 Printf、append 这样的可变参数函数,需要在函数的最后一个参数前面加上 … 符号。

map

map 是 Go 提供的另外一种集合:

•它可以将 key 映射到 value。

•可快速通过 key 找到对应的 value

•它的 key 几乎可以是任何类型

#逗号与ok写法
if moon,ok := temperature["Moon"]; ok{
	fmt.Printf("On average the moon is %v \n",moon)
}else {
    fmt.Println("Where is the moon?")
}

map类型在赋值给新变量或传递至函数/方法的时候会创建相应的副本,即如果对原map里某个对象修改,则该副本也会被修改

•除非你使用复合字面值来初始化 map,否则必须使用内置的 make 函数来为 map 分配空间。

•创建 map 时,make 函数可接受一个或两个参数

第二个参数用于为指定数量的 key 预先分配空间

Go语言学习(四)

struct

定义struct

type location struct{
	lat,long float64
}

通过复合字面值初始化 struct

•两种形式:

•通过成对的字段和值进行初始化

opportunity := location{lat: -1.9462, long:354.4734}

•按字段定义的顺序进行初始化

insight := location{4.5,135.9}

将 struct 编码为 JSON

json 包的 Marshal 函数可以将 struct 中的数据转化为 JSON 格式

•Marshal 函数只会对 struct 中被导出的字段(大写字母开头)进行编码。

•Go 语言中的 json 包要求 struct 中的字段必须以大写字母开头,类似 CamelCase 驼峰型命名规范。

但有时候我们需要 snake_case 蛇形命名规范,那么该怎么办?

•可以为字段注明标签,使得 json 包在进行编码的时候能够按照标签里的样式修改字段名。(例子 21.10)

type location struct{
	lat float64 'json:"latitude"'
	long float64 'json:"longitude"'
}

Go 语言没有 class

Go 和其它经典语言不同,它没有 class,没有对象,也没有继承。但是 Go 提供了 struct 和方法。

方法可以被关联到你声明的类型上

•可以使用 struct 复合字面值来初始化你所要的数据。

但如果 struct 初始化的时候还要做很多事情,那就可以考虑写一个构造用的函数。

•Go 语言没有专用的构造函数,但以 new 或者 New 开头的函数,通常是用来构造数据的。例如 newPerson(),NewPerson()

小写字母开头只能在本包中使用,大写字母开头可以在别的包中使用

•有一些用于构造的函数的名称就是 New(例如 errors 包里面的 New 函数)。

Go 语言没有 class,但使用 struct 并配备几个方法也可以达到同样的效果。

组合

•Go 通过结构体实现组合(composition)。

•Go 提供了“嵌入”(embedding)特性,它可以实现方法的转发(forwarding)

type report struct {
	sol 		int
	temperature temperature
	location	location
}
type temperature struct{
	high, low celsius
}
type location struct {
	lat, long float64
}
type celsius float64

转发方法

•Go 可以通过 struct 嵌入 来实现方法的转发。

在 struct 中只给定字段类型,不给定字段名即可。

在 struct 中,可以转发任意类型。

type report struct {
	sol 		int
	temperature 
	location	
} //temperature和location就是struct的嵌入
type temperature struct{
	high, low celsius
}
type location struct {
	lat, long float64
}
type celsius float64

命名冲突时,其本身的方法比转发的方法优先级更高

•优先使用对象组合而不是类的继承。

•对传统的继承不是必需的;所有使用继承解决的问题都可以通过其它方法解决。

接口

•接口关注于类型可以做什么,而不是存储了什么。

•接口通过列举类型必须满足的一组方法来进行声明。

•在 Go 语言中,不需要显式声明接口。

typpe talker interface {
	talk() string
}
type martian struct{}
func (m martian) talk() string {
	return "nack nack"
}
type laser int 
func (l laser) talk() string {
	return strings.Repeat("pew ",int(l))
}
func shouter (t talker) {
    louder := strings.ToUpper(t.talk())
    fmt.Println(louder)
}

•为了复用,通常会把接口声明为类型。

•按约定,接口名称通常以 er 结尾。

•Go 标准库导出了很多只有单个方法的接口。

•Go 通过简单的、通常只有单个方法的接口……来鼓励组合而不是继承,这些接口在各个组件之间形成了简明易懂的界限。

Go语言学习(五)

指针

大致与c语言中的指针相似

•C 语言中的内存地址可以通过例如 address++ 这样的指针运算进行操作,但是在 Go 里面不允许这种不安全操作。

•和结构体一样,可以把 & 放在数组的复合字面值前面来创建指向数组的指针。

数组在执行索引或切片操作时会自动解引用

•与 C 语言不一样,Go 里面数组和指针式两种完全独立的类型。

•Slice 和 map 的复合字面值前面也可以放置 & 操作符,但是 Go 并没有为它们提供自动解引用的功能。

•Go 语言在变量通过点标记法进行调用的时候,自动使用 & 取得变量的内存地址。

内部指针

•它用于确定结构体中指定字段的内存地址。

•& 操作符不仅可以获得结构体的内存地址,还可以获得结构体中指定字段的内存地址。

隐式的指针

•map 就是一种隐式指针。

slice 指向数组

•每个 slice 内部都会被表示为一个包含 3 个元素的结构,它们分别指向:

•数组的指针

•slice 的容量

•slice 的长度

•当 slice 被直接传递至函数或方法时,slice 的内部指针就可以对底层数据进行修改。

指向 slice 的显式指针的唯一作用就是修改 slice 本身:slice 的长度、容量以及起始偏移量。

nil

nil即为其他语言中的NULL

nil slice

•如果 slice 在声明之后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值就是 nil。

•幸运的是,range、len、append 等内置函数都可以正常处理值为 nil 的 slice。

•虽然空 slice 和值为 nil 的 slice 并不相等,但它们通常可以替换使用。

nil map

•和 slice 一样,如果 map 在声明后没有使用复合字面值或内置的 make 函数进行初始化,那么它的值将会是默认的 nil

nil 接口

•声明为接口类型的变量在未被赋值时,它的零值是 nil。

•对于一个未被赋值的接口变量来说,它的接口类型和值都是 nil,并且变量本身也等于 nil。

•当接口类型的变量被赋值后,接口就会在内部指向该变量的类型和值。

•在 Go 中,接口类型的变量只有在类型和值都为 nil 时才等于 nil。

•即使接口变量的值仍为 nil,但只要它的类型不是 nil,那么该变量就不等于 nil。

处理错误

defer 关键字

•使用 defer 关键字,Go 可以确保所有 deferred 的动作可以在函数返回前执行。

•可以 defer 任意的函数和方法。

•defer 并不是专门做错误处理的。

•defer 可以消除必须时刻惦记执行资源释放的负担

New error

•errors 包里有一个构造用 New函数,它接收 string 作为参数用来表示错误信息。该函数返回 error 类型。

自定义错误类型

•error 类型是一个内置的接口:任何类型只要实现了返回 string 的 Error() 方法就满足了该接口。

•可以创建新的错误类型。

•按照惯例,自定义错误类型的名字应以 Error 结尾。

类型断言

•使用类型断言,你可以把接口类型转化成底层的具体类型。

err.(SudokuRrror)

如何 panic

•Go 里有一个和其他语言异常类似的机制:panic,但它很少出现。

#创建panic
panic("I forgot my towel")

错误值、panic、os.Exit ?

•通常,更推荐使用错误值,其次才是 panic。

•panic 比 os.Exit 更好:panic 后会执行所有 defer 的动作,而 os.Exit 则不会。

recover函数

•为了防止 panic 导致程序崩溃,Go 提供了 recover 函数。

•defer 的动作会在函数返回前执行,即使发生了 panic。

•但如果 defer 的函数调用了 recover,panic 就会停止,程序将继续运行。

Go语言学习(六)

goroutine 和并发(concurrent)

goroutine

•在 Go 中,独立的任务叫做 goroutine

•虽然 goroutine 与其它语言中的协程、进程、线程都有相似之处,但 goroutine 和它们并不完全相同

•Goroutine 创建效率非常高

•Go 能直截了当的协同多个并发(concurrent)操作

•在某些语言中,将顺序式代码转化为并发式代码需要做大量修改

•在 Go 里,无需修改现有顺序式的代码,就可以通过 goroutine 以并发的方式运行任意数量的任务。

只需在调用前面加一个 go 关键字即可启动goroutine

•每次使用 go 关键字都会产生一个新的 goroutine。

•表面上看,goroutine 似乎在同时运行,但由于计算机处理单元有限,其实技术上来说,这些 goroutine 不是真的在同时运行。

•计算机处理器会使用“分时”技术,在多个 goroutine 上轮流花费一些时间。

•在使用 goroutine 时,各个 goroutine 的执行顺序无法确定。

func main() {
	for i:=0;i<5;i++ {
		go sleepyGopher(i)
	}
	time.Sleep(4*time.Second)
}

func sleepyGopher(id int){
	time.Sleep(3*time.Second)
	fmt.Println("... snore ...",id)
}

通道 channel

•通道(channel)可以在多个 goroutine 之间安全的传值。

•通道可以用作变量、函数参数、结构体字段…

•创建通道用 make 函数,并指定其传输数据的类型

• c := make(chan int)

通道channel发送,接受

•使用左箭头操作符 <- 向通道发送值 或 从通道接收值

•向通道发送值:c <- 99

•从通道接收值:r := <- c

•发送操作会等待直到另一个 goroutine 尝试对该通道进行接收操作为止。

•执行发送操作的 goroutine 在等待期间将无法执行其它操作

•未在等待通道操作的 goroutine 让然可以继续自由的运行

•执行接收操作的 goroutine 将等待直到另一个 goroutine 尝试向该通道进行发送操作为止。

使用 select 处理多个通道

•等待不同类型的值。

•time.After 函数,返回一个通道,该通道在指定时间后会接收到一个值(发送该值的 goroutine 是 Go 运行时的一部分)。

•select 和 switch 有点像。

•该语句包含的每个 case 都持有一个通道,用来发送或接收数据。

•select 会等待直到某个 case 分支的操作就绪,然后就会执行该 case 分支。

•注意:即使已经停止等待 goroutine,但只要 main 函数还没返回,仍在运行的 goroutine 将会继续占用内存。

•select 语句在不包含任何 case 的情况下将永远等下去。

nil 通道

•如果不使用 make 初始化通道,那么通道变量的值就是 nil(零值)

•对 nil 通道进行发送或接收不会引起 panic,但会导致永久阻塞。

•对 nil 通道执行 close 函数,那么会引起 panic

•nil 通道的用处:

•对于包含 select 语句的循环,如果不希望每次循环都等待 select 所涉及的所有通道,那么可以先将某些通道设为 nil,等到发送值准备就绪之后,再将通道变成一个非 nil 值并执行发送操作。

阻塞和死锁

•当 goroutine 在等待通道的发送或接收时,我们就说它被阻塞了。

•除了 goroutine 本身占用少量的内存外,被阻塞的 goroutine 并不消耗任何其它资源。

•goroutine 静静的停在那里,等待导致其阻塞的事情来解除阻塞。

•当一个或多个 goroutine 因为某些永远无法发生的事情被阻塞时,我们称这种情况为死锁。而出现死锁的程序通常会崩溃或挂起。

close函数

•Go 允许在没有值可供发送的情况下通过 close 函数关闭通道

•例如 close(c)

•通道被关闭后无法写入任何值,如果尝试写入将引发 panic。

•尝试读取被关闭的通道会获得与通道类型对应的零值。

•注意:如果循环里读取一个已关闭的通道,并没检查通道是否关闭,那么该循环可能会一直运转下去,耗费大量 CPU 时间

•执行以下代码可得知通道是否被关闭:

•v, ok := <- c


文章作者: wck
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wck !
评论
  目录