【每日随记】 Go语言学习笔记
2022-07-11 / 0 评论 / 115 阅读 / 20 点赞

【每日随记】 Go语言学习笔记

发光的神
2022-07-11 / 0 评论 / 115 阅读 / 正在检测是否收录...

环境变量

GOROOT:

  • GOROOT 指向的是 Go 的安装路径。当你导入 Go 内部库时,Go 会在 GOROOT 路径下寻找相关的库文件。

GOARCH:

  • GOARCH 表示目标机器的处理器架构,例如 386、amd64 或 arm。这意味着你可以编译适用于不同处理器架构的 Go 程序。

GOOS:

  • GOOS 表示目标机器的操作系统,例如 darwin、freebsd、linux 或 windows。这允许你编译适用于不同操作系统的 Go 程序。

GOPATH:

  • GOPATH 是一个路径,用来存放第三方库和项目的工作空间。

    • bin 目录存放编译后生成的可执行文件。
    • pkg 目录存放编译后的包/库的归档文件。
    • src 目录存放项目和库的源文件。

在 Go 中,并没有项目的概念,而是有 这样的概念。在 GOPATH 目录中,第一个路径是默认的包目录,意味着使用 go get 下载的包都会存放在第一个路径的 src 目录下。而在使用 go install 编译时,生成的可执行文件会存放在相应 GOPATH 的 bin 目录下。

Go 标准文档

常见命令

go mod init  // 初始化go.mod
go mod tidy  // 更新依赖文件
go mod download  // 下载依赖文件
go mod vendor  // 将依赖转移至本地的vendor文件
go mod edit  // 手动修改依赖文件
go mod graph  // 打印依赖图
go mod verify  // 校验依赖
go mod why // 解释为什么需要依赖

设置代理

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

设置32位

go env -w GOARCH=386 // 设置32位
go env -w GOARCH=amd64 // 设置64位

Test


在Go语言中,整数类型(intuint 系列)非常丰富,能够根据不同的需求选择适当的整数类型。相比于C/C++中的int类型,Go提供了更多细化的类型,以适应不同位数和符号要求的整数值。下面是对Go语言中各int类型的总结:

整数类型

  1. 整数类型

    • int8:占用1字节,取值范围为 -128127
    • int16:占用2字节,取值范围为 -3276832767
    • int32:占用4字节,取值范围为 -21474836482147483647
    • int64:占用8字节,取值范围为 -92233720368547758089223372036854775807
    • int:在64位系统上通常为64位,在32位系统上为32位。Go会根据平台自动确定其字节大小。
  2. 无符号整数类型

    • uint8:占用1字节,取值范围为 0255
    • uint16:占用2字节,取值范围为 065535
    • uint32:占用4字节,取值范围为 04294967295
    • uint64:占用8字节,取值范围为 018446744073709551615
    • uint:与int类似,大小与平台有关,64位系统上为64位,32位系统上为32位。

在给出的Go语言代码中,我们可以看到如何使用Go语言的整数类型以及类型转换。

整数类型范围

代码使用math包中的常量,输出了Go语言中各整数类型的最小值和最大值:

fmt.Println("int8:", math.MinInt8, "~", math.MaxInt8)
fmt.Println("int16:", math.MinInt16, "~", math.MaxInt16)
fmt.Println("int32:", math.MinInt32, "~", math.MaxInt32)
fmt.Println("int64:", math.MinInt64, "~", math.MaxInt64)

推导类型字节大小

通过n := 1234567890,Go语言会根据值推导出变量n的类型,输出该类型的信息:

fmt.Printf("n := 1234567890 的默认类型为:%T\n", n)
fmt.Println("int类型的字节数为:", unsafe.Sizeof(n))

在这个例子中,n的类型会根据它的值推导为int(在64位系统上是int64),并通过unsafe.Sizeof函数输出字节大小。

类型转换

Go语言支持类型转换,但对于不同类型之间的转换可能会发生截断或溢出。代码示范了将int32类型的变量a转换为其他整数类型:

var a int32 = 987654321
b := int8(a)  // 转换为int8,发生数值截断
c := int16(a) // 转换为int16,发生数值截断
d := int64(a) // 转换为int64

在这种转换中,如果值超出了目标类型的取值范围,就会发生截断,导致数据丢失。

操作注册表

package main

import (
    "golang.org/x/sys/windows/registry"
)

func main() {

    key, exists, _ := registry.CreateKey(registry.CURRENT_USER, `SOFTWARE\Hello Go\`, registry.ALL_ACCESS)
    defer key.Close()

    // 判断是否已经存在了
    if exists {
        println(`键已存在`)
    } else {
        println(`新建注册表键`)
    }

    // 写入:32位整形值
    key.SetDWordValue(`32位整形值`, uint32(123456))
    // 写入:64位整形值
    key.SetQWordValue(`64位整形值`, uint64(123456))
    // 写入:字符串
    key.SetStringValue(`字符串`, `hello`)
    // 写入:字符串数组
    key.SetStringsValue(`字符串数组`, []string{`hello`, `world`})
    // 写入:二进制
    key.SetBinaryValue(`二进制`, []byte{0x11, 0x22})

    // 读取:字符串
    s, _, _ := key.GetStringValue(`字符串`)
    println(s)

    // 读取:一个项下的所有子项
    keys, _ := key.ReadSubKeyNames(0)
    for _, key_subkey := range keys {
        // 输出所有子项的名字
        println(key_subkey)
    }

    // 创建:子项
    subkey, _, _ := registry.CreateKey(key, `子项`, registry.ALL_ACCESS)
    defer subkey.Close()
    // 删除:子项
    // 该键有子项,所以会删除失败
    // 没有子项,删除成功
    registry.DeleteKey(key, `子项`)
}

网络请求

HTTP 协议定义了多种请求方法,用于向服务器发送请求。在日常开发中,以下是常用的五种请求方法及其作用:

方法描述
GET请求指定的页面信息,并返回实体主体。
POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
PUT从客户端向服务器传送的数据取代指定的文档的内容。
DELETE请求服务器删除指定的页面。
PATCH是对 PUT 方法的补充,用来对已知资源进行局部更新。

GET 请求

resp, err := http.Get("http://localhost:8080/get")
if err != nil {
    log.Printf("get请求失败 error: %+v", err)
    return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Printf("读取Body失败 error: %+v", err)
    return
}
log.Println(string(body))
  • http.Get 发送 GET 请求,获取指定 URL 的响应。

POST 请求

resp, err := http.Post("http://localhost:8080/post", "text/json;charset=utf-8", strings.NewReader("{\"name\":\"jack\"}"))
if err != nil {
    log.Printf("post请求失败 error: %+v", err)
    return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Printf("读取Body失败 error: %+v", err)
    return
}
log.Println(string(body))
  • http.Post 发送 POST 请求,strings.NewReader 用于构造请求体。

POSTForm 请求

resp, err := http.PostForm("http://localhost:8080/postForm", url.Values{"name": {"jack"}})
if err != nil {
    log.Printf("postForm请求失败 error: %+v", err)
    return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Printf("读取Body失败 error: %+v", err)
    return
}
log.Println(string(body))
  • http.PostForm 用于发送表单数据请求,数据以 url.Values 形式传递。

HEAD 请求

resp, err := http.Head("http://localhost:8080/head")
if err != nil {
    log.Printf("Head请求失败 error: %+v", err)
    return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Printf("读取Body失败 error: %+v", err)
    return
}
log.Println(string(body))
  • http.Head 请求资源的头信息,不返回响应体。

复杂请求(POST 请求 + 请求头)

type User struct {
    Id   int    `json:"id"`
    Name string `json:"name"`
}
user := User{Id: 1, Name: "bob"}
jsonBytes, err := json.Marshal(user)

client := &http.Client{}
request, err := http.NewRequest("POST", "http://localhost:8080/complex?env=dev", bytes.NewReader(jsonBytes))
request.Header.Add("Cookie", "123")
request.Header.Add("Content-Type", "application/json;charset=utf-8")
request.Header.Add("Token", "456")
client.Timeout = 1 * time.Minute

resp, err := client.Do(request)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    log.Printf("读取Body失败 error: %+v", err)
    return
}
log.Println(string(body))
  • http.NewRequest 用于创建复杂的 HTTP 请求,包括自定义请求方法、请求头等。

完整的 HTTP 请求(GET、POST、PUT、PATCH、DELETE)

func TestHttpGet(t *testing.T) { ... }
func TestHttpGetHeader(t *testing.T) { ... }
func TestHttpPost(t *testing.T) { ... }
func TestHttpPostForm(t *testing.T) { ... }
func TestHttpPostJSON(t *testing.T) { ... }
func TestHttpPut(t *testing.T) { ... }
func TestHttpPatch(t *testing.T) { ... }
func TestHttpDelete(t *testing.T) { ... }

在这些测试方法中,使用了 Go 的 http 包来发送各种 HTTP 请求方法,并处理响应内容。


线程并发编程

Go 语言的并发性是其最具吸引力的特点之一。与其他语言(如 Java)不同,Go 使用轻量级的 Goroutine 来实现并发,这使得 Go 在处理大量并发时非常高效。Goroutine 是一种非常轻量的线程,它的内存开销小,程序可以创建成千上万的 Goroutine 而不会占用太多资源。


创建 Goroutine

创建 Goroutine 使用 go 关键字,它会启动一个并发执行的函数。在下面的示例中,count 函数通过 Goroutine 被并发执行:

package main

import (
    "fmt"
    "time"
)

func main() {
    go count(5, "?")
    go count(5, "?")
}

func count(n int, animal string) {
    for i := 0; i < n; i++ {
        fmt.Println(i+1, animal)
        time.Sleep(time.Millisecond * 500)
    }
}

程序提前退出

  • 在上面的代码中,虽然我们创建了两个 Goroutine,但是程序会在 Goroutine 执行之前就退出。因为主函数没有等待 Goroutine 执行完毕。

解决方法

使用 time.Sleep 延迟主线程结束(不推荐,简便,但不优雅)

time.Sleep(time.Second * 1)

使用 fmt.Scan 等待用户输入(仍不太优雅)

var stop int
fmt.Scan(&stop)

使用 sync.WaitGroup(推荐)

sync.WaitGroup 用于追踪 Goroutine 的执行状态。我们可以通过 AddDoneWait 方法控制 Goroutine 的执行和等待。

var wg sync.WaitGroup
wg.Add(2) // 添加两个任务

go func() {
   count(5, "?")
   wg.Done() // 每个 Goroutine 完成时,计数器减1
}()

go func() {
   count(5, "?")
   wg.Done()
}()

wg.Wait() // 等待所有 Goroutine 完成

实现多并发

上面的代码实现了多个 Goroutine 并发执行任务,每个 Goroutine 都会执行 count 函数并在完成后通过 wg.Done() 通知主线程:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup // 计数器
    wg.Add(2) // 添加2个线程
    go func() {
        count(5, "?")
        wg.Done() // 运行完毕就在计数器里减1
    }()

    go func() {
        count(5, "?")
        wg.Done()
    }()
    wg.Wait()
}

func count(n int, animal string) {
    for i := 0; i < n; i++ {
        fmt.Println(n, animal)
        time.Sleep(time.Millisecond * 500)
    }
}
  • 这里每个 Goroutine 运行时,独立执行并发的任务,没有直接的通信。

实现 Goroutine 间通信

Go 的 channel 允许 Goroutine 之间传递数据,它是 Go 并发编程的核心之一。通过 channel,多个 Goroutine 可以共享数据或者进行同步。
在下面的代码中,使用了两个 channel 来实现 Goroutine 之间的通信:

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string) // 创建信道 获取信息
    c2 := make(chan string)

    go func() {
        c1 <- "?" // 向 c1 发送数据
        time.Sleep(time.Millisecond * 500)
    }()

    go func() {
        c2 <- "?" // 向 c2 发送数据
        time.Sleep(time.Millisecond * 500)
    }()

    for {
        select {
        case msg := <-c1: // 接收来自 c1 的数据
            fmt.Println(msg)
        case msg := <-c2: // 接收来自 c2 的数据
            fmt.Println(msg)
        }
    }
}
  • select 语句用于从多个 channel 中选择一个可以接收到数据的 channel,并执行相应的操作。
  • 当数据从任意一个 channel 接收到时,程序会打印相应的信息。

程序打包

打包命令

go build main.go // 直接打包,大体积
go build         // 也可以不带任何指定使用
go build -ldflags "-w -s -H=windowsgui" // 隐藏DOS窗口,小体积打包(推荐使用)

rsrc工具

如果需要为程序添加 UAC 权限或图标,可以使用 rsrc 工具来编译资源文件。可以通过以下两种方式下载 rsrc 工具:

git clone https://github.com/akavel/rsrc.git
go get github.com/akavel/rsrc`

下载 rsrc 工具

添加 UAC 与图标

  1. 新建 manifest 文件
    新建名为 nac.manifest 的文件,填写以下内容:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
            <security>
                <requestedPrivileges>
                    <requestedExecutionLevel level="requireAdministrator"/>
                </requestedPrivileges>
            </security>
        </trustInfo>
    </assembly>
  2. 生成并打包资源文件
    使用 rsrc 工具来编译资源文件:

    rsrc -manifest nac.manifest -o nac.syso // 编译资源文件
    rsrc -manifest nac.manifest -o nac.syso -ico 00.ico // 添加自定义图标
  3. 编译可执行文件
    使用 go build 命令来生成最终的可执行文件:

    go build // 编译 Go 源代码并生成可执行文件
  4. 参数打包
    常规编译方式:

    go build -ldflags="-w -s" -o main.exe

    无界面编译方式(隐藏DOS窗口):

    go build -ldflags="-w -s -H windowsgui" -o main.exe

通过以上步骤,可以生成一个带有 UAC 权限和自定义图标的小体积 Go 程序。

20

评论 (0)

取消
0:00