Go 语法基础 ¶
约 3054 个字 158 行代码 预计阅读时间 12 分钟
Abstract
归档一些学习 Go 的一些语法,和其他编程语言无太大区别,入门还是很简单的
为什么学习 Go ¶
- 硬件限制:摩尔定律已然失效
- Go 为并发而生:在多核并发上拥有原生的设计优势,从底层原生并发
- Go 性能强悍:同 C/C++ 一样,Go 也是编译型语言,执行效率更高,性能更好
- Go 简单易学:语法简洁、代码风格统一、开发效率高
怎么装 Go ¶
下载 ¶
- Go 官网下载地址
- Go 官方镜像站(推荐)
- Windows 平台和 Mac 平台推荐下载可执行文件版,Linux 平台下载压缩文件版。
安装 ¶
- Windows:以 64 位 Win10 举例,将选好安装包下载到本地,双击一直 next,注意要指定一个 Go 安装目录,这很重要
- Linux:
# 在版本选择好下载文件后
wget https://dl.google.com/go/go1.14.1.linux-amd64.tar.gz
# 将下载好的文件解压到 `/usr/local` 目录中,没有权限加上 `sudo`
tar -zxvf go1.14.1.linux-amd64.tar.gz -C /usr/local
# 配置环境变量,在 `~/.profile` 中添加以下代码,并用 `source` 重新加载
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
/usr/local/go
目录下
GOROOT 和 GOPATH ¶
GOROOT
和GOPATH
都是环境变量,其中GOROOT
是我们安装 go 开发包的路径,而从Go 1.8
版本开始,Go 开发包在安装完成后会为GOPATH
设置一个默认目录,并且在Go1.14
及之后的版本中启用了Go Module
模式之后,不一定非要将代码写到GOPATH
目录下,所以也就不需要我们再自己配置GOPATH
了,使用默认的即可。
- 查看
GOPATH
路径:go env
GOPROXY
Go1.14
版本之后,都推荐使用go mod
模式来管理依赖环境了,也不再强制我们把代码必须写在GOPATH
下面的 src 目录了,你可以在你电脑的任意位置编写 go 代码
默认GoPROXY
配置是:GOPROXY
=https://proxy.golang.org,direct,由于国内访问不到 https://proxy.golang.org,所以我们需要换一个 PROXY,这里推荐使用 https://goproxy.io 或 https://goproxy.cn。
可以执行下面的命令修改 GOPROXY:
怎么编写、运行 Go ¶
任何编辑器都可以编写 Go(包括记事本
) ,这里推荐用 VsCode(需安装 Go 扩展) ,当然也可以用 Sublime,JB 的全家桶等等
go mod init¶
使用go module
模式新建项目时,我们需要通过go mod init
项目名命令对项目进行初始化,该命令会在项目根目录下生成go.mod
文件。执行如下命令
记得配置好 GOPROXY
编译、运行、安装 ¶
go build
: 命令表示将源代码编译成可执行文件。go run
:命令本质上是先在临时目录编译程序然后再执行。go install
:它先编译源代码得到可执行文件,然后将可执行文件移动到 GOPATH 的 bin 目录下。因为把GOPATH
下的bin
目录添加到了环境变量中,所以就可以在任意地方直接执行可执行文件。
go module¶
go module
是 Go1.13 版本之后。Go 语言默认的依赖管理工具。
- 设置好
GO111MODULE=on
就可以使用go module
- 使用
go module`` 管理依赖后会在项目根目录下生成两个文件
go.mod和
go.sum` go mod
命令:go mod download
下载依赖的 module 到本地 cache(默认为 $GOPATH/pkg/mod 目录)go mod edit
编辑 go.mod 文件go mod graph
打印模块依赖图go mod init
初始化当前文件夹 , 创建 go.mod 文件go mod tidy
增加缺少的 module,删除无用的 modulego mod vendor
将依赖复制到 vendor 下go mod verify
校验依赖go mod why
解释为什么需要依赖
- go.mod 文件记录了项目所有的依赖信息
- 在项目中执行
go get
命令可以下载依赖包,并且还可以指定下载的版本
变量和常量 ¶
- 变量
- 需要显式声明,并且在函数调用等情况下,编译器会检查其类型的正确性
var
声明 1 个或多个变量,Go 会自动推断已经有初始值的变量类型- 声明后却没有给出对应的初始值时,变量将会初始化为 零值 。 例如,int 的零值是 0
- := 语法是声明并初始化变量的简写(不能在函数外使用
) , 例如var f string = "short"
可以简写为f := "short"
- 在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量,一个下划线
_
表示
- 常量
- 支持字符、字符串、布尔和数值常量
- const 用于声明一个常量
- const 语句可以出现在任何 var 语句可以出现的地方
- iota 是常量计数器,在 const 关键字出现时会被重置为 0
条件语句 ¶
if/else 分支 ¶
- if-else if-else 结构
- Go 规定与 if 匹配的左括号 { 必须与 if 和表达式放在同行,其他位置会触发编译错误,同理 else
- 在条件语句之前可以有一个声明语句;在这里声明的变量可以在这个语句所有的条件分支中使用
- 在 Go 中,条件语句的圆括号不是必需的,但是花括号是必需的
- Go 没有三目运算符, 即使是基本的条件判断,依然需要使用完整的 if 语句
if-else
switch 分支 ¶
- 不带表达式的 switch 是实现 if/else 逻辑的另一种方式
- 每个 switch 只能有一个 default 分支
- 一个分支可以有多个值,多个 case 值中间使用英文逗号分隔
- fallthrough 语法可以执行满足条件的 case 的下一个 case,是为了兼容 C 语言中的 case 设计的
switch
循环 ¶
- 经典的初始 / 条件 / 后续 for 循环
- for 循环的初始语句可以被忽略,但是初始语句后的分号必须要写
- for 循环的初始语句和结束语句都可以省略
- for 循环可以通过 break、goto、return、panic 语句强制退出循环
- Go 语言中可以使用 for range 遍历数组、切片、字符串、map 及通道(channel
) 。 通过 for range 遍历的返回值有以下规律- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- 通道(channel)只返回通道内的值
for
数据类型 ¶
数字与运算 ¶
- 整型有 int8/16/32/64,包括对应的无符号整型
- 浮点型有 float32/64
- 复数有 complex64/128
- 运算:
- 和 C/C++ 无太大差别
布尔类型 ¶
- 通过 bool 来声明布尔型数据,只有 true 和 false
- 默认值为 false
- 不允许将整型强制转换为布尔型
- 布尔型无法参与数值运算,也无法与其他类型进行转换
字符串 ¶
- 字符串的内部实现使用 UTF-8 编码,字符串的值为双引号 (") 中的内容,可以在 Go 语言的源码中直接添加非 ASCII 码字符
- 要定义一个多行字符串时,就必须使用反引号字符,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出
- 常用方法
len(str)
:求长度+或fmt.Sprintf
:拼接字符串strings.Split
:分割strings.contains
:判断是否包含strings.HasPrefix,strings.HasSuffix
:前缀 / 后缀判断strings.Index(),strings.LastIndex()
:子串出现的位置strings.Join(a[]string, sep string)
:join 操作
byte 和 rune 类型 ¶
- 组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来
- Go 包括两种字符:
uint8
类型,或者叫byte
型,代表一个 ASCII 码字符rune
类型,代表一个UTF-8
字符
- 当需要处理中文、日文或者其他复合字符时,则需要用到
rune
类型。rune
类型实际是一个int32
- Go 使用了特殊的
rune
类型来处理Unicode
,让基于Unicode
的文本处理更为方便,也可以使用byte
型进行默认字符串处理,性能和扩展性都有照顾 - 字符串底层是一个 byte 数组,所以可以和
[]byte
类型相互转换 - 字符串是不能修改的,字符串是由
byte
字节组成,所以字符串的长度是byte
字节的长度 rune
类型用来表示utf8
字符,一个rune
字符由一个或多个byte
组成- 要修改字符串,需要先将其转换成
[]rune
或[]byte
,完成后再转换为string
byte and rune
数组 ¶
- 定义一个长度为 3 元素类型为 int 的数组 a:
var a [3]int
- 内置函数 len 可以返回数组的长度
- 初始化
- 可以使用初始化列表来初始化数组:
var num = [3]int{1, 2}
- 可以让编译器根据初始值的个数自行推断数组的长度,
var num = [...]{1, 2}
- 指定索引值来初始化数组,
num := [...]int{1: 1, 3: 5}
- 可以使用初始化列表来初始化数组:
- 遍历
for i := 0; i < len(a); i ++ {}
for index, value := range a {}
- 多维数组定义,
a := [2][3]int
- 多维数组只有第一层可以使用 ... 来让编译器推导数组长度,不支持内层使用
- 数组是值类型,赋值和传参会复制整个数组,因此改变副本的值,不会改变本身的值
[n]*T
表示指针数组,*[n]T
表示数组指针
切片 ¶
- 定义
- 切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容
- 切片声明:
var name []T
,name 表示变量名,T 表示切片中的元素类型 - 切片拥有自己的长度和容量,我们可以通过使用内置的 len() 函数求长度,使用内置的 cap() 函数求切片的容量
- 切片的底层就是数组,和 Python 一样,可以使用
b := a[1:3]
,表示从数组 a 中选出 索引值在 \([1,3)\) 中的元素组成切片 b - 完整的切片表达式:
a[low: high: max]
,得到的结果切片的容量设置为max - low
,low 可以省略,默认为 0 - 使用内置
make函数
构造切片,make([]T, size, cap)
,T 表示元素类型,size 表示元素数量,cap 表示切片容量 - 判断切片是否为空,请使用
len(s) == 0
,不要使用s == nil
- 切片不能直接比较
- 切片遍历和数组一样
- 可以用
append()
为切片添加元素s = append(s, 1,2,3)
,也可以添加另一个切片的元素s = append(s, s2...)
- 切片的扩容策略(略)
- 可以使用
copy()
复制切片,但切片是引用类型,所以两个切片都指向同一块内存地址,对一个切片的修改会影响另一个切片的内容 - 排序,需要
import sort
,排序时可自定义排序sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
Map¶
- 一种无序的
key-value
数据结构,在 Go 中 map 是引用类型,必须初始化才能使用 - 定义:
map[KeyType]ValueType
,KeyType 表示键的类型,ValueType 表示键对应的值的类型,x := map[int]int{1:3, 2:4}
或s := make([int]int, 2), s[1] = 3, s[2] = 4
- 判断键是否存在:
value, ok := map[key]
,查看 ok 是否为 true - 遍历:
for k, v := range map {}
,v 可省略,遍历元素的顺序与添加键值对的顺序无关,可以把 key 放入 slice 中排序,然后遍历 slice 就可以指定顺序 delete()
从 map 中删除一组键值对
函数 ¶
- 使用
func
来定义函数 - 通过函数名的方式调用函数
- 函数的参数中如果相邻变量的类型相同,则可以省略类型,
- 可变参数是指函数的参数数量不固定。Go 语言中的可变参数通过在参数名后加 ... 来标识,可变参数通常要作为最后一个参数
- 函数如果有多个返回值,必须使用 () 将所有返回值包裹
- 函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过 return 关键字返回
- 可以使用 type 关键字来定义一个函数类型
- 高阶函数
- 函数作为参数
- 函数作为返回值
- 可以定义匿名函数,
add := func(x, y int)
,多用于回调函数和闭包 - 闭包 = 函数 + 引用环境(略)
defer
语句,将其后面跟随的语句进行延迟处理,会被逆序处理- 使用 panic/recover 模式来处理错误,recover() 必须搭配 defer 使用,defer 一定要在可能引发 panic 的语句之前定义
func
func 函数名(参数)(返回值) {
函数体
}
func add(a, b int) int {
return x + b
}
func intSum2(x ...int) int {
fmt.Println(x) //x是一个切片
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}
func calc(x, y int) (int, int) {
reurn x + y, x - y
}
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的操作符")
return nil, err
}
}
指针 ¶
- 和 C 的区别不大,都是用
*
和&
来操作,*
表示取值操作符,&
表示取地址操作符 - new 和 make 分配内存
a = new(int)
,make 只用于 slice、map 以及 channel 的内存创建,返回的是引用类型- 而 new 用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
结构体 ¶
定义与初始化 ¶
- type 定义类型别名
- type + struct 定义结构体,type 类型名 struct { 字段名 字段类型 ...}
- 使用 var 实例化结构体
- 通过 . 访问结构体字段
- 初始化
- 没有初始化的结构体,其成员变量都是对应其类型的零值
- 使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值
- 结构体占用一块连续的内存,空结构体是不占用空间的
- 结构题没有构造函数,但是可以自己实现
构造函数
方法 ¶
- 方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver
) 。接收者的概念就类似于其他语言中的 this 或者 self,当然还可以有指针类型的接收者 - 方法与函数的区别是,函数不属于任何类型,方法属于特定的类型
其他 ¶
- 结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段
- 一个结构体中可以嵌套包含另一个结构体或结构体指针
- 通过结构体可以实现“继承”
- 结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)
- 结构体可以使用 JSON 序列化
接口 ¶
接口类型 ¶
- 接口是一种由程序员来定义的类型,一个接口类型就是一组方法的集合,它规定了需要实现的所有方法
- 每个接口类型由任意个方法签名组成
- 接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口
- 可以通过值接收者或指针接收者实现借口
类型与接口的关系 ¶
- 一个类型实现多个接口
- 多种类型实现同一接口
接口组合 ¶
- 接口与接口之间可以通过互相嵌套形成新的接口类型,例如 Go 标准库 io 源码中就有很多接口之间互相组合的示例
空接口 ¶
- 空接口是指没有定义任何方法的接口类型。因此任何类型都可以视为实现了空接口。也正是因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值
- 通常我们在使用空接口类型时不必使用 type 关键字声明,可以像下面的代码一样直接使用 interface{},
var x interface{}
- 空接口作为函数的参数,使用空接口实现可以接收任意类型的函数参数
- 空接口作为 map 的值,使用空接口实现可以保存任意值的字典
Error 接口 ¶
- Go 语言中把错误当成一种特殊的值来处理,不支持其他语言中使用 try/catch 捕获异常的方式
- Go 语言中使用一个名为 error 接口来表示错误类型
- 当一个函数或方法需要返回错误时,我们通常是把错误作为最后一个返回值,例如之前查询 map 中是否存在键值对
- 默认零值为 nil
- 使用 fmt.Errorf 才描述错误信息
- 可以对自定义结构体类型实现 error 接口
标准库 ¶
fmt¶
fmt 包实现了类似 C 语言 printf 和 scanf 的格式化 I/O。主要分为向外输出内容和获取输入内容两大部分
向外输出 ¶
Print()
:将内容输出到系统的标准输出Fprint()
:将内容输出到一个 io.Writer 接口类型的变量 w 中,我们通常用这个函数往文件中写入内容Sprint()
:把传入的数据生成并返回一个字符串Errorf()
:根据 format 参数生成格式化字符串并返回一个包含该字符串的错误
获取输入 ¶
Scan()
:标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符Scanf()
:标准输入扫描文本,根据 format 参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中Scanln()
:类似 Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置bufio.NewReader
:时候我们想完整获取输入的内容,而输入的内容可能包含空格,这种情况下可以使用 bufio 包来实现Fscan()系列
:类似于Scan()系列
,从 io.Reader 中读取数据Sscan()系列
:类似于Scan()系列
,从指定字符串中读取数据
log¶
Go 语言内置的 log 包实现了简单的日志服务
log
除了默认提供日志的时间信息,还可以通过配置 logger 来设置输出信息
文件操作 ¶
打开和关闭文件 ¶
os.Open()
函数能够打开一个文件,返回一个*File
和一个err
。对得到的文件实例调用close()
方法能够关闭文件
open and close
为了防止文件忘记关闭,我们通常使用 defer 注册文件关闭语句
读取文件 ¶
-
file.Read()
- 接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回 0 和 io.EOF
- 使用 for 循环读取文件中的所有数据
-
bufio 读取文件
bufio
是在file
的基础上封装了一层 API,支持更多的功能
- 读取整个文件
io/ioutil
包的ReadFile
方法能够读取完整的文件,只需要将文件名作为参数传入
文件写入操作 ¶
os.OpenFile()
函数能够以指定模式打开文件,从而实现文件写入相关功能Write
和WriteString
bufio.NewWriter
ioutil.WriteFile
strconv¶
strconv 包实现了基本数据类型与其字符串表示的转换
Atoi()¶
用于将字符串类型的整数转换为 int 类型
Itoa()¶
用于将 int 类型数据转换为对应的字符串表示
Parse 系列函数 ¶
Parse 类函数用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()
-
返回字符串表示的 bool 值
-
返回字符串表示的整数值
-
解析一个表示浮点数的字符串并返回其值
Format 系列函数 ¶
Format 系列函数实现了将给定类型数据格式化为 string 类型数据的功能
-
根据 b 的值返回”true”或”false”
-
返回 i 的 base 进制的字符串表示
-
函数将浮点数表示为字符串并返回
Referencce¶
创建日期: 2023年9月6日 10:51:03