这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
1. Go 语言结构
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
package main
//定义包名
import "fmt"
//导入包文件
func main() {
//func main() 是主函数,是一个可执行文件必须包含的,另外 { 不能单独放一行
fmt.Println("Hello, World!")
}
2. Go 语言基础语法
2.1 Go标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。
2.2 行分隔符
在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
2.3 字符串连接
Go 语言的字符串连接可以通过 + 实现:
2.4 关键字
下面列举了 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 |
2.5 格式化字符串
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
- Printf 根据格式化参数生成格式化的字符串并写入标准输出。
%v:可以表示字符串、数组、结构体
%+v:更加详细的结构
%#v:进一步详细
%.2f:保留两位小数的浮点数
3. Go 语言变量
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
声明变量的一般形式是使用 var 关键字:
var identifier type
可以一次声明多个变量:
var identifier1, identifier2 type
3.1 变量声明
第一种,指定变量类型,如果没有初始化,则变量默认为零值
零值就是变量没有做初始化时系统默认设置的值。
package main
import "fmt"
func main() {
// 声明一个变量并初始化
var a = "Flyone"
fmt.Println(a)
// 没有初始化就为零值
var b int
fmt.Println(b)
// bool 零值为 false
var c bool
fmt.Println(c)
}
输出结果为:
Flyone
0
false
不同数据类型零值不同
-
数值类型(包括complex64/128)为 0
-
布尔类型为 false
-
字符串为 ""(空字符串)
-
以下几种类型为 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是接口
第二种,根据值自行判定变量类型。
var v_name = value
第三种,如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误,格式: v_name := value
例如:
var intVal int
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
复制代码
直接使用下面的语句即可:
intVal := 1 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句
复制代码
intVal := 1 相等于:
var intVal int
intVal =1
3.2 多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
3.3 值类型和引用类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值
当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝
你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。
值类型变量的值存储在堆中。
3.4 匿名变量
空白标识符_也被用于抛弃值,如值 5 在 :_ , b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func 函数同时得到:val, err = Func(var1)。
3.5 iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b = iota
c = iota
)
iota 用法
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
4. Go 语言条件语句
| 语句 | 描述 |
|---|---|
| if 语句 | if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 |
| if...else 语句 | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 |
| if 嵌套语句 | 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 |
| switch 语句 | switch 语句用于基于不同条件执行不同动作。 |
| select 语句 | select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。 |
注意:Go 没有三目运算符,所以不支持 ? : 形式的条件判断。
4.1 if 语句
与java等语言不同,if 后面没有 ()
if 布尔表达式 {
//语句
}
4.2 if...else 语句
if 布尔表达式 {
//语句
} else {
//语句
}
4.3 if 嵌套语句
if 布尔表达式 {
if 布尔表达式{
}
}
4.4 switch 语句
- switch 语句中的变量可以是任何类型,可以是一个常量或者一个变量,在每个case后面可以跟多个值,用逗号隔开
- Go中的switch语句也支持在条件判断语句前执行简单的语句,这样就可以在一个switch语句中处理多个条件
//switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,
//如果我们需要执行后面的 case,可以使用 fallthrough 。
switch var1 {
case var1:
...
case var2:
...
default:
...
}
//Type Switch
switch x.(type) {
case type:
statement(s);
case type:
statement(s);
default(可选):
statement(s);
}
4.5 select 语句
select 是 Go 中的一个控制结构,类似于 switch 语句。
select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
select {
case <- channel1:
// 执行的代码
case value := <- channel2:
// 执行的代码
case channel3 <- value:
// 执行的代码
// 你可以定义任意数量的 case
default:
// 所有通道都没有准备好,执行的代码
}
5. Go 语言数组
Go 语言提供了数组类型的数据结构。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
声明数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var variable_name [SIZE] variable_type
以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32:
var balance [10] float32
6. Go 语言切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
6.1 定义切片
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
也可以指定容量,其中 capacity 为可选参数。
make([]T, length, capacity)
6.2 切片初始化
s :=[] int {1,2,3 }
直接初始化切片, [] 表示是切片类型, {1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
s := arr[:]
初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex]
默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1。
s :=make([]int,len,cap)
通过内置函数 make() 初始化切片s, []int 标识为其元素类型为 int 的切片。
6.3 len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
6.4 append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
7. Go 语言范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
以上代码中的 key 和 value 是可以省略。
如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
如果只想读取 value,格式如下:
for _, value := range oldMap
遍历简单的数组,2%d 的结果为索引对应的次方数
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
以上实例运行输出结果为:
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
for 循环的 range 格式可以省略 key 和 value,如下实例:
package main
import "fmt"
func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
// 读取 key 和 value
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
// 读取 key
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}
// 读取 value
for _, value := range map1 {
fmt.Printf("value is: %f\n", value)
}
}
8. Go 语言Map(集合)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
8.1 定义 Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */
var mapName map[type1]type2
/* 使用 make 函数 */
maName := make(map[type1]type2)
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
8.2 delete() 函数
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。
9. Go 语言指针
我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
以下实例演示了变量在内存中地址:
package main
import "fmt"
func main() {
var a int = 10
fmt.Printf("变量的地址: %x\n", &a )
}
9.1 如何使用指针
指针使用流程:
- 定义指针变量。
- 为指针变量赋值。
- 访问指针变量中指向地址的值。
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
以上实例执行输出结果为:
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20
9.2 Go 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
10. Go 语言结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:
- Title :标题
- Author : 作者
- Subject:学科
- ID:书籍ID
10.1 定义结构体
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
type name struct {
member 1
member 2
...
member n
}
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
name := structType {value1, value2...valuen}
或
name := structType { key1: value1, key2: value2..., keyn: value n}
10.2 访问结构体成员
如果要访问结构体成员,需要使用点号 . 操作符,格式为:
结构体.成员名
11. Go 错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
完整示例
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
执行以上程序,输出结果为:
100/10 = 10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0
12. Go 字符串操作
使用前需要import
import strings
方法
strings.Contains(a,"ll"):判断字符串是否包含另一个字符串strings.Count(a,"l"):字符串计数strings.HasPrefix(a,"he"):是否以字符串开头strings.HasSuffix(a,"llo"):是否以字符串结尾strings.Index(a,"ll"):查找某个字符串的位置strings.Join([]string{"he","llo"},"-"]):连接多个字符串strings.Repeat(a,2):重复多个字符串strings.Replace(a,"e","E",-1):替换字符串strings.Split("a-b-c","-"):分割字符串strings.Tolower(a):小写字符串astrings.ToUpper(a):大写字符串a
13. Go JSON处理
使用前需要import
import encoding/json
方法
对于一个结构体,如果要JSON处理,只需要保证结构体的每个字段的首字母大写,就能使用下面方法
json.Marshal(a):序列化
打印时需要强制转换为string,否则打印的是16进制的编码
json.Unmarshal(buf,&b):反序列化
14. Go 时间处理
使用前需要import
import time
方法
time.Now():获取当前时间time.Date(2022,3,27,1,25,36,0,time.UTC):构造一个时区的时间 ——2022-03-27 01:25:36 +0000 UTCt.Year():获取年份t.Mouth():获取月份t.Day():获取日t.Hour():获取小时t.Minute():获取分钟t.Second():获取秒t.Format(字符串示例):格式化时间t2.Sub(t):对两个时间做减法运算,得到的是时间段time.Parse(字符串示例,t):格式化时间t.Unix():获取时间戳
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
s := userInfo{Name: "Flyone", Age: 20, Hobby: []string{"Golang", "Java"}}
//通过json.Marshal序列化
buf, err := json.Marshal(s)
if err != nil {
panic(err)
}
//序列化后变成but数组,需要转换成字符串,不然会打印出16进制的编码
fmt.Println(buf)
//通过转换成字符串即可输出我们的内容
fmt.Println(string(buf))//{"Name":"Flyone","age":20,"Hobby":["Golang","Java"]}
buf, err = json.MarshalIndent(s, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
//反序列化到空变量b中
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b)
//输出结果
//main.userInfo{Name:"Flyone", Age:20, Hobby:[]string{"Golang","Java"}}
}
15. Go 数字解析
使用前需要import
import strconv
方法
strconv.ParseFloat("1.234",64):字符串转浮点数strconv.ParseInt("111",10,64):字符串转整型,第二个参数为进制(0-自动推测,10-10进制...),64位精度的整型strconv.ParseInt("0x1000",0,64):4096strconv.Atoi("123"):迅速转换,可以字符串和数字互相转换,参数不合法会返回错误
16. Go 进程信息
使用前需要import
import (
os
os/exec
)
方法
os.Args:进度执行的一些命令行参数os.Getenv("PATH"):获取环境变量os.Setenv("AA","BB"):写入环境变量exec.Command("grep","127.0.0.1","/etc/hosts").CombinedOutput:快速启动子进程并且获取其输入输出