一把LOL的时间我入门了Go语言

走进 Go 语言~

前言:

Go 语言是由 Google 公司推出的一款新的编程语言,作为谷歌的亲儿子,发展势头迅猛,各个大厂目前都在积极推进 Go 语言的使用。Go 是云计算、云原生、区块链等众多前沿领域的首推语言,目前流行的 Docker、Kubernetes 等开源项目也都是基于 Go 语言的。

一把LOL的时间我入门了Go语言_第1张图片

Why GO?用 Go 的好处:

  • Go 语言语法简单,学习曲线平缓,相比较 Java 和 C++ 等语言动辄半年的学习周期,Go 语言只需要几周的时间就可以上手做项目了。
  • 作为大厂的流行语言,Go 语言有着丰富的标准库和完善的工具链。
  • Go 语言具有高性能、高并发的特点,静态链接、编译快速。同时具备像 Java 一样的垃圾回收机制,让我们能专注于业务逻辑。

既然 Go 语言这么牛,当然要快快学起来鸭!


搭建 GO 开发环境的正确姿势

1. 安装 Golang

浏览器访问 Golang 官网 (https://go.dev) 按照官网提示下载并安装 Go 语言环境。安装过程非常简单,无脑点击下一步即可。

一把LOL的时间我入门了Go语言_第2张图片

PS:

  • 如果官方网站打不开,还可以通过 Golang 中国镜像下载:Go下载 - Golang中文社区。
  • 按照 七牛云 - Goproxy.cn 网站配置 Go 模块代理,可以大大加快下载第三方包的速度。
2. 挑选一款合适的 IDE
  • GoLand:

一把LOL的时间我入门了Go语言_第3张图片

提到 IDE 就离不开大名鼎鼎的 JetBrains 了,如果你之前使用过它家出品的 Java 集成开发环境 IDEA,那么我强烈推荐尝试一下同样来自它家的 GoLand 作为 Go 语言 IDE。

GoLand 传送门:GoLand by JetBrains: More than just a Go IDE

  • VS Code:

一把LOL的时间我入门了Go语言_第4张图片

此外,代码编辑器 VS Code 安装 Go 语言扩展以后也可以很好的支持 Go 语言开发,如果你喜欢更加轻量的开发环境,VS Code 也是一个不错的选择。

VS Code 传送门:Visual Studio Code - Code Editing. Redefined


第一个 Go 程序 - Hello World

现在让我们通过 Hello World 这个小例子来粗浅地了解一下 Go 语言。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello world")
}

package main 表示这个文件属于 main 包的一部分,main 包是程序的入口包。import 用来导入包依赖,这里导入了标准库中的 fmt 包,fmt 包用来格式化输入输出。紧接着是 func main() 函数中的语句,调用了 fmt 包下的 Println() 方法在屏幕上打印 “Hello World”。

细心的你此时还会注意到 Go 的语法有点像 Python,在每条语句的末尾是不需要写分号 ; 的。

如果你使用的是 VS Code,可以在终端通过 go run 指令运行该程序:

$ go run example/01-hello/main.go 
hello world

需要编译成可执行的二进制文件,可以使用 go build 指令:

$ go build example/01-hello/main.go 
$ ./main 
hello world

Golang 基础语法

1. 变量 & 常量:

类似于 Python,Golang 不需要我们声明具体的变量或者常量类型,Golang 会根据上下文自动推导类型。

变量的声明主要有两种方式,第一种方式是 var + 变量名 = 值,或者我们可以直接 变量名 := 值 的方式来声明一个变量。(如果有需要,也可以指定变量类型)

没有进行赋值,但是指明了是什么类型,Golang 会自动加上空值。

var a = "initial"    // 字符串变量
var b, c int = 1, 2    // 多变量赋值并指定为整数类型
var d = true    // 布尔类型变量

var e float64    // 类型为64位浮点数,一个空的变量

f := float32(e)    // 强制转换类型 64位浮点->32位浮点
g := a + "foo"    // 字符串拼接

需要注意的是 := 这种方式是省略了 var,但是这种方式能对新的变量使用,对已经声明的变量使用会报错。

常量的声明只需要把 var 改成 const 即可。

const s string = "constant"
const h = 500000000
const i = 3e20 / h
2. 判断语句 - if else:

区别于 C 和 Java 语言,Golang 中的 if else 不需要加上括号 (),如果加上括号你的编辑器在保存时会自动帮你去掉括号。同时 if else 的花括号 {} 不可以省略,即使后面只跟着一条语句。

if 7%2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 is odd")
}

if 8%4 == 0 {
    fmt.Println("8 is divisible by 4")
}

还可以在 if 表达式中声明变量,这个变量的范围只在 if 中能够使用。

if score:=60; score>=60 { 
    fmt.Println("及格了") 
}
3. 循环:

Golang 中没有 whiledo-while 循环,只能使用 for 循环。

for 可以像 C 语言的语法一样后面跟着三条语句,并且任意一条都可以省略,也后面可以什么都不写,表示一个死循环。同样的,for 循环也支持 continuebreak 操作。

下面是一个 for 循环示例:

package main

import "fmt"

func main() {

    i := 1
    for {
	fmt.Println("loop")
	break
    }
    for j := 7; j < 9; j++ {
	fmt.Println(j)
    }

    for n := 0; n < 5; n++ {
	if n%2 == 0 {
            continue
	}
	fmt.Println(n)
    }
    for i <= 3 {
	fmt.Println(i)
	i = i + 1
    }
}
4. switch 语句:

在 C++ 和 Java 语言中,switch 语句如果不显示的加上 break 语句,会从第一个匹配的 case 开始跑完下面所有的 case,而 Golang 中的 switch 是不需要加 break 的。

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")
}

比如上面的代码执行完 case 2: 以后会直接跳出 switch 语句。

Golang 中的 switch 语句不仅支持数字,还可以使用字符串或者结构体,甚至用 switch 来取代任意的 if-else 语句。

// switch 替代 if-else 用来判断当前是上午还是下午
t := time.Now()
switch {
    case t.Hour() < 12:
	fmt.Println("It's before noon")
    default:
	fmt.Println("It's after noon")
}
5. 数组 - array:

Golang 中使用 [数组长度]类型 表示一个数组,可以直接按照声明变量的方式来声明一个数组。

package main

import "fmt"

func main() {

    var a [5]int    // 声明一个长度为5的整形数组
    a[4] = 100    // 修改指定下标位置的值
    fmt.Println("get:", a[2])
    fmt.Println("len:", len(a))

    b := [5]int{1, 2, 3, 4, 5}    // 声明数组的同时初始化复制
    fmt.Println(b)

    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)
}

运行上面的示例得到的结果如下:

$ go run example/06-array/main.go 
get: 0
len: 5
[1 2 3 4 5]
2d:  [[0 1 2] [1 2 3]]
6. 切片 - slice:

Go 语言切片是对数组的抽象。数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型 - 切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

可以声明一个未指定大小的数组来定义切片:var identifier []type,或者通过 make() 函数创建切片。

// 声明切片的同时进行初始化赋值
good := []string{"g", "o", "o", "d"}

// 使用make函数创建一个初始长度为3的切片
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"

make([]T, len, capacity)len 是数组的长度并且也是切片的初始长度,capacity 为可选参数,用来指定切片容量。

使用 append() 函数在切片中追加新的元素。由于切片的底层还是数组,它包含了数组长度、容量和一个指向数组的指针,容量不够时会进行扩容并返回新的 slice,因此使用 append() 函数还需要将结果赋值给原变量。

s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)    // [a b c d e f]

copy() 函数可以在两个 slice 之间拷贝数据:

c := make([]string, len(s))
copy(c, s)
fmt.Println(c)    // [a b c d e f]

类似于 Python,Golang 也支持切片操作:

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]

s[2:5] 表示取出切片中第 2 个位置到第 5 个位置的元素(不包括第 5 个位置)。

7. 集合 - map:

map 是一种无序的键值对的集合。map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

map 类似于 C++ 或是 Python 中的哈希与字典,不过 Go 中的 map 是完全无序的,遍历的顺序与插入顺序和字典序都没有关系。

可以使用内建函数 make() 也可以使用 map 关键字来定义 map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就会创建一个 nil mapnil map 不能用来存放键值对。

下面是一个 map 使用示例:

package main

import "fmt"

func main() {
    m := make(map[string]int)
    m["one"] = 1
    m["two"] = 2
    fmt.Println(m)
    fmt.Println(len(m))
    fmt.Println(m["one"])
    fmt.Println(m["unknow"])

    // 获取k-v对时可以在后面加一个ok来检查该key是否存在:
    r, ok := m["unknow"]
    fmt.Println(r, ok)

    // 删除指定的k-v对:
    delete(m, "one")

    // 创建map同时进行初始化:
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3)
}

运行结果如下:

$ go run example/07-slice/main.go 
get: c
len: 3
[a b c d e f]
[a b c d e f]
[c d e]
[a b c d e]
[c d e f]
[g o o d]
8. 语言范围 - range:

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在 map 中返回 key-value 对。

range 使得我们可以进行快速遍历,让代码更加简洁,下面是 range 的使用示例:

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}
    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

    m := map[string]string{"a": "A", "b": "B"}
    for k, v := range m {
	fmt.Println(k, v)    // b 8; a A
    }
    for k := range m {
	fmt.Println("key", k)    // key a; key b
    }
}

for i, num := range nums 中的 i 是切片 nums 的索引下标,numnums 中对于下标的值,如果不需要使用下标,可以使用下划线 _ 代替。

for k, v := range m 遍历集合 map 中的键值对,第一个 k 是键,第二个 v 是值。

运行上面的示例得到的结果如下:

$ go run example/09-range/main.go 
index: 0 num: 2
9
a A
b B
key a
key b
9. 函数:

Golang 中使用 func 函数名(参数列表) 返回值类型 {} 来声明一个函数。

默认情况下,Go 语言使用的是值传递,函数在调用时参数是拷贝复制的,即在调用过程中不会影响到实际参数。

Golang 和其他语言的函数有很大的不同,Go 原生支持在函数中返回多个值。在实际的开发中,函数一般都会返回多个值,第一个值是我们真正要返回的结果,第二个值是错误信息。

package main

import "fmt"

// 定义函数:返回两整形相加的结果
func add(a int, b int) int {
    return a + b
}
// 参数类型相同可以简写:
func add2(a, b int) int {
    return a + b
}

// 返回多个值的函数,第一个返回值v,第二个返回值ok
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)

    v, ok := exists(map[string]string{"a": "A"}, "a")
    fmt.Println(v, ok)
}

运行上面的示例得到的结果如下:

$ go run example/10-func/main.go 
3
A true
10. 指针 - point:

和 C/C++ 一样,Golang 也初步支持了指针,相比于 C++,Go 的指针只支持有限的操作,通常用来修改参数。

一个指针变量指向了一个值的内存地址,声明格式:var var_name *var-type

var ip *int        /* 指向整型 */
var fp *float32    /* 指向浮点型 */

下面是一个指针的使用示例:

package main

import "fmt"

func add2(n int) {
    n += 2
}

func add2ptr(n *int) {
    *n += 2
}

func main() {
    n := 5
    add2(n)
    fmt.Println(n)
    add2ptr(&n)
    fmt.Println(n)
}

由于 Golang 的函数参数是原来的拷贝,在 add2(n int) 函数中对变量 n 的自增操作并不会生效。要想自增的效果作用在变量 n 上,add2ptr(n *int) 函数中使用了指针类型作为参数,调用时为了确保类型匹配,入参要加上 & 符表示取变量 n 的地址。

11. 结构体 - struct:

结构体是带类型的字段的集合,使用 type 结构体名 struct{} 定义一个结构体,使用点 . 来访问结构体中的字段。

下面是结构体的使用示例:

package main

import "fmt"

// 结构体定义
type user struct {
    name     string
    password string
}

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
}

// 使用指针作为参数,避免拷贝过程的开销
func checkPassword2(u *user, password string) bool {
    return u.password == password
}

运行上面的代码得到的结果如下:

$ go run example/12-struct/main.go 
{wang 1024} {wang 1024} {wang 1024} {wang 1024}
false
false
12. 结构体方法 - struct method:

结构体方法类似于 Java 中的类成员函数,我们可以将上一个例子中的 checkPassword() 从一个普通函数改写成结构体方法,并使用 . 符号来调用它。

改写方法如下:

package main

import "fmt"

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
}
13. 错误处理:

Golang 中的错误处理习惯使用一个单独的返回值来处理错误信息。这种方式可以很清晰的知道哪个函数出现了错误,并且可以使用 if-else 对错误进行处理。

下面是一个错误处理示例:

package main

import (
    "errors"
    "fmt"
)

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)
    }
}

在函数的返回值类型中加一个 error,代表这个函数可能会返回错误,调用函数对返回的 error 信息进行判断就可以确定函数是否出错。

该示例的运行结果如下:

$ go run example/14-error/main.go 
wang
not found

Go 中的常用标准库

Golang 提供了丰富的标准库供我们使用,使用时只需在 import() 中导入对应的包即可。

1. 字符串 - strings:

标准库中的 strings 包提供了非常多的字符串工具函数,常用的方法参考下面的表格。

strings 包下的函数 描述
Contains() 判断一个字符串中是否包含另一个字符串
Count() 统计指定子串在字符串中的出现次数
Index() 查找指定子串的位置
Join() 对多个字符串进行拼接
Repeat() 将字符串重复指定次数
Replace() 替换字符串中的内容
Split() 将字符串按照给定的字符进行分割

此外还可以使用内置函数 len() 来获取字符串的长度,对于中文,一个中文可能对应多个字符。

下面是 strings 包下函数的使用示例:

package main

import (
    "fmt"
    "strings"
)

func main() {
    a := "hello"
    fmt.Println(strings.Contains(a, "ll"))                // true
    fmt.Println(strings.Count(a, "l"))                    // 2
    fmt.Println(strings.HasPrefix(a, "he"))               // true
    fmt.Println(strings.HasSuffix(a, "llo"))              // true
    fmt.Println(strings.Index(a, "ll"))                   // 2
    fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
    fmt.Println(strings.Repeat(a, 2))                     // hellohello
    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
}
2. Json 处理:

"encoding/json" 包提供了 Json 的处理工具,可以实现结构体与 Json 格式的相互转换。

"json" 包中的 Marshal() 方法用来将结构体序列化成 Json 字符串,该方法返回的是一个 byte 数组,要想打印成字符串需要强制类型转换。使用 Unmarshal() 方法可以将 Json 反序列赋值给一个变量。

下面是 Json 工具的使用示例:

package main

import (
    "encoding/json"
    "fmt"
)

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"}}
}

在上面的示例中,序列化后的字符串默认的风格是大写字母开头,如果需要改成小写风格,可以在结构体的字段后面加上一个 json tag,比如上面代码中的 Age 字段后面指定了 json 字符串的字段名。

3. 时间处理:

Golang 中最常用的是使用 time.Now() 获取当前的时间,也可以使用 time.Date() 方法构造指定时期的时间。

下面是 time 包下常用方法的使用示例:

package main

import (
    "fmt"
    "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
}
4. 数字解析:

"strconv" 包中封装了字符串与数字的转换工具。

ParseInt() 方法接收三个参数,第一个参数是指定的字符串,第二个参数是进制(传入 0 会自动推测字符串表示的进制),第三个参数是转换精度。ParseFloat() 方法接收两个参数,字符串与转换精度,该方法返回转换后的浮点数。

Atoi() 方法只接收一个字符串参数,用来快速地将字符串转成十进制地数字。同样的也可以使用 Itoa() 方法将一个数字转换成对应的字符串。

下面是 "strconv" 包中函数的使用示例:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f)    // 1.234

    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
}

你可能感兴趣的:(golang,开发语言)