这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
路漫漫其修远兮~
我们一起从 0 开始学习 Go 语言~
不出意外今天的内容包括两部分:
- Go 基本语法
- Go 的一些标准库
Go 基本语法
首先,每一个 .go
文件,第一行都有 package main
这句话表示这个文件属于 main
包的一部分,而 main
包是程序的入口包
//导入标准库中的一些包
import (
"fmt" //往屏幕输入输出字符串、格式化字符串
"math"
)
(1)声明一个变量
定义一个变量,一般用var 变量名 变量类型
指定变量类型
var b, c int = 1, 2
或者自动推断类型:
var a = "initial"
不用 var
的声明一个变量
f := float32(e)
常量的声明
const s string = "constant"
常量可以没有确定的类型,根据上下文确定类型
const h = 500000000
const i = 3e20 / h
(2)for 循环
golang 中没有 while
和 do…while
循环只有for
循环
我们看一下 for 循环的用法
for j := 7; j < 9; j++ {
fmt.Println(j)
}
可以用break
结束循环
for {
fmt.Println("loop")
break
}
用continue
继续循环
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for
后面不跟小括号,跟大括号,且大括号后面要换行
(3)if 语句
用法:if 布尔条件语句 {}
示例:
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if
后面也可以接赋值语句,需要接 ;
后加布尔条件语句及{}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
(4)switch 语句`
golang
中的 switch
语句更加强大,可以用接字符串、结构体
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
switch
甚至可以取代任意的 if …… else
语句
例如:
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
switch
后不加任何变量,而在case
中写任意的分支,比多个if else
更加清晰
(5)数组 array
golang
中的数组
是长度固定的元素序列
声明一个数组 var 数组名 [数组大小]数组元素类型
var a [5]int
b := [5]int{1, 2, 3, 4, 5}//声明一个数组,且赋值
fmt.Println(b)
存取和读取数组中特定索引下的值
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
声明一个二维数组,并赋值
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
(6)切片 slice
因为数组长度是固定,因此我们在 go 语言中更常用的是切片 slice
切片 slice
是一个可变长度的数组,支持任意时刻更改其大小,以及更多的操作
创建切片,make([]元素类型,切片长度)
s := make([]string, 3) //创建切片
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
append
向切片中追加元素
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
拷贝切片
,即将一个切片的值复制到另一个切片上
s := []string{"a", "b", "c", "d", "e", "f"}
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s) //拷贝切片的数值
fmt.Println(c) // [a b c d e f]
取出slice
特定索引位置的值
s := []string{"a", "b", "c", "d", "e", "f"}
fmt.Println(s) // [a b c d e f]
//取出前闭后开的切片元素
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
但是 slice
不支持负数索引
,因此需要用 len
先取出切片的长度,再做一些运算
(7)map
map
类似于其他程序语言中的哈希
或者字典
,但是是非常常用的数据结构
创建一个空 map
, make(map[key的类型]value的类型)
m := make(map[string]int)
m["one"] = 1 //写入元素
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0,不存在的key值,返回默认的值 0
读取map
中的元素时,用一个ok (布尔值)
,检测这个key
是否存在
不存在 ok
返回 false
,数字返回 0
,字符串 返回 空
var m = map[string]int{"one": 1, "two": 2}
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
删除key-value
m := map[string]int{"one": 1, "two": 2}
delete(m, "one")
fmt.Println(m) // map[two:2]
map
是完全无序的,因此遍历的时候不会按照插入顺序输出
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3) //map[one:1 two:2] map[one:1 two:2]
(8)range
对于一个slice
和map
可以用range
快速遍历,使得代码更加简洁
对于数组/切片
会返回两个值,第一个是索引,第二个是对应索引的值,如果不需要索引的话,可以用下划线 _
代替 去做for
循环
nums := []int{2, 3, 4}//nums是slice
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
对 map
进行 range
的话,第一个值是 key
,第二个是 value
m := map[string]string{"a": "A", "b": "B"} //m的类型是map
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
只对 map
range
Key
for k := range m {
fmt.Println("key", k) // key a; key b
}
只对map
range
value
for _, v := range m {
fmt.Println("value", v) //value A; value B
}
(9)定义一个函数
定义一个函数时:
func 函数名(接入的变量名 变量类型) 返回的变量类型{}
可以看到变量类型是后置的
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
支持返回多个值,需要用括号括起来,第一个是真正返回的,第二个是错误信息
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
调用函数时
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
(10)指针 point
感觉每次涉及到指针的知识点,都比较难以理解
指针
:对传入的参数进行修改
把 n
写成一个指针类型:(n *变量类型)
之后,对指针变量进行运算的时候,都要在前面加 *
而调用的时候需要在变量名前加一个&
才能编译通过
func add2ptr(n *int) {
*n += 2 //运算时也需要加一个星号
}
看一下用了指针后的不同:
// 无效,是copy类型的+2
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2 //运算时也需要加一个星号
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
//为了类型匹配,调用的时候需要加一个&才能编译通过
add2ptr(&n)
fmt.Println(n) // 7
}
(11)结构体 struct
结构体
:带类型的字段的集合
type user struct {
name string
password string
}
用.
变量名进行读取和写入
var d user
d.name = "wang"
d.password = "1024"
如果没有对结构体
初始化字段的话,会将字段值初始化成一个空值
,数字为 0
,字符串为空字符串
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"} //如果指定字段的话,可以只指定一个值
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
// 结构体也可以作为函数的参数
func checkPassword(u user, password string) bool {
return u.password == password
}
// 有指针和非指针两种用法
// 用指针的话可以进行结构体的修改,也能避免大结构体copy的开销
func checkPassword2(u *user, password string) bool {
return u.password == password
}
(12)结构体方法
结构体方法
:类成员函数
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
// 带指针可以对结构体进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
Go 的一些标准库
(1)错误 error
错误的处理需要引入 errors
包
import (
"errors"
)
错误的处理(函数的声明中,变量类型在变量名后面)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
(2)字符串 string
导入strings
import (
"strings"
)
字符串的拼接
g := a + "foo"
判断字符串是否包含另一个字符串
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
字符串计数
a := "hello"
fmt.Println(strings.Count(a, "l")) // 2
查找某个字符串的位置
a := "hello"
fmt.Println(strings.Index(a, "ll")) // 2
连接多个字符串
a := "hello"
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
重复多个字符串
a := "hello"
fmt.Println(strings.Repeat(a, 2)) // hellohello
其他用法
a := "hello"
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) //获取字符串长度,一个中文对应三个字符串 // 5
b := "你好"
fmt.Println(len(b)) // 6
(3)fmt
fmt
对输出格式进行控制,经常用到
导入 fmt
import (
"fmt"
)
换行输出
fmt.Println(s, n)
fmt.Println(p)
按特定格式输出
s := "hello"
n := 123
p := point{1, 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
//结构体整个构造的类型名称以及字段名称和值
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
(16)json
导入 json
库
import (
"encoding/json"
)
具体用法:
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
(17)time
导入 time
import (
"time"
)
具体用法:
func main() {
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
//格式化一个时间到一个时间字符串
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
//对两个时间做减法,得到一个时间段
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1648738080获取时间戳
}
(18)strconv
字符串和数字之间的转换
导入 strconv
import (
"strconv"
)
具体用法:
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
//字符串,进制,返回64进制的整数
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
(19)env
进程在执行时的命令化参数
导入标准库
import (
"os"
"os/exec"
)
具体用法:
func main() {
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
//获取环境变量
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
//写入环境变量
fmt.Println(os.Setenv("AA", "BB"))
//快速启动子进程,并获取输入输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
把学习过程记录下来,以后也方便自己查阅,今天的学习就先到这里~