Golang学习笔记

本文最后更新于:8 天前

学习笔记

Golang简介

摘录自菜鸟教程:Go 语言教程 | 菜鸟教程

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

特色

  • 简洁、快速、安全
  • 并行、有趣、开源
  • 内存管理、数组安全、编译迅速

用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

常用命令

标识符、关键字、命名规则

变量

常量

数据类型

布尔类型

数字类型

字符串

格式化输出

运算符

流程控制

if

if else

if else if

嵌套if

switch

for

for range

break

continue

goto

数组

切片(slice)

定义切片

Go 语言切片是对数组的抽象。

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

// 可以声明一个未指定大小的数组来定义切片,切片不需要说明长度
var identifier []type

// 未初始化的切片默认为nil, 长度为0,容量为0
func test6()  {
    var s1 []int
    fmt.Printf("len: %v cap: %v slice: %v", len(s1), cap(s1), s1)
    if s1 == nil {
        print("s1是空的")
    }
}

// 也可以使用make函数进行定义, 这里 len 是数组的长度并且也是切片的初始长度。
var slice1 []type = make([]type, len)
slice1 := make([]type, len) // 简写

func test2() { // make方式创建切片
    var s2 = make([]int, 2)
    fmt.Printf("s2: %v\n", s2)
}

// 可以指定容量 capacity 为可选参数
make([]T, length, capacity)

初始化切片

直接定义

s := []int{1, 2, 3}

使用数组初始化

arr := [...]int{1, 2, 3}
s1 := arr[:] // 使用整个数组作为切片

使用部分数组初始化(截取数组)

切片的底层就是一个数组,基于数组通过 切片表达式可以得到切片。表达式中 [A:B]包含下标为A的不包含下表为B的(左包含右不包含),得到切片的 长度=B-A,容量=切片底层数组的容量

func test5()  { // 创建切片
    h1 := []int{1,2,4,5,6,7,8,9,10}
    fmt.Printf("h1: %v\n", h1)
    h2 := h1[:] // 整个数组都为切片
    fmt.Printf("h2: %v\n", h2)
    h3 := h1[1:3] // 数组下标从1到2都为切片
    fmt.Printf("h3: %v\n", h3)
    h4 := h1[4:] // 数组下标从4到结尾都为切片
    fmt.Printf("h4: %v\n", h4)
    h5 := h1[:4] // 数组下标从0到下标3都为切片
    fmt.Printf("h5: %v\n", h5)
}

切片遍历

for循环

func test7() {
    s1 := []int{1, 2, 3, 4, 5, 6}
    for i := 0; i < len(s1); i++ {
        fmt.Printf("s1[%v]: %v\n", i, s1[i])
    }
}

for range 循环

func test8()  {
    s1 := []int{1, 2, 3}

    for i, v := range s1 {
        fmt.Printf("i: %v v: %v \n", i, v)
    }

    // 简写省略掉索引,索引使用匿名变量的方式
    for _, v := range s1 {
        fmt.Printf("v: %v\n", v)
    }
}

切片函数

len() 和 cap()

func test3() {
    var s1 = []int{1, 23}
    fmt.Printf("len:s1: %v\n", len(s1)) // 输出长度
    fmt.Printf("cap: s1: %v\n", cap(s1)) // 输出容量
}

append() 和 copy()

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。由于切片是 引用类型赋值的方式会修改原有内容。

切片是动态数组 append函数可用用来增加元素,同样可以根据切片特性使用 append函数删除元素,

// 创建空切片并添加元素
func test9()  {
    s1 := []int{}
    s1 = append(s1, 100)
    s1 = append(s1, 200)
    fmt.Printf("s1: %v\n", s1)
}

// 向切片中添加切片
func test10()  {
    s1 := []int{1, 2, 3, 4, 5, 6, 7}
    s1 = append(s1, s1...) // 追加整个切片
    fmt.Printf("s1: %v\n", s1)

    s1 = append(s1, s1[3:]...) // 追加从下标为3后的部分切片 ...是切片运算符
    fmt.Printf("s1: %v\n", s1)

    s1 = append(s1, s1[:5]...) // 追加从下标为0到下标为4的部分切片
    fmt.Printf("s1: %v\n", s1)
}


// 删除切片中指定位置的元素
func test11 () {
    s1 := []int{1, 2, 3, 4, 5, 6, 7}
    s1 = append(s1[:3], s1[4:]... )  // 删除索引为3位置的元素
    fmt.Printf("s1: %v\n", s1)
}

// 更新切片中指定位置的元素
func test12 () {
    s1 := []int{1, 2, 3, 4, 5, 6, 7}
    s1[1] = 100  // 将下标为1的元素更改为100
    fmt.Printf("s1: %v\n", s1)
}

// 查询切片中元素的下标
func test13()  {
    s1 := []int{1, 2, 3, 4, 5, 6, 7}
    var val = 5
    for i, v := range s1 {
        if val == v {
            fmt.Printf("i: %v\n", i)  // 输出下标值
        }
    }
}

// 切片赋值
func test14() {
    s1 := []int{1, 2, 3, 4, 5}
    var s2 = s1 // s1 和 s2公用一个数组,s2使用的是s1的地址,任一变动都会变
    s2[2] = 100
    fmt.Printf("s2: %v\n", s2)
    fmt.Printf("s1: %v\n", s1)

    var s3 = make([]int, 10) // 拷贝的时候务必使用一个容量比被拷贝的切片大的切片,请使用make并定义长度
    copy(s3, s1)             // 拷贝使s1 和 s3分别拥有一个相同的数组,互不影响
    s3[3] = 200
    fmt.Printf("s1: %v\n", s1)
    fmt.Printf("s3: %v\n", s3)
}

Map

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

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

定义map

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

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

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

// []中定义key的数据类型,后面跟着value的数据类型

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

初始化

func test1() { // map初始化
    var map1 map[string]int
    fmt.Printf("map1: %v\n", map1)

    map1 = make(map[string]int, 5)
    fmt.Printf("map1: %v\n", map1) // 输出map的值

    fmt.Printf("map1: %T\n", map1) // 输出map类型
}

赋值

func test2() { // map赋值
    // 直接赋值
    var map1 = map[string]string{"name": "xiao"}
    fmt.Printf("map1: %v\n", map1)

    // 先make再赋值
    m2 := make(map[string]string)
    m2["name"] = "Pixiao"
    fmt.Printf("m2: %v\n", m2)
}

取值

func test3()  { // 通过key获取value
    var m1 = map[string]string{"name": "xiao", "email": "xiao@123.com"}
    var val =m1["name"]
    fmt.Printf("val: %v\n", val)
}


func test4() { // 查看key对应的value是否存在
    var m1 = map[string]string{"name": "xiao", "email": "xiao@123.com"}
    val1, ok1 := m1["name"]
    fmt.Printf("val1: %v\n", val1)
    fmt.Printf("ok1: %v\n", ok1)

    val2, ok2 := m1["age"]
    fmt.Printf("val2: %v\n", val2)
    fmt.Printf("ok2: %v\n", ok2)
}

遍历

func test1()  { // 遍历
    var m1 = map[string]string{"name": "xiao", "age": "18", "sex": "man"}

    for key := range m1 { // 只获取key
        fmt.Printf("key: %v\n", key)
    }

    for key, v := range m1 { // 获取key和value
        fmt.Printf("key: %v val: %v\n", key, v)
    }

    for _, v := range m1 { // 只获取value 忽略key
        fmt.Printf("v: %v\n", v)
    }
}

函数

函数简介

我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名(signature)

函数特性

函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用。

  1. go语言中有3种函数:普通函数匿名函数(没有名称的函数)、方法(定义在struct上 的函数)。
  2. go语言中不允许函数重载(overload),也就是说不允许函数同名
  3. g0语言中的函数不能嵌套函数,但可以嵌套匿名函数
  4. 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
  5. 函数可以作为参数传递给另一个函数。
  6. 函数的返回值可以是一个函数。
  7. 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
  8. 函数参数可以没有名称。

调用

在调用一个函数时,会给该函数分配一个新的空间(通常为栈区),编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来

调用结束时,程序会销毁这个函数对应的栈空间

定义语法

func function_name([parameter list])[return_types]{
    函数体
}
  1. func : 函数由 func 开始声明
  2. function_name : 函数名称,函数名和参数列表一起构成了函数签名。
  3. [parameter list] :参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  4. return_types : 返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。

普通函数

func sum(num1 int, num2 int)(result int)  { // 求和
    result = num1 + num2
    return result
}

func main() {
    sum := sum(1, 2)
    fmt.Printf("sum: %v\n", sum)
}

函数返回值

函数可以有 0 或多个返回值,返回值需要指定数据类型,返回值通过 return 关键字来指定。 return 可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回值。

  1. return关键字中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称
  2. 当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值
  3. 但即使返回值命名了,return 中也可以强制指定其它返回值的名称,也就是说 return 的优先级更高
  4. 命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义
  5. return中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如 return a+b 是正确的,但 return c=a+b 是错误的。
  6. 当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如,同类型的返回值可以放进slice中,不同类型的返回值可以放进map中。
  7. 但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线_来丢弃这些返回值。

函数类型和函数变量

可以使用 type关键字来定义函数类型,两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型都相同),形参名可以不同 。 表现形式如下

type fun func(int, int) int
// 上面定义了一个fun的函数类型,此类函数接收了两个int类型的参数,并且返回了一个int类型的返回值

// 以下三个函数的函数类型完全一样
func add (a , b int) int { return a + b }
func sub (c int, d int) int { return c - d }
func mul (e int, f int) int { return e * f }

函数类型也是一种类型,故可以将其定义为函数入参,在 go 语言中函数名可以看做是函数类型的常量,所以我们可以直接将函数名作为参数传入的函数中。

// 求和
func sum(a int, b int) int {
    return a + b
}

// 最大值
func max(a int, b int) int {
    if a > b {
        return a
    } else {
        return b
    }
}

func main() {
    type fun func(int, int) int

    // sum 和 max都是符合fun此种类型的签名,就可以进行变量赋值
    f := sum
    s := f(1, 2)
    fmt.Printf("s: %v\n", s)

    f = max
    m := f(10, 9)
    fmt.Printf("m: %v\n", m)
}

高阶函数

golang的函数可以作为函数的参数,传递给另外一个函数,也可以作为另外一个函数的返回值返回。

func sayHello(name string) {
    fmt.Printf("name: %v\n", name)
}

// 函数作为参数
func test1(name string, f func(string)) {
    // 直接调用f函数
    f(name)
}

func add(a int, b int) int {
    return a + b
}

func sub(a int, b int) int {
    return a - b
}

// 函数作为返回值
func test2(oper string) func(int, int) int {
    switch oper {
    case "+":
        return add
    case "-":
        return sub
    default:
        return nil
    }
}

func main() {
    // 调用test1函数, 传递一个stirng类型的参数和一个func(string)类型的函数
    // 此时这个函数就是一个变量
    test1("tome", sayHello)

    // 调用test2函数获得一个函数,然后进行计算,此时函数就是一个返回值变量
    f := test2("+")
    i := f(10, 10)
    fmt.Printf("求和结果: %v\n", i)
    f = test2("-")
    i = f(7, 2)
    fmt.Printf("做差结果: %v\n", i)
}

匿名函数

golang函数不能嵌套,但是可以在函数内部定义匿名函数,实现简单功能的整合与调用,格式如下

// 特点为函数没有指定函数名称

func (参数列表)(返回值)

// 参数列表和返回值均可以省略
func main() {
    // 匿名函数
    max := func(a int, b int) int {
        if a >= b {
            return a
        } else {
            return b
        }
    }
    // max此时为函数,添加参数后进行调用
    i := max(3, 4)
    fmt.Printf("i: %v\n", i)

    // min为匿名函数带参数执行后的返回值
    min := func(a int, b int) int {
        if a <= b {
            return a
        } else {
            return b
        }
    }(5, 6)
    fmt.Printf("min: %v\n", min)
}

闭包

递归

defer语句

init函数

指针

结构体

泛型

方法

接口

OCP设计原则

OOP思想

继承

构造函数

包管理

包可以区分命名空间(一个文件夹中不可以存在两个同名文件),也可以更好的管理项目。go中创建一个包,一般是创建一个文件夹,在该文件夹里面的go文件中,使用package关键字声明包名称,通常文件夹名称和包名称相同。并且同一个文件夹下面只有一个包。

注意事项

  1. 保证 GO111MODULE="on"配置项开启,否则就使用 go env -w GO111MODULE=on进行开启操作(on , off , auto)
  2. 保证子包中可以被引用的函数是以大写字母开头的
  3. 新建了模块之后需要在模块目录下运行 go build命令

并发

标准库

MySQL

MongoDB

GORM

热重载

Air使用说明

推荐使用Air热重载工具,Air安装步骤如下

  1. 开发环境Mac,使用brew工具安装的Go,版本1.9.1,安装完直接运行 go env,其中GOPATH=”/Users/pixiao/go”,GOROOT=”/usr/local/Cellar/go/1.19.1/libexec”

  2. 打开Mac配置文件 open ~/.bash_profile添加如下代码

    #Go
    export GOROOT=/usr/local/Cellar/go/1.19.1/libexec
    export GOPATH=$HOME/go
    export PATH=$PATH:$GOROOT/bin

    执行配置文件立即生效命令:source ~/.bash_profile

    注意其中的 $HOME就等于 /Users/pixiao

  3. Go 1.16之后的版本在安装air或者beego的时候需要使用 go install 命令进行打包,然后将可执行文件从GOPATH的bin目录下粘贴到GOROOT的bin目录下,Air安装命令如下

    go install github.com/cosmtrek/air@latest
  4. 在现有的项目中使用 air init命令生成默认的 .air.toml配置文件

  5. 直接在项目的根目录下执行 air即可实现go代码热重载

Air详细的配置文件说明

# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary, can setup environment variables when run your app.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms
# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'.
args_bin = ["hello", "world"]

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true

跨平台打包

指定平台打包

Golang 支持在一个平台下生成多个平台运行包

1、Mac下编译Linux, Windows平台的64位可执行程序

  CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build test.go

  CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.go

2、Linux下编译Mac, Windows平台的64位可执行程序

  CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build test.go

  CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.go

3、Windows下编译Mac, Linux平台的64位可执行程序

  SET CGO_ENABLED=0 SET GOOS=darwin3 SET GOARCH=amd64 go build main.go

  SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go

GOOS:目标可执行程序运行操作系统,支持 darwin,freebsd,linux,windows GOARCH:目标可执行程序操作系统构架,包括 386,amd64,arm

批量打包

使用GoReleaser进行批量打包, MacOS进行安装

brew install goreleaser

初始化Go mod

go mod init com.shuicheng/main

生成goreleaser配置文件

goreleaser init

需要给 .goreleaser.yaml文件增加项目名称项 project_name

# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
project_name: MapApp
before:
  hooks:
    # You may remove this if you don't use go modules.
    - go mod tidy
    # you may remove this if you don't need go generate
    - go generate ./...

如果项目没有创建git请先创建git仓库

git init
git add .
git commit -m "init"

进行本地打包,然后会生成一个dist包,其中会有生成的所有打包文件 --skip-publish可以跳过发布

goreleaser --snapshot --skip-publish --rm-dist

示例配置文件

# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
project_name: MapApp
before:
  hooks:
    # You may remove this if you don't use go modules.
    - go mod tidy
    # you may remove this if you don't need go generate
    # - go generate ./...
builds:
  - env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
      #- netbsd
      #- openbsd
      #- freebsd
    goarch:
      - 386
      - amd64
      - arm
archives:
  - replacements:
      darwin: Darwin
      linux: Linux
      windows: Windows
      386: i386
      amd64: x86_64
checksum:
  name_template: 'checksums.txt'
snapshot:
  name_template: "{{ incpatch .Version }}"
changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'

宝塔运行Go项目

  1. 使用goreleaser进行打包

  2. 将Linux amd64 的可执行文件上传到服务器指定目录下

  3. 宝塔添加Go项目

    执行命令选填、root执行、开机启动选填、可以绑定域名(然后开启外网映射,申请SSL域名证书)后就可以使用域名访问接口

宝塔负载均衡

  • 创建一个或者多个Go后端服务,保证端口不同

  • 新建一个PHP静态项目,配置该项目的Nginx配置文件实现负载均衡

    upstream gofzjh {
    server 127.0.0.1:9999;
    server 127.0.0.1:9998;
    }
    server
    {
        listen 80;
        server_name fzjh.xst.shuicheng.vip;
        index index.php index.html index.htm default.php default.htm default.html;
        root /www/wwwroot/fzjh.xst.shuicheng.vip;
    
        # 进行反向代理
        location / {
           proxy_pass http://gofzjh;
        }
    
        #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
        #error_page 404/404.html;
        #SSL-END
    
        #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
        #error_page 404 /404.html;
        #error_page 502 /502.html;
        #ERROR-PAGE-END
    
        #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-00.conf;
    #PHP-INFO-END
    
    #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    include /www/server/panel/vhost/rewrite/fzjh.xst.shuicheng.vip.conf;
    #REWRITE-END
    
    #禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }
    
    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }
    
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }
    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log /dev/null;
        access_log /dev/null;
    }
    
    access_log  /www/wwwlogs/fzjh.xst.shuicheng.vip.log;
    error_log  /www/wwwlogs/fzjh.xst.shuicheng.vip.error.log;
    }

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!其他问题请通过下方微信联系!

 目录