Go语言基础
2024-01-03 07:56:53

Go语言基础

Go语言吉祥物

01 序章

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

Go语言是由多名著名的编程语言大师所创造,于2007年9月开始设计由罗伯特·格瑞史莫(Robert Graysmith)罗勃·派克(Robert Pike)肯·汤普逊(Ken Thompson)开始设计,随后伊恩·兰斯·泰勒(Ian Lance Taylor)拉斯·考克斯(Russ Cox)加入项目。Go是基于Inferno操作系统所开发的。Go于2009年11月正式宣布推出,成为开放源代码项目,支持Linux、macOS、Windows等操作系统。

在2016年,Go被软件评价公司TIOBE选为“TIOBE 2016年最佳语言”。

未命名

图1 罗伯特·格瑞史莫、罗勃·派克、肯·汤普逊

Go语言,由谷歌推出,目的是为了简化编程并提高性能的编程语言。罗布·派克,谷歌的首席软件工程师,曾解释道:我们之所以开发,是因为过去10多年间软件开发的难度令人沮丧。派克表示,和今天的C++或C一样,Go是一种系统语言。

Go语言的开发受到了以下核心动机和优势的驱动:

  1. 应对不断提高的硬件性能:计算机硬件技术不断更新,性能迅猛提升。传统编程语言在充分利用多核和多CPU的硬件优势方面表现不佳。Go语言的目标是更好地利用现代硬件,提供更高的性能。
  2. 降低代码复杂性:现代软件系统变得越来越复杂,维护成本不断攀升。Go语言应运而生,通过提供简洁而高效的编程语言,应对这一挑战。
  3. 项目兼容性:许多企业继续维护着使用C/C++编写的项目。这些项目在性能方面表现出色,但编译速度较慢,同时存在内存泄漏等问题。Go语言为这些企业提供了一种现代化的解决方案,平衡了性能和开发效率。

02 环境搭建

2.1 Visual Studio 安装:

Visual Studio是微软推出的编码工具。

安装vscode,下载地址:https://code.visualstudio.com/
下载好安装包,一路点击下一步即可。

安装完vscode后,如图2所示,点击左侧的扩展工具按钮,搜索golang,安装一个名为Go的扩展使VS Code为Go编程语言提供了丰富的语言支持。

1697889469764

图2 Go扩展安装

2.2 golang SDK安装:

SDK的全称(Software Development Kit 软件开发工具包)

下载

编译器下载地址:https://go.dev/dl/

如果你是系统是Windows系统,则点击下图红框处进行下载即可:

1697885261504

图3 Golang下载

安装

安装过程如图3所示,即一路下一步即可。

golang安装

图4 Golang安装过程:从左到右

测试是否安装成功

按win+r,输入cmd

1697885827745

图5 呼出cmd命令提示符

在终端中输入:go version,若出现对应的版本号即可可认为安装:成功

1697885848935

图6 golang版本查看

若上方没有出现golang的版本号,可以检测环境变量:

按win键输入”环境变量”
1697885791612

依次按照如下步骤,查看环境变量中的golang路径是否正确

环境变量

图7 环境变量查看

03 Hello world示例

3.1 代码编写

hello world程序包含了一个Go程序的入口点 main 函数,在 main 函数中,我们使用 fmt 包来输出 “Hello, World!” 到控制台。

在cmd中创建工作目录:

1
mkdir D:\goproject\src\gocode\testproject01\main

创建一个test.go文件,输入如下代码:

1
2
3
4
5
6
package main
import "fmt"

func main() {
fmt.Println("Hello, World!")
}

如图7用鼠标右键点击main文件夹可以看到new file选项,创建一个test.go文件,然后在右侧输入上方代码。

1697886794453

图8 代码编写

3.2 运行代码

1697886827615

图9 打开集成终端

在终端环境下输入go run test.go即可看到输出:Hello,world!
1697886890096

图10 运行go代码

这是一个非常简单的Go程序,但它演示了Go语言的基本结构和语法。

04 Go语言编译到运行的原理

4.1 Go语言编译原因

Go语言是需要编译才能运行的,主要原因包括以下几点:

  1. 静态类型语言:Go是一种静态类型语言,这意味着变量的类型在编译时就已经确定,而不是在运行时。这有助于提前检测类型错误和提高程序的性能。
  2. 编译优化:Go编译器可以执行各种编译优化,包括内联函数、消除未使用的变量和代码等,以提高程序的性能。这些优化通常是在编译时完成的,从而减少了运行时的性能开销。
  3. 生成本地机器码:Go编译器将Go源代码编译为本地机器码,这意味着生成的可执行文件可以直接在目标计算机上运行,而无需依赖额外的解释器或虚拟机。
  4. 依赖管理:Go编译器能够有效地处理程序的依赖关系,确保所需的库和包在编译时正确链接到程序中。这有助于简化依赖管理和程序分发的过程。
  5. 跨平台支持:Go编译器支持跨不同操作系统和架构的交叉编译。这意味着您可以在一台机器上编译Go程序,然后将生成的可执行文件移植到其他操作系统或架构上运行。

4.2 Go语言编译步骤

在Hello world例子中,我们使用了go run命令可以直接运行Go源代码文件而无需显式编译。

这是因为Go编译器在执行go run时会在后台自动进行必要的编译和链接操作

当运行go run test.go时,Go编译器会自动执行以下步骤,如图:

  1. 编译:Go编译器将test.go文件编译成中间代码(通常是一个临时的二进制文件),而不是生成最终的可执行文件。如果单独对文件进行编译则可以执行:

    1
    go build -o test.exe test.go

    这段命令的意思是用test.go源码编译test.exe文件。

  2. 链接:编译完成后,Go编译器会自动链接所需的库和依赖项,以生成可执行文件。

  3. 运行:最后,编译器会运行生成的可执行文件。

这种方式允许您以一种非常便捷的方式运行Go程序,而无需手动进行独立的编译和链接步骤。这对于快速开发和调试非常有用,但在将程序部署到生产环境之前,通常还会执行显式的编译以获得最佳性能。

编译到可执行二进制文件

图11 Go编译

05 项目布局

https://go.dev/doc/modules/layout

基本布局三个文件:go.mod模块文件、modname.go代码文件、modname_test.go测试文件。

1
2
3
4
project-root-directory/
go.mod
modname.go
modname_test.go

modname.go文件内容:

1
2
3
4
5
package modname

import "github.com/someuser/modname"

// ... package code here

则文件go.mod中 module 名应为 module github.com/someuser/modname

1
2
3
module modname

go 1.21.3

5.1 基本项目结构

主入口main.go

最简单的程序可以由定义的单个 Go 文件组成func main

较大的程序可以将其代码拆分为多个文件,所有文件都声明package main:

1
2
3
4
5
6
project-root-directory/
go.mod
auth.go
auth_test.go
client.go
main.go

这里的main.go文件包含func main,但这只是一个约定。

用户应该能够通过以下方式将其安装在他们的计算机上:

1
$ go install github.com/someuser/modname@latest

5.2 多个模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
token/
token.go
token_test.go
hash/
hash.go
internal/
trace/
trace.go

其中go.mod的内容为:

1
module github.com/someuser/modname

用户可以通过以下方式导入子包:

1
2
3
import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"

5.3 多个命令

顶级 internal目录可以包含存储库中所有命令使用的共享包。

1
2
3
4
5
6
7
8
project-root-directory/
go.mod
internal/
... shared internal packages
prog1/
main.go
prog2/
main.go

用户可以通过以下命令安装这些程序到internal:

1
2
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest

5.4 包和命令位于同一存储库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
internal/
... internal packages
cmd/
prog1/
main.go
prog2/
main.go

假设这个模块被称为github.com/someuser/modname,用户现在可以从中导入包:

1
2
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"

并从中安装程序:

1
2
$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest

5.5 示例

目录结构:

1701931687045

创建一个文件夹:myproject,执行下方命令会产生一个go.mod文件

1
2
cd myproject
go mod init example.com

go.mod文件内容:

1
2
3
module example.com

go 1.21.3

继续创建greetings文件夹

1
2
mkdir greetings
cd greetings

执行下方命令,生成greetings/go.mod

1
go mod init example.com/greetings

greetings/go.mod的内容为:

1
2
3
module example.com/greetings

go 1.21.3

创建文件greetings/greetings.go

1
2
3
4
5
6
7
8
9
10
package greetings

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}

返回上一层

1
cd ..

创建main.go,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"

"example.com/greetings"
)

func main() {
// Get a greeting message and print it.
message := greetings.Hello("Gladys")
fmt.Println(message)
}

执行如下命令根据当前代码的依赖关系,更新和清理 go.mod 文件

1
go mod edit -replace example.com/greetings=./greetings
1
go mod tidy

运行

1
go run .

06 Go语言基本语法

6.1 注释

在Go中,注释是为了提供代码的解释、文档和可读性,编译器会忽略注释部分。通常,Go代码中的注释用于文档生成工具(例如,GoDoc)以及代码维护和协作。良好的注释可以帮助其他开发者理解您的代码并提供有用的信息。

Go语言支持两种主要类型的注释:单行注释和多行注释。

  1. 单行注释:单行注释以双斜杠 // 开头,可以用于注释单行代码或单行注释。单行注释后的文本将被编译器忽略,不会对程序产生任何影响。
1
2
// 这是一个单行注释
fmt.Println("Hello, World!") // 这是一个单行注释
  1. 多行注释:多行注释以斜杠加星号 /* 开头,以星号加斜杠 */ 结尾,可以用于注释多行代码块或多行注释。多行注释可以跨越多行,并且可以包含多行文本。
1
2
3
4
/*
这是一个
多行注释
*/

6.2 代码风格

Effective Go代码风格链接: https://go.dev/doc/effective_go

Go语言有一套官方的代码风格指南,通常称为”Effective Go”,它目的是帮助Go开发者编写清晰、一致和易于阅读的代码。以下是一些关于Go代码风格的主要原则和建议:

  1. 使用短小的命名:Go鼓励使用简洁而具有描述性的变量和函数名称。通常,Go使用短小的单词或缩写,而不是过于冗长的名称。

  2. 使用驼峰命名法:Go代码通常使用驼峰命名法(camelCase)来命名变量、函数和方法。例如,myVariableNamemyFunctionName

  3. 一行代码长度不要超过80个字符:虽然Go语言不强制限制行的长度,但建议在80个字符左右断行,以便代码在不同的编辑器和屏幕上更容易阅读。

  4. 注释:良好的注释对于代码的可读性非常重要。Go鼓励编写清晰、简洁的注释,用于解释代码的目的、行为和使用方式。

  5. 使用格式化工具:Go附带了一个格式化工具 gofmt,它可以自动调整代码的格式以符合官方的代码风格指南。使用 gofmt 可以保持代码的一致性。

  6. 导入分组:Go的import语句应该按照一定的分组方式组织,标准库导入应该在最上面,然后是第三方库,最后是项目自身的包。每个分组之间应该有空行。

  7. 错误处理:Go鼓励使用明确的错误处理机制,通常使用多值返回,例如,result, err := someFunction()。错误应该被检查和处理,不要忽略错误。

  8. 使用函数字面量:Go支持函数字面量,这使得函数式编程在Go中更加方便。使用函数字面量来简化代码。

  9. 避免全局变量:尽量避免使用全局变量,而是使用函数参数和返回值来传递数据。全局变量应该被谨慎使用。

  10. 可读性优于智能性:Go鼓励代码的可读性优于智能性。编写易于理解和维护的代码是更重要的。

6.3 内置标准库

内置标准库文档链接:https://pkg.go.dev/std

在国内,有时候编程语言自带的函数也被称为API,这是错误的。在编程中,API通常指的是用于与外部组件、服务或库进行交互的接口,而不是编程语言本身提供的内置函数。

尽管在某些语境下出现了这种用法,但在国际编程社区中,API的定义通常更为明确,指的是与外部组件进行交互的接口。编程语言自身的内置函数和功能通常不被称为API。不同地区和文化可能会有一些不同的编程术语使用方式,因此在具体的语境中,对术语的理解可能会有所不同。

6.4 变量

Go语言中的变量是用来存储数据的标识符。在Go中,变量的声明通常包括变量名、变量类型和可选的初始化值。

Golang Variables, Zero Values, and Type inference | CalliCoder

图12 内存与变量的关系

在Go(也称为Golang)编程语言中,标识符是用来命名变量、函数、类型和其他程序实体的名称

标识符必须遵循一些规则和约定,以便在代码中使用。以下是关于Go中标识符的一些规则:

  1. 标识符由字母、数字和下划线组成,但必须以字母或下划线开头。
  2. 标识符区分大小写,因此myVarmyvar被视为不同的标识符。
  3. Go中有一些预定义的标识符,如ifelseforfunc等,它们具有特殊含义,不能用作自定义标识符。
  4. Go的关键字(reserved words)也不能用作标识符。
  5. 标识符应该具有描述性,以便提高代码的可读性。通常采用驼峰命名法,例如myVariableName

6.5 变量类型

Go的类型系统是强类型的,这意味着变量的类型必须在编译时明确定义,不能在运行时随意改变,以下是Go语言的基本数据类型、复合数据类型

6.5.1 基本数据类型

  1. 整数类型(int):Go语言提供了不同大小的整数类型,如intint8int16int32int64,分别表示不同位数的有符号整数。还有uintuint8uint16uint32uint64表示无符号整数。例如:

    1
    2
    var x int = 42
    var y uint8 = 10
  2. 浮点数类型(float)float32float64,分别表示单精度和双精度浮点数。例如:

    1
    var pi float64 = 3.141592
  3. 复数类型(complex)complex64complex128,分别表示单精度和双精度复数。例如:

    1
    var z complex128 = 2 + 3i
  4. 字符串类型(string):字符串是不可变的字节切片。例如:

    1
    var str string = "Hello, World!"
  5. 布尔类型(bool):布尔类型只有两个值:truefalse,用于表示逻辑真和逻辑假。例如:

    1
    var isTrue bool = true
  6. 字符类型(rune):字符类型rune用于表示Unicode字符,通常用单引号括起来。例如:

    1
    var ch rune = '💖'

6.5.2 复合数据类型

  1. 数组类型(array):数组是具有固定长度的同一类型元素的集合。例如:

    1
    var numbers [5]int
  2. 切片类型(slice):切片是对数组的一种引用,它允许动态增加或减少其长度。例如:

    1
    var mySlice []int
  3. 字典类型(map):字典是键-值对的集合,用于实现哈希表。例如:

    1
    2
    3
    4
    5
    6
    var myMap map[string]int

    // 添加键值对到 map
    myMap["Alice"] = 92
    myMap["Bob"] = 85
    myMap["Charlie"] = 78
  4. 结构体类型(struct):结构体是一种自定义的复合类型,用于组织不同类型的数据。例如:

    1
    2
    3
    4
    type Person struct {
    Name string
    Age int
    }
  5. 接口类型(interface):接口是一种定义了一组方法签名的类型,用于实现多态性。例如:

    1
    2
    3
     type Shape interface {
    Area() float64
    }
  6. 指针类型(Pointer Type):指针类型表示指向某种数据类型的指针。指针变量包含了一个内存地址,该地址指向存储了特定数据类型的值的内存位置。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

    func main() {
    var x int = 42
    var y *int // 声明一个整数指针

    y = &x // 将变量 x 的地址赋给指针 y
    *y = 100 // 通过指针 y 修改变量 x 的值

    fmt.Println("x =", x) // 输出 x 的值
    }
  7. 管道类型(Channel):它是一种用于在协程(goroutine)之间进行通信和同步的数据结构,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package main

    import "fmt"

    func main() {
    // 创建一个无缓冲管道
    ch := make(chan int)

    // 启动一个协程发送数据到管道
    go func() {
    ch <- 42
    }()

    // 主协程从管道中接收数据
    value := <-ch

    fmt.Println("接收到的值:", value)
    }
  8. 函数类型(Function):在Go语言中,函数也是一种数据类型,可以像其他数据类型一样被赋值、传递和作为参数传递给其他函数。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package main

    import "fmt"

    // 声明一个函数类型
    type MyFunction func(int, int) int

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

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

    func main() {
    // 将函数赋值给函数类型变量
    var operation MyFunction
    operation = add

    result := operation(5, 3) // 调用函数类型变量
    fmt.Println("5 + 3 =", result)

    operation = subtract
    result = operation(10, 4)
    fmt.Println("10 - 4 =", result)
    }

Go 语言没有传统编程语言中所谓的 “派生数据类型” 的概念。派生数据类型通常指的是用户自定义的数据类型,它们是基本数据类型的扩展或组合。

6.5.3 通过反射来查看变量类型

reflect 包是Go语言标准库中的一个非常强大和灵活的包,用于在运行时进行反射操作,允许你检查和操作变量的类型、结构、字段和方法等。反射是一种在程序运行时检查、探索和操作程序结构的能力,而不是在编译时已知的。这使得你可以编写具有高度动态性的代码,但同时也需要小心使用,因为它通常会导致较慢的性能。

以下是reflect包的一些主要功能和应用:

  1. 类型检查和类型查询:你可以使用 reflect.TypeOf() 函数获取变量的类型信息,或者使用 reflect.ValueOf() 获取变量的值信息。这对于在运行时动态地检查变量的类型非常有用。

  2. 结构字段访问reflect包允许你检查结构体类型的字段,并获取或设置字段的值。

  3. 方法调用:你可以使用 reflect.Value 类型的 MethodByName 方法来调用结构体的方法。

  4. 创建和修改变量reflect包提供了一些方法,允许你在运行时创建和修改变量的值。

  5. 枚举、遍历和操作集合:你可以使用 reflect 包来检查和操作数组、切片、映射等集合类型的元素。

  6. 反射接口值reflect 包允许你将接口值转换为反射值,并进行类型断言和类型转换。

尽管reflect包提供了很多强大的功能,但需要小心使用,因为它会增加代码的复杂性,并且可能导致性能开销。通常情况下,你应该尽量避免在普通应用程序中过度使用反射,只在需要动态处理类型的高级应用中使用它。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"reflect"
)

func main() {
// 定义一个整数变量
num := 42

// 使用reflect.TypeOf()函数获取变量的类型
t := reflect.TypeOf(num)

// 打印变量的类型
fmt.Println("变量的类型是:", t)
}

以下让我们针对这些类型进行详细讲解一下它们的使用方法。

6.6 整数类型

如变量类型中介绍,Go语言提供了不同大小的整数类型,如intint8int16int32int64,分别表示不同位数的有符号整数。还有uintuint8uint16uint32uint64表示无符号整数。

6.6.1 整数范围

下面是常见的有符号和无符号整数类型及其范围的表格:

有符号整数类型:

类型 位数 (字节) 最小值 最大值
int 取决于平台 -9223372036854775808 到 9223372036854775807 -9223372036854775808 到 9223372036854775807
int8 1 -128 到 127 -128 到 127
int16 2 -32768 到 32767 -32768 到 32767
int32 4 -2147483648 到 2147483647 -2147483648 到 2147483647
int64 8 -9223372036854775808 到 9223372036854775807 -9223372036854775808 到 9223372036854775807

无符号整数类型:

类型 位数 (字节) 最小值 最大值
uint 取决于平台 0 18446744073709551615
uint8 1 0 255
uint16 2 0 65535
uint32 4 0 4294967295
uint64 8 0 18446744073709551615

intuint 的大小取决于平台,通常为32位或64位。这些范围反映了整数类型可以表示的最小和最大值。当选择整数类型时,需要根据应用程序的需求和所需的数据范围来选择适当的整数类型。

6.6.2 整数最大最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"math"
)

func main() {
fmt.Printf("int8 -> 最小值:%d,最大值:%d\n", math.MinInt8, math.MaxInt8)
fmt.Printf("int16 -> 最小值:%d,最大值:%d\n", math.MinInt16, math.MaxInt16)
fmt.Printf("int32 -> 最小值:%d,最大值:%d\n", math.MinInt32, math.MaxInt32)
fmt.Printf("int64 -> 最小值:%d,最大值:%d\n", int64(math.MinInt64), int64(math.MaxInt64))

fmt.Printf("uint8 -> 最小值:0,最大值:%d\n", math.MaxUint8) // uint8的最小值是0。没有最小值常量
fmt.Printf("uint16 -> 最小值:0,最大值:%d\n", math.MaxUint16) // uint16的最小值是0。没有最小值常量
fmt.Printf("uint32 -> 最小值:0,最大值:%d\n", uint32(math.MaxUint32)) // uint32的最小值是0。没有最小值常量
fmt.Printf("uint64 -> 最小值:0,最大值:%d\n", uint64(math.MaxUint64)) // uint64的最小值是0。没有最小值常量
}

6.6.3 数学运算代码示例

以下示例演示了如何使用整数执行加法、减法、乘法、除法、取余以及自增和自减操作。这些操作在Go中是非常常见的,用于各种数学和计算任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

func main() {
// 加法
sum := 5 + 3
fmt.Println("加法结果:", sum)

// 减法
difference := 10 - 4
fmt.Println("减法结果:", difference)

// 乘法
product := 6 * 7
fmt.Println("乘法结果:", product)

// 除法
quotient := 15 / 3
fmt.Println("除法结果:", quotient)

// 取余
remainder := 17 % 4
fmt.Println("取余结果:", remainder)

// 自增
i := 5
i++
fmt.Println("自增结果:", i)

// 自减
j := 8
j--
fmt.Println("自减结果:", j)
}

6.7 浮点类型

6.7.1 浮点数范围

在Go语言中,浮点数遵循IEEE 754标准,这是一种用于表示浮点数的二进制标准。具体来说,Go支持单精度浮点数(float32)和双精度浮点数(float64),以下表格显示了float32float64浮点数类型的类型、大小、范围。有助于你了解浮点数类型的范围和精度,以便在编写数值计算代码时选择合适的类型。但注意的是浮点数在进行精确计算时可能会引入舍入误差,这需要在设计算法时考虑。

类型 大小 范围
float32 32位 -3.4e+38 to 3.4e+38.
float64 64位 -1.7e+308 to +1.7e+308.

6.7.2 IEEE 754标准(可选读)

IEEE 754浮点数转换器:https://www.h-schmidt.net/FloatConverter/IEEE754.html

IEEE 754浮点算术标准是由电气和电子工程师协会(IEEE)于1985年制定的浮点计算的技术标准。该标准解决了各种浮点实现中存在的问题,这些问题使它们难以可靠地使用并降低了它们的可移植性。IEEE 754标准的浮点数是当今计算机上表示实数的最常见方式,包括基于Intel的个人计算机、Mac和大多数Unix平台。

有多种表示浮点数的方式,但在大多数情况下,IEEE 754是最高效的。如图12、图13,IEEE 754具有三个基本组成部分:

  1. 符号位:0表示正数,而1表示负数。

  2. 指数位:指数字段需要表示正指数和负指数。为了获得存储的指数,需要添加一个偏置值。

  3. 尾数位:尾数是科学记数法或浮点数的一部分,由其有效数字组成。在这里,我们只有2个数字,即0和1。因此,规范化的尾数是指在小数点左侧只有一个1的尾数。

这个标准使浮点数计算在计算机中更一致,同时允许在不同计算机平台之间进行更可靠的数值计算。需要注意的是,浮点数在处理某些数值时可能引入舍入误差,因此在关键应用中需要谨慎处理这些问题。

IEEE Standard 754 Floating Point Numbers - GeeksforGeeks

图13 IEEE 754标准的32位浮点型内存分配

img

图14 IEEE 754标准的64位浮点型内存分配

6.7.3 IEEE 754示例:

当将十进制的 浮点数18.75 存储为IEEE 754单精度浮点数时,需要将它转换为二进制表示,并使用规定的格式存储,它包括三个主要部分:符号位、指数位和尾数位。。

  1. 转换为二进制

    • 整数部分 18 转换为二进制是 10010
  • 小数部分 0.75 转换为二进制是 0.11

将它们合并在一起,得到二进制表示:10010.11

  1. 规范化:规范化是将二进制中的小数点移动到左边只剩一个1的形式。在这种情况下,我们可以将小数点向左移动,直到只有一个非零数字 1 位于小数点左侧。所以,将小数点移到 1 的右边,得到规范化的形式:1.001011

  2. 确定符号位:18.75 是正数,所以符号位为 0。

  3. 确定指数

    • 规范化后,小数点移到了 1 的右边,所以我们需要记录移动的位数。在这种情况下,小数点右移了 4 位。
  • IEEE 754使用偏置指数来表示指数部分。单精度浮点数的偏置值通常是 127。所以,实际指数为 4,加上偏置值 127,得到存储的指数值为 131(以二进制表示为 10000011)。
  1. 存储尾数、指数和符号位
    • 符号位:0(表示正数)
    • 指数位:131(以二进制表示为 10000011
    • 尾数位:001011(可以对照规范化的结果类进行查看)

综合起来,18.75 的单精度IEEE 754浮点数表示如下:

  • 符号位:0(正数)
  • 指数位:131(以二进制表示为 10000011
  • 尾数位:001011(可以对照规范化的结果类进行查看)

这个表示将用于在计算机中存储和运算浮点数的值。请注意,具体的二进制表示可能会因计算机体系结构而异,但IEEE 754规定了这些表示的标准化方式。这个表示形式允许有效地表示广泛的实数范围。

1697894759261

图15 IEEE 754标准存储浮点数值18.75

6.7.3 代码示例

通过下面的例子可以看到,单精度与双精度浮点型的定义,以及科学计数法的使用过程。另外还需要特别留意关于精度丢失问题,因为在单精度浮点数表示中,对于非常大和非常小的数字,精度可能会受到限制,一般可以替换成双精度浮点型,以存储更高精度的浮点数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"reflect"
)

func main() {
// 单精度浮点型
var pi1 float32 = 3.14
fmt.Println("单精度浮点数:", pi1)
// 双精度浮点型
var pi2 float64 = 3.14
fmt.Println("双精度浮点数:", pi2)

// 当没有制定具体类型的时候,默认为float64
pi3 := 3.14
fmt.Println(reflect.TypeOf(pi3))

// 科学计数法
var pi4 float32 = 314E-2
fmt.Println(pi4)
var pi5 float32 = 314E+2
fmt.Println(pi5)

// 精度丢失情况
var num1 float32 = 100000000.0
var num2 float32 = 0.000001
result := num1 + num2
fmt.Printf("结果为:%f\n", result)
}

6.8 复数类型

Go语言提供了内置的复数类型 complex64complex128。下面是一个使用复数类型的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func main() {
// 使用复数类型 complex64 来表示复数
z := complex(3, 4) // 3 + 4i

// 打印复数
fmt.Println("复数 z =", z)

// 获取实部和虚部
realPart := real(z)
imagPart := imag(z)

fmt.Println("实部:", realPart) // 输出: 3
fmt.Println("虚部:", imagPart) // 输出: 4

// 使用复数进行算术运算
a := complex(1, 2)
b := complex(3, 5)

sum := a + b
product := a * b

fmt.Println("a + b =", sum) // 输出: (4+7i)
fmt.Println("a * b =", product) // 输出: (-7+11i)
}

理解复数乘法:

下面我将简要解释实部的乘积是如何推导出来的:

假设有两个复数 a 和 b,它们分别表示为 $a = x_1 + y_2i$ 和 $b = x_2 + y_2i$,其中$x_1$是实部、$y_1$是虚部、$x_2$是实部、$y_2$是虚部。

复数乘法 a * b 的实际计算如下:

$$
a * b = (x_1 + y_1i) * (x_2 + y_2i)
$$

使用分配律将每个项相乘:
$$
a * b = x_1 * x_2 + x_1 * y_2i + y_1i * x_2 + y_1i * y_2i
$$

现在,我们可以观察两个实部的项和两个虚部的项:

  • $x_1 * x_2$ 是实部的乘积
  • $x_1 * y_2i$ 和 $y_1i * x_2$ 合并在一起,它们也是实部的乘积
  • $y_1i * y_2i$ 是虚部的乘积

所以,实部的乘积是:
$$
实部的乘积 = x_1 * x_2 + (x_1 * y_2i + y_1i * x_2)
$$
然后,我们可以将其中的虚数单位 “i” 的性质 “i^2 = -1” 应用到项中:

$$
x_1 * y_2i + y_1i * x_2 = x_1 * y_2i - y_1 * x_2
$$
所以最终实部的乘积是:
$$
实部的乘积 = x1 * x2 + (x1 * y2i - y1 * x2)
$$

6.9 字符类型

6.9.1 ASCII编码

在理解字符类型之前,我们先看看ASCII码表,如图15所示,ASCII(American Standard Code for Information Interchange)码,即美国信息交换标准编码,是一种使用7位或8位二进制编码来表示文本字符的标准编码系统。它是一种字符编码标准,用于将文本字符(如字母、数字、标点符号和控制字符)映射到二进制数字,以便计算机能够理解和处理它们。

以下是 ASCII 码的一些重要特点:

  1. 字符表示:ASCII 码为每个字符分配一个唯一的数字值,范围从 0 到 127。这些字符包括大写字母、小写字母、数字、标点符号、控制字符和一些特殊字符。

  2. 7位编码:最初的 ASCII 标准使用7位编码,从 0 到 127,用于表示128个不同的字符。这意味着一个 ASCII 字符可以用7位二进制数表示。

  3. 扩展的 ASCII:随着计算机和国际化的发展,扩展的 ASCII 标准(如 ISO-8859)使用8位编码,以支持更多字符集,包括不同语言中的特殊字符。

  4. Unicode:尽管 ASCII 是一种重要的字符编码标准,但它主要用于英语文本。但在现实生活中,对于不同的语言文化来说,需要的编码来一一对应。因此,通常使用 Unicode 编码标准,它支持全球范围内的字符。

  5. 字符映射:ASCII 将每个字符映射到一个整数值,例如,字母 “A” 的 ASCII 值是 65,而数字 “0” 的 ASCII 值是 48。

ASCII 码是计算机和通信系统中的基本字符编码,使得计算机能够存储、传输和显示文本信息。虽然它最初用于英语文本,但在许多应用中仍然广泛使用。Unicode 包括了ASCII 码中的字符,并且扩展了字符集,以满足不同语言和文化的需求。

ASCII Code – VLSIFacts

图16 Ascii码表
转义符 含义 Unicode值
\b 退格(backspace) \u0008
\n 换行 \u000a
\r 回车 \u000d
\t 制表符(tab) \u0009
\“ 双引号 \u0022
\‘ 单引号 \u0027
\ 反斜杠 \u005c
图17 常见转义字符表

6.9.2 代码示例

格式化输出,以及Unicode字符不能赋值给Byte的代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

func main() {
var a byte = 'A'
fmt.Println(a) // 输出65,对照Ascii码表
fmt.Printf("具体字符:%c", a) // 输出A

/*
❌ 因为‘中’字属于unicode编码
❌ 赋值给byte类型则空间不足
❌ 因此不能将‘中’赋值给zhong
*/
// var zhong byte = '中'
// fmt.Println(zhong)

// \n 换行符
fmt.Print("\n你好:\n今天是星期天!\n")

// \b 退格
fmt.Println("i 💖 u\b 小明")

// \r 光标回到本行的开头,后续输入则按顺序替换原有字符
fmt.Println("abc\r123456789")

// \t 制表符
fmt.Println("姓名\t年龄\t性别")
fmt.Println("小明\t18\t男")
fmt.Println("小红\t21\t女")

// \" 转义后的"与外层的双引号不冲突,\' \\同理
fmt.Println("\"I 💖 Golang\"")
}

6.10 布尔类型

布尔类型是编程语言中的一种基本数据类型,用于表示逻辑值。在Go语言中,布尔类型只有两个可能的值:true(真)和false(假)。布尔类型通常用于条件判断和控制程序的流程,例如在条件语句(if语句)、循环语句和逻辑运算中。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
var b bool = 3 > 4

fmt.Println("3 大于 4 吗?", b)
}

6.11 字符串类型

在Go语言中,字符串类型被表示为双引号 " 或反引号 ``` 中的文本。

以下是一些关于Go语言中字符串类型的特点和操作:

  1. 不可变性:在Go语言中,字符串是不可变的,意味着一旦创建,它们的内容不能被更改。如果需要修改字符串,通常需要创建一个新的字符串。
  2. 字符串长度:可以使用内置的 len() 函数来获取字符串的长度,即字符的数量。

6.11.1 常见的字符串操作函数

以下Go语言中strings字符串处理模块的常用函数,以及它们的说明:

函数 说明
strings.Clone 复制一个字符串的副本。
strings.Compare 比较两个字符串,返回比较的结果。
strings.Contains 检查一个字符串是否包含另一个字符串。
strings.ContainsAny 检查一个字符串是否包含任何给定的字符集合中的字符。
strings.ContainsFunc 检查一个字符串是否包含满足给定函数条件的字符。
strings.ContainsRune 检查一个字符串是否包含指定的Unicode字符。
strings.Count 计算一个字符串中指定子串的出现次数。
strings.Cut 切割,并返回切断后的 前后部分 和 是否能切割标识
strings.CutPrefix 移除字符串的前缀。
strings.CutSuffix 移除字符串的后缀。
strings.EqualFold 不区分大小写地比较两个字符串是否相等。
strings.Fields 将字符串拆分为一个字符串切片,根据空格分隔。
strings.FieldsFunc 根据自定义函数将字符串拆分为字符串切片。
strings.HasPrefix 检查字符串是否以指定的前缀开头。
strings.HasSuffix 检查字符串是否以指定的后缀结尾。
strings.Index 返回字符串中指定子串的第一次出现的位置。
strings.IndexAny 返回字符串中任何指定字符中的第一次出现的位置。
strings.IndexByte 返回字符串中指定字节的第一次出现的位置。
strings.IndexFunc 返回字符串中满足自定义函数条件的字符的第一次出现的位置。
strings.IndexRune 返回字符串中指定Unicode字符的第一次出现的位置。
strings.Join 使用指定的分隔符将字符串切片连接成一个字符串。
strings.LastIndex 返回字符串中指定子串的最后一次出现的位置。
strings.LastIndexAny 返回字符串中任何指定字符中的最后一次出现的位置。
strings.LastIndexByte 返回字符串中指定字节的最后一次出现的位置。
strings.LastIndexFunc 返回字符串中满足自定义函数条件的字符的最后一次出现的位置。
strings.Map 将字符串中的每个字符映射到一个新字符。
strings.Repeat 复制字符串并连接多次以生成新字符串。
strings.Replace 替换字符串中的所有匹配子串为指定的新子串。
strings.ReplaceAll 替换字符串中的所有匹配子串为指定的新子串。
strings.Split 将字符串根据指定的分隔符拆分为字符串切片。
strings.SplitAfter 将字符串根据指定的分隔符拆分为字符串切片,并保留分隔符。
strings.SplitAfterN 将字符串根据指定的分隔符拆分为字符串切片,最多拆分 n 次。
strings.SplitN 将字符串根据指定的分隔符拆分为字符串切片,最多拆分 n 次。
strings.Title 将字符串中的每个单词的首字母大写。
strings.ToLower 将字符串中的所有字符转换为小写。
strings.ToLowerSpecial 将字符串中的所有字符使用指定的字符映射表转换为小写。
strings.ToTitle 将字符串中的所有字符转换为标题格式。
strings.ToTitleSpecial 将字符串中的所有字符使用指定的字符映射表转换为标题格式。
strings.ToUpper 将字符串中的所有字符转换为大写。
strings.ToUpperSpecial 将字符串中的所有字符使用指定的字符映射表转换为大写。
strings.ToValidUTF8 修复包含无效UTF-8编码的字符串,并返回有效的UTF-8编码字符串。
strings.Trim 移除字符串的前导和尾随指定字符集的字符。
strings.TrimFunc 移除字符串的前导和尾随满足给定函数条件的字符。
strings.TrimLeft 移除字符串的前导指定字符集的字符。
strings.TrimLeftFunc 移除字符串的前导满足给定函数条件的字符。
strings.TrimPrefix 移除字符串的前导指定前缀。
strings.TrimRight 移除字符串的尾随指定字符集的字符。
strings.TrimRightFunc 移除字符串的尾随满足给定函数条件的字符。
strings.TrimSpace 移除字符串的前导和尾随空白字符。
strings.TrimSuffix 移除字符串的尾随指定后缀。

这些函数提供了广泛的字符串处理功能,可以用于搜索、替换、拆分、连接、大小写转换和修剪等操作。根据具体需求,你可以选择适当的函数来处理字符串。

6.11.2 Println字符串换行输出函数

字符串换行输出

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
// 字符串定义
var hello string = "world"
fmt.Println(hello)
}

输出结果:

1
world

6.11.3 字符串加法拼接

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
var s1 string = "你好吗?"
var s2 string = "小蓝"
fmt.Println(s1 + s2)
}

输出结果:

1
你好吗?小蓝

6.11.4 通过下标进行获取字符

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
var s3 string = "abcdefg"
fmt.Printf("获取的字符是:%c\n", s3[2])
}

输出结果:

1
获取的字符是:c

6.11.5 多行字符串输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func main() {
// 多行字符串
var s4 string = `
静夜思
床前明月光,疑是地上霜。
举头望明月,低头思故乡。
`
fmt.Println(s4)
}

输出结果:

1
2
3
       静夜思
床前明月光,疑是地上霜。
举头望明月,低头思故乡。

6.11.6 字符串切片

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
s6 := "Hello, World!"
s7 := s6[7:12] // s8 的值是 "World"
fmt.Println("切片结果:", s7)
}

输出结果:

1
切片结果: World

6.11.7 Clone字符串克隆函数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strings"
)

func main() {
var s8 string = "abc"
copys8 := strings.Clone(s8)
fmt.Println("克隆后的结果:", copys8)
}

输出结果:

1
克隆后的结果: abc

6.11.8 Compar字符串对比函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"strings"
)

func main() {
// 字符串对比
// 输出-1,因为a、b的ascii分别编码是97、98,所以a比b少输出-1
fmt.Println("a > b吗?", strings.Compare("a", "b"))
// 输出0, 因为a、b的ascii编码都是97,所以相等输出0
fmt.Println("a = a吗?", strings.Compare("a", "a"))
// 输出1, 因为a、b的ascii分别编码是97、98,所以b比a大输出1
fmt.Println("b > a吗?", strings.Compare("b", "a"))
}

输出结果:

1
2
3
a > b吗? -1
a = a吗? 0
b > a吗? 1

6.11.9 Contains函数

字符串是否包含另一个字符串

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("seafood是否包含foo:", strings.Contains("seafood", "foo"))
}

输出结果:

1
seafood是否包含foo: true

6.11.10 ContainsAny函数

字符串是否包含任一这些子字符串

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("字符串是否包含任一这些子字符串:", strings.ContainsAny("ure", "ui"))
}

输出结果:

1
字符串是否包含任一这些子字符串: true

6.11.11 ContainsFunc自定义检查包含函数

根据回调函数返回值来检查字符是否是大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"strings"
)

func main() {
isUpperCase := func(r rune) bool {
return 'A' <= r && r <= 'Z'
}
result := strings.ContainsFunc("123A456", isUpperCase)
fmt.Println("是否包含大写字母:", result)
}

输出结果:

1
是否包含大写字母: true

6.11.12 ContainsRune函数

使用 ContainsRune 函数检查字符串是否包含指定的 Unicode 字符

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strings"
)

func main() {
s9 := "Hello, 世界" // 包含英文和中文字符
contains := strings.ContainsRune(s9, '界')
fmt.Println("是否包含 '界':", contains)
}

输出结果:

1
是否包含 '界': true

6.11.13 Count字符统计函数

统计字符出现次数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("e出现次数:", strings.Count("cheese", "e"))
}

输出结果:

1
e出现次数: 3

6.11.14 Cut函数

切割,并返回切断后的 前后部分 和 是否能切割标识

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
before1, after1, found1 := strings.Cut("Hello, World!", ", ")
fmt.Printf("前部分剩下:%q, 后部分剩下:%q, 能否切割: %v\n", before1, after1, found1)
}

输出结果:

1
前部分剩下:"Hello", 后部分剩下:"World!", 能否切割: true

6.11.15 CutPrefix切割字符串头部函数

切割前缀

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
after2, found2 := strings.CutPrefix("Hello, World!", "Hel")
fmt.Printf("剩下:%q, 能否切割: %v\n", after2, found2)
}

输出结果:

1
剩下:"lo, World!", 能否切割: true

6.11.16 CutSuffix切割字符串尾部函数

切割后缀

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
before3, found3 := strings.CutSuffix("Hello, World!", "rld!")
fmt.Printf("剩下:%q, 能否切割: %v\n", before3, found3)
}

输出结果:

1
剩下:"Hello, Wo", 能否切割: true

6.11.17 EqualFold比较字符串

使用 EqualFold 函数比较字符串,不区分大小写

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
equal := strings.EqualFold("Hello, World!", "hello, world!")
fmt.Println("是否相等:", equal)
}

输出结果:

1
是否相等: true

6.11.18 Fields以空格分割

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
words1 := strings.Fields("Hello World Golang")
fmt.Println("分割后的单词切片:", words1)
}

输出结果:

1
分割后的单词切片: [Hello World Golang]

6.11.19 FieldsFunc自定义分割函数

根据回调函数返回值来拆分字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
// 使用 FieldsFunc 函数根据数字字符拆分字符串
isDigit := func(r rune) bool {
return unicode.IsDigit(r)
}
words2 := strings.FieldsFunc("Hello123Wor6ld456Golang", isDigit)
fmt.Println("根据数字字符拆分后的切片:", words2)
}

输出结果:

1
根据数字字符拆分后的切片: [Hello Wor ld Golang]

6.11.20 HasPrefix是否包含前缀

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("是否包含前缀: ", strings.HasPrefix("Gopher", "Go"))
// 因为空前缀在字符串的最前面因此也属于包含
fmt.Println("是否包含空前缀: ", strings.HasPrefix("Gopher", ""))
}

输出结果:

1
2
是否包含前缀:  true
是否包含空前缀: true

6.11.21 HasSuffix是否包含后缀

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("是否包含后缀: ", strings.HasSuffix("Amigo", "go"))
}

输出结果:

1
是否包含后缀:  true

6.11.22 Index寻找字符串并返回下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("ken的下标位置:", strings.Index("chicken", "ken"))
}

输出结果:

1
ken的下标位置: 4

6.11.23 IndexAny寻找任一子字符串并返回下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("包含aeiouy任一字母,且下标位置为:", strings.IndexAny("chicken", "aeiouy"))
}

输出结果:

1
包含aeiouy任一字母,且下标位置为: 2

6.11.24 IndexByte寻找某个字节并返回下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("查找单个字符g, 下标为:", strings.IndexByte("golang", 'a'))
}

输出结果:

1
查找单个字符g, 下标为: 3

6.11.25 IndexFunc根据自定义函数寻找目标并返回下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
// 根据回调函数返回值来进行字符串查找,并返回其下标
f := func(c rune) bool {
return unicode.Is(unicode.Han, c)
}
fmt.Println("\"Hello, 世界\"字符串是否包含汉字?", strings.IndexFunc("Hello, 世界", f))
}

输出结果:

1
"Hello, 世界"字符串是否包含汉字? 7

6.11.26 IndexRune寻找单个unicode字符并返回下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("✨的位置在:", strings.IndexRune("我爱你,小✨✨", '✨'))
}

输出结果:

1
✨的位置在: 15

6.11.27 Join字符串拼接

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
)

func main() {
s := []string{"foo", "bar", "baz"}
fmt.Println("字符串拼接结果:", strings.Join(s, ", "))
}

输出结果:

1
字符串拼接结果: foo, bar, baz

6.11.28 LastIndex寻找最后子字符并返回下标

查找子字符串最后出现的位置,并返回其下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("go最后出现在:", strings.LastIndex("go gopher", "go"))
}

输出结果:

1
go最后出现在:3

6.11.29 LastIndexAny寻找任一最后子字符并返回下标

查找go任一字符最后出现的位置,并返回其下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("g或o最后出现在:", strings.LastIndexAny("go gopher", "go"))
}

输出结果:

1
g或o最后出现在: 4

6.11.30 LastIndexByte寻找最后出现的字节并返回下标

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("最后出现的字节r:", strings.LastIndexByte("Hello, world", 'r'))
}

输出结果:

1
最后出现的字节r: 9

6.11.31 LastIndexFunc根据自定义函数寻找并返回下标

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
fmt.Println("数字的位置为:", strings.LastIndexFunc("go 123", unicode.IsNumber))
}

输出结果:

1
数字的位置为: 5

6.11.32 Map将某个字符映射成另一字符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"strings"
)

func main() {
mapFunc := func(r rune) rune {
return r + 1
}
newStr := strings.Map(mapFunc, "abc")
fmt.Println("映射后的结果:", newStr)
}

输出结果:

1
映射后的结果: bcd

6.11.33 Repeat重复字符串函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("说十句我爱你:", strings.Repeat("我爱你", 10))
}

输出结果:

1
说十句我爱你: 我爱你我爱你我爱你我爱你我爱你我爱你我爱你我爱你我爱你我爱你

6.11.34 Replace替换字符串函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("我恨你 替换成:", strings.Replace("我恨你", "恨", "爱", 1))
}

输出结果:

1
我恨你 替换成: 我爱你

6.11.35 ReplaceAll替换全部字符串

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("我很 坏坏坏 替换成:", strings.ReplaceAll("我很 坏坏坏", "坏", "好"))
}

输出结果:

1
我恨你 替换成: 我爱你

6.11.36 Split字符串分割函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Printf("字符串分割结果:%q\n", strings.Split("a,b,c", ","))
}

输出结果:

1
字符串分割结果:["a" "b" "c"]

6.11.37 SplitAfter分割字符串保留逗号作为结尾

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Printf("字符串分割保留结尾结果:%q\n", strings.SplitAfter("a,b,c", ","))
}

输出结果:

1
字符串分割保留结尾结果:["a," "b," "c"]

6.11.38 SplitN按次数分割函数

将字符串按次数分割

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Printf("字符串按次数分割结果:%q\n", strings.SplitN("aaaaaaaaaaaaaa,b,c,d,e,f", ",", 4))
}

输出结果:

1
字符串按次数分割结果:["aaaaaaaaaaaaaa" "b" "c" "d,e,f"]

6.11.39 SplitAfterN按次数分割保留逗号作为结尾

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Printf("字符串按次数分割并保留逗号结果:%q\n", strings.SplitAfterN("aaaaaaaaaaaaaa,b,c,d,e,f", ",", 4))
}

输出结果:

1
字符串按次数分割并保留逗号结果:["aaaaaaaaaaaaaa," "b," "c," "d,e,f"]

6.11.40 ToLower转换成小写

字符串转小写

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("字符串转小写:", strings.ToLower("Gopher"))
}

输出结果:

1
字符串转小写: gopher

6.11.41 ToLowerSpecial特殊字符串转小写函数

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
fmt.Println("土耳其语转小写:", strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş"))
}

输出结果:

1
土耳其语转小写: önnek iş

6.11.42 ToTitle首字母大写函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("首字母大写:", strings.ToTitle("i love you"))
}

输出结果:

1
首字母大写: I LOVE YOU

6.11.43 ToTitleSpecial特殊字符串首字母大写函数

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
fmt.Println("土耳其语首字母大写:", strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))
}

输出结果:

1
土耳其语首字母大写: DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR

6.11.44 ToUpper转换成大写

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("字符串转大写:", strings.ToUpper("Gopher"))
}

输出结果:

1
字符串转大写: GOPHER

6.11.45 ToUpperSpecial特殊字符串转大写函数

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
fmt.Println("土耳其语转大写:", strings.ToUpperSpecial(unicode.TurkishCase, "Önnek İş"))
}

输出结果:

1
土耳其语转大写: ÖNNEK İŞ

6.11.46 ToValidUTF8修复无效UTF-8序列

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strings"
)

func main() {
str := "Hello, \xed\xa0\x80 World!" // 包含一个无效的 UTF-8 序列
validStr := strings.ToValidUTF8(str, "?")
fmt.Println("修复无效的UTF-8序列:", validStr)
}

输出结果:

1
修复无效的UTF-8序列: Hello, ? World!

6.11.47 Trim去除两边的特殊字符函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("去除两边的空格或-:", strings.Trim(" Hello, Gophers---", " -"))
}

输出结果:

1
去除两边的空格或-: Hello, Gophers

6.11.48 TrimFunc根据自定义函数去除两边的特殊字符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"strings"
"unicode"
)

func main() {
trimFunc := func(r rune) bool {
return unicode.IsSpace(r)
}
fmt.Println("去除两边的空格: ", strings.TrimFunc(" Hello, World! ", trimFunc))
}

输出结果:

1
去除两边的空格:  Hello, World!

6.11.49 TrimLeft去除左边的指定字符函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("去除左边的指定字符:", strings.TrimLeft("- - - - - - - - - - Hello, Gophers- - - - - - -", "- "))
}

输出结果:

1
去除左边的指定字符: Hello, Gophers- - - - - - -

6.11.50 TrimLeftFunc根据回调函数的返回值去除左边的指定字符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strings"
)

func main() {
lefttrimFunc := func(r rune) bool {
return r == ' ' || r == '-'
}
fmt.Println("根据回调函数的返回值去除左边的指定字符:", strings.TrimLeftFunc("- - - - - - - - - - Hello, Gophers- - - - - - -", lefttrimFunc))
}

输出结果:

1
根据回调函数的返回值去除左边的指定字符: Hello, Gophers- - - - - - -

6.11.51 TrimPrefix去除字符串前缀函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("去除字符串前缀:", strings.TrimPrefix("https://google.com", "https://"))
}

输出结果:

1
去除字符串前缀: google.com

6.11.52 TrimRight去除右边的指定字符数据函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("去除右边的指定字符:", strings.TrimRight("- - - - - - - - - - Hello, Gophers- - - - - - -", "- "))
}

输出结果:

1
去除右边的指定字符: - - - - - - - - - - Hello, Gophers

6.11.53 TrimRightFunc根据回调函数的返回值去除左边的指定字符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strings"
)

func main() {
righttrimFunc := func(r rune) bool {
return r == ' ' || r == '-'
}
fmt.Println("根据回调函数的返回值去除右边的指定字符:", strings.TrimRightFunc("- - - - - - - - - - Hello, Gophers- - - - - - -", righttrimFunc))
}

输出结果:

1
根据回调函数的返回值去除右边的指定字符: - - - - - - - - - - Hello, Gophers

6.11.54 TrimSpace 去除\t \n \r这种空格函数

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strings"
)

func main() {
fmt.Println("去除\\t \\n \\r这种空格:", strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))
}

输出结果:

1
去除\t \n \r这种空格: Hello, Gophers

6.11.3 Builder、Reader、Replacer说明

除了以上的函数外,strings 包提供了用于处理字符串的函数和类型。其中,type Buildertype Readertype Replacerstrings 包中的类型,用于不同的字符串处理任务。

  1. type Builder:

    • Builder 类型是一个用于构建字符串的可变缓冲区。

    • 它通常用于需要频繁构建字符串的情况,以避免不断创建新的字符串对象。

    • Builder 具有方法来添加字符、字符串和其他数据到内部缓冲区,最后可以将构建好的字符串获取出来。

    • 以下是 strings.Builder 类型的相关函数的说明和整理成表格:

      函数 说明
      Cap 返回 strings.Builder 内部缓冲区的容量。
      Grow strings.Builder 的内部缓冲区扩大以容纳指定的字节数。
      Len 返回 strings.Builder 中已经写入的字节数。
      Reset 重置 strings.Builder,清空其内容,但保留底层缓冲区。
      String strings.Builder 的内容转换为字符串并返回。
      Write 将字节切片写入 strings.Builder
      WriteByte 将单个字节写入 strings.Builder
      WriteRune 将单个 Unicode 字符写入 strings.Builder
      WriteString 将字符串写入 strings.Builder
  2. type Reader:

    • Reader 类型是用于从字符串中读取数据的读取器。

    • 它实现了 io.Reader 接口,可以用于从字符串中按字节或字符读取数据。

    • 以下是 strings.Reader 类型相关函数的说明整理成表格:

      函数 说明
      Len 返回剩余未读取的数据长度。
      Read strings.Reader 中读取数据到字节切片。
      ReadAt 从指定位置开始,从 strings.Reader 中读取数据到字节切片。
      ReadByte strings.Reader 中读取一个字节。
      ReadRune strings.Reader 中读取一个 Unicode 字符。
      Reset 重置 strings.Reader,将读取位置重置到开头。
      Seek 设置 strings.Reader 的读取位置到指定的偏移位置。
      Size 返回 strings.Reader 中数据的总长度。
      UnreadByte 将最后一个读取的字节回退,使其可以再次被读取。
      UnreadRune 将最后一个读取的 Unicode 字符回退,使其可以再次被读取。
      WriteTo strings.Reader 的内容写入实现了 io.Writer 接口的目标中。
  3. type Replacer:

    • Replacer 类型用于替换字符串中的指定子串。

    • 它允许你指定要替换的子串和替代品,然后可以一次性替换多个出现。

    • 以下是 strings.Replacer 类型的主要方法和说明:

      方法 说明
      Replace(s string) string 将替代规则应用于输入字符串 s,并返回替代后的结果字符串。
      WriteString(w io.Writer, s string) (n int, err error) 将替代规则应用于输入字符串 s,并将结果写入实现了 io.Writer 接口的目标 w。 返回写入的字节数和可能的错误。

6.11.4 type Builder代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"fmt"
"strings"
)

func main() {
// 创建一个新的 strings.Builder
var builder strings.Builder

fmt.Println("before容量:", builder.Cap())
/*
输出:before容量:0
*/

// 向 Builder 添加字符串
builder.WriteString("Hello, ")
builder.WriteString("World!")

// 将单个Unicode字符写入
builder.WriteByte('-')
builder.WriteRune('💖')

fmt.Println("after容量:", builder.Cap())
/*
输出:after容量:16
*/

// 扩大容量
builder.Grow(8)

// 由于 Grow 方法内部的实现可能会选择适当的方式来增加容量,它可能会增加更多的容量,以减少频繁的扩容操作,提高性能。
fmt.Println("扩大容量后:", builder.Cap())
/*
输出:扩大容量后:40
*/

fmt.Println("已写入的字符串长度:", builder.Len())
/*
输出:已写入的字符串长度:13
*/

// 将 Builder 中的内容转换为字符串
result := builder.String()

// 重置
builder.Reset()
fmt.Println("重置后容量:", builder.Cap())
fmt.Println("重置后字符串长度:", builder.Len())

fmt.Println(result)
/*
输出:Hello, World!-💖
*/
}

6.11.5 type Reader代码示例

6.11.5.1 读取到缓存区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"io"
"strings"
)

func main() {
// 创建一个字符串
text := "111112222233333"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 创建一个缓冲区来接收读取的数据
buffer := make([]byte, 5)

// 循环读取字符串数据
for {
n, err := reader.Read(buffer)
if err == io.EOF {
break
}
fmt.Printf("读取了 %d 字节: %s\n", n, string(buffer[:n]))
}

/*
输出:
读取了 5 字节: 11111
读取了 5 字节: 22222
读取了 5 字节: 33333
*/
}
6.11.5.2 在某个位置开始读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"io"
"strings"
)

func main() {
// 创建一个字符串
text := "111112222233333"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 创建一个缓冲区来接收读取的数据
buffer := make([]byte, 3)

// 使用 ReadAt 从指定位置读取数据
var position int64 = 6
n2, err2 := reader.ReadAt(buffer, position)
if err2 != nil {
if err2 == io.EOF {
fmt.Println("已经读取完整个字符串。")
} else {
fmt.Printf("发生错误:%v\n", err2)
}
} else {
fmt.Printf("从位置 %d 开始读取了 %d 字节: %s\n", position, n2, string(buffer[:n2]))
}
}
6.11.5.3 每次读一个字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"strings"
)

func main() {
// 创建一个字符串
text := "Hello, World!"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 逐个字节读取字符串
for {
// 使用 ReadByte 读取一个字节
char, err := reader.ReadByte()

if err != nil {
// 如果到达字符串末尾,会返回 EOF 错误
if err.Error() == "EOF" {
break
}

fmt.Printf("发生错误:%v\n", err)
break
}

fmt.Printf("读取字节: %c\n", char)
}
}

6.11.5.4 每次读一个字节或一个Unicode字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"strings"
)

func main() {
// 创建一个字符串
text := "Hello, 你好💖🤩👉👍🫖😍!"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 逐个 Unicode 字符读取字符串
for {
// 使用 ReadRune 读取一个 Unicode 字符
char, size, err := reader.ReadRune()

if err != nil {
// 如果到达字符串末尾,会返回 EOF 错误
if err.Error() == "EOF" {
break
}

fmt.Printf("发生错误:%v\n", err)
break
}

fmt.Printf("读取字符: %c (字节大小:%d)\n", char, size)
}
}
6.11.5.5 Seek例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"fmt"
"io"
"strings"
)

func main() {
// 创建一个字符串
text := "Hello, World!"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 设置读取位置到第 7 个字节
position := int64(6)
if _, err := reader.Seek(position, io.SeekStart); err != nil {
fmt.Printf("发生错误:%v\n", err)
return
}

// 创建一个缓冲区来接收读取的数据
buffer := make([]byte, 5)

// 从指定位置开始读取数据
n, err := reader.Read(buffer)
if err != nil {
if err == io.EOF {
fmt.Println("已经读取完整个字符串。")
} else {
fmt.Printf("发生错误:%v\n", err)
}
} else {
fmt.Printf("从位置 %d 开始读取了 %d 字节: %s\n", position, n, string(buffer[:n]))
}
}
6.11.5.6 WriteTo例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"os"
"strings"
)

func main() {
// 创建一个字符串
text := "Hello, World!"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// 将数据写入标准输出
n, err := reader.WriteTo(os.Stdout)
if err != nil {
fmt.Printf("发生错误:%v\n", err)
return
}

fmt.Printf("成功写入了 %d 字节到标准输出。\n", n)
}
6.11.5.7 UnreadByte与UnreadRune例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"io"
"strings"
)

func main() {
// 创建一个字符串
text := "Hello, 🫖World!"

// 创建一个 strings.Reader
reader := strings.NewReader(text)

// reader.Seek(int64(4), io.SeekStart)

// 读取并回退最后一个字节
char, err := reader.ReadByte()
if err != nil {
fmt.Printf("发生错误:%v\n", err)
return
}

fmt.Printf("读取字节: %c\n", char)

// 使用 UnreadByte 回退最后一个字节
err = reader.UnreadByte()
if err != nil {
fmt.Printf("回退字节时发生错误:%v\n", err)
return
}

// 再次读取回退的字节
char, err = reader.ReadByte()
if err != nil {
fmt.Printf("再次读取字节时发生错误:%v\n", err)
return
}

fmt.Printf("再次读取字节: %c\n", char)


// =================================================================

reader.Seek(int64(7), io.SeekStart)

// 读取并回退最后一个 Unicode 字符
char2, size2, err2 := reader.ReadRune()
if err != nil {
fmt.Printf("发生错误:%v\n", err2)
return
}

fmt.Printf("读取unicode字符: %c (字节大小:%d)\n", char2, size2)

// 使用 UnreadRune 回退最后一个 Unicode 字符
err2 = reader.UnreadRune()
if err2 != nil {
fmt.Printf("回退unicode字符时发生错误:%v\n", err2)
return
}

// 再次读取回退的 Unicode 字符
char2, size2, err2 = reader.ReadRune()
if err != nil {
fmt.Printf("再次读取unicode字符时发生错误:%v\n", err2)
return
}

fmt.Printf("再次unicode读取字符: %c (字节大小:%d)\n", char2, size2)
}

6.11.6 type Replacer代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"strings"
)

func main() {
// 创建一个 Replacer 实例,用于将 "apple" 替换为 "orange","banana" 替换为 "grape"
r := strings.NewReplacer("apple", "orange", "banana", "grape")

// 定义一个字符串
text := "I have an apple and a banana. An apple a day keeps the doctor away."

// 使用 Replacer 替换字符串中的内容
result := r.Replace(text)

// 打印替换后的字符串
fmt.Println(result)
}

6.12 基本类型默认值

在 Go 语言中,基础类型(如整数、浮点数、布尔等)的默认值通常为零值。以下是一些常见基础类型的默认零值:

  1. 整数类型(int、int8、int16、int32、int64):默认值为 0
  2. 无符号整数类型(uint、uint8、uint16、uint32、uint64):默认值为 0
  3. 浮点数类型(float32、float64):默认值为 0.0
  4. 布尔类型(bool):默认值为 false
  5. 字符串类型(string):默认值为空字符串 ""
  6. 字符类型(rune):默认值为 0(表示空字符,对应 Unicode 中的 NUL 字符)。
  7. 指针类型(如 *int*string):默认值为 nil

需要注意的是,结构体和自定义类型的默认值是其成员的零值,而零值可能不是一个有意义的默认值。因此,在使用结构体和自定义类型时,应根据具体情况自行初始化默认值。

请注意,Go 语言中变量在声明时会自动初始化为其零值,这有助于避免未初始化变量的问题。

即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
// 整数类型默认值是 0
var i int
fmt.Println("整数默认值:", i)

// 浮点数类型默认值是 0.0
var f float64
fmt.Println("浮点数默认值:", f)

// 布尔类型默认值是 false
var b bool
fmt.Println("布尔默认值:", b)

// 字符串类型默认值是空字符串
var s string
fmt.Println("字符串默认值:", s)

// 字符类型 (rune) 默认值是 0 (NUL 字符)
var r rune
fmt.Println("字符 (rune) 默认值:", r)

// 指针类型默认值是 nil
var ptr *int
fmt.Println("指针默认值:", ptr)
}

6.13 基本数据类型的转换

注意:基本数据类型的转换过程中需要考虑转换后的数据溢出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
)

func main() {
var n1 int = 100
// ❌ 不能进行隐式转换
// var n2 float32 = n2
fmt.Println(n1)

// 显式转换
var n2 float32 = float32(n1)
fmt.Printf("它的类型是:%T\n", n2)

// 虽然都是整数,但是由于尺寸不一样,会导致转换的过程中数据溢出,导致数据错误
var n3 int64 = 99999999
var n4 int8= int8(n3)
fmt.Printf("n4的类型是%T, 值是%d\n", n4, n4)

// 进行数学运算的时候,需要确保式子中的自变量类型一样
var n5 int32 = 12
var n6 int64 = int64(n5) + 30
fmt.Printf("n6的类型是%T, 值是%d\n", n6, n6)

// 因为int8是有符号位,所以最大值位127(b1符号位 b111 1111)
// 所以下方式子会造成溢出变成-128
var n7 int64 = 1
var n8 int8 = int8(n7) + 127
fmt.Printf("n8的类型是%T, 值是%d\n", n8, n8)
}

6.14 输出与输出

6.14.1 输出占位符

在Go语言中,您可以使用fmt包来格式化输出。要格式化数字并设置占位符,您可以使用Printf函数。以下是一些常见的数字占位符:

  1. %d: 用于整数,例如,fmt.Printf("%d", 42) 将输出整数42。

  2. %f: 用于浮点数,例如,fmt.Printf("%f", 3.14159) 将输出浮点数3.141590。

  3. %s: 用于字符串,例如,fmt.Printf("%s", "Hello, World") 将输出字符串”Hello, World”。

  4. %x: 用于格式化为十六进制,例如,fmt.Printf("%x", 255) 将输出”ff”。

  5. %o: 用于格式化为八进制,例如,fmt.Printf("%o", 64) 将输出”100”。

  6. %b: 用于格式化为二进制,例如,fmt.Printf("%b", 8) 将输出”1000”。

这些是一些常见的占位符,还有其他占位符可用于不同的数据类型和格式化选项。您可以将这些占位符与其他格式化标志一起使用,以实现更复杂的输出格式。例如,您可以使用 %04d 来输出一个整数,并确保它占据至少4个字符的宽度,不足的部分用零填充。

6.14.1 Append追加

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
bytes := fmt.Append([]byte("old "), " money")
fmt.Println(bytes)
fmt.Println(string(bytes))
}

输出结果:

1
2
[111 108 100 32 102 97 108 99 111 110]
old falcon

6.14.2 Appendf格式化追加

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
b1 := []byte("你好,")
b2 := fmt.Appendf(b1, "%s %d %x %b", "def", 15, 15, 15)
fmt.Println(string(b2))
}

输出结果:

1
你好,def 15 f 1111

6.14.3 Appendln换行追加

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
b1 := []byte("你好")
b2 := fmt.Appendln(b1, "d", "e", "f", 15, 15, 15)
fmt.Printf("Appendln=[%s]", string(b2))
}

输出结果:

1
2
Appendln=[你好d e f 15 15 15
]

6.14.4 Errorf格式化错误输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"errors"
"fmt"
)

func main() {
err1 := errors.New("test error1")
fmt.Printf("type: %T\n", err1)
fmt.Printf("value: %v\n", err1)

fmt.Println("------------------------------------------")

err2 := fmt.Errorf("test error2 %v", "abc bank")
fmt.Printf("type: %T\n", err2)
fmt.Printf("value: %v\n", err2)
}

输出结果:

1
2
3
4
5
type: *errors.errorString
value: test error1
------------------------------------------
type: *errors.errorString
value: test error2 wakuwaku bank

6.14.5 Fprint 与 Fprintf函数

os.Stdout 是 Go 语言标准库中的一个变量,它代表标准输出流。

Fprint是打印内容到标准输出流,Fprintf是格式化输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"os"
)

func main() {
const name, age = "Kim", 22

n, err := fmt.Fprint(os.Stdout, name, " is ", age, " years old.\n")

// The n and err return values from Fprint are
// those returned by the underlying io.Writer.
if err != nil {
fmt.Fprintf(os.Stderr, "Fprint: %v\n", err)
}
fmt.Print(n, " bytes written.\n")
}

输出结果:

1
2
Kim is 22 years old.
21 bytes written

6.14.6 Fscan函数

在文件流中按格式读取数据:

input.txt文件

1
Name: lijunjie Age: 31

main.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"os"
)

func main() {
file, err := os.Open("input.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()

var name string
var age int

// 从文件中按照指定格式读取数据
_, err = fmt.Fscanf(file, "Name: %s Age: %d", &name, &age)
if err != nil {
fmt.Println("Error reading data:", err)
return
}

fmt.Printf("Name: %s\nAge: %d\n", name, age)
}

输出结果:

1
2
Name: lijunjie
Age: 31

6.14.7 Fscanln函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"io"
"strings"
)

func main() {
s := `小明 18 98.5
小红 20 99
`
r := strings.NewReader(s)

var a string
var b int
var c float64
for {
n, err := fmt.Fscanln(r, &a, &b, &c)
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
fmt.Printf("读取列数:%d 第一列:%s 第二列:%d 第三列:%f\n", n, a, b, c)
}
}

输出结果:

1
2
读取列数:3  第一列:小明  第二列:18  第三列:98.500000
读取列数:3 第一列:小红 第二列:20 第三列:99.000000

6.14.8 Print函数

打印输出到标准输出流

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
const name, age = "Kim", 22
fmt.Print(name, " is ", age, " years old.\n")
}

输出结果:

1
Kim is 22 years old.

6.14.9 Printf函数

格式化输出

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
const name, age = "Kim", 22
fmt.Printf("%s is %d years old.\n", name, age)
}

输出结果:

1
Kim is 22 years old.

6.14.10 Println函数

输出文本后面加换行

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
const name, age = "lijunjie", 22
fmt.Println(name, "is", age, "years old.")
}

输出结果:

1
lijunjie is 22 years old.

6.14.11 Scan函数

获取输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
var name string
var age int

fmt.Print("请输入姓名,回车后输入年龄: ")

if n, err := fmt.Scan(&name, &age); err == nil {
fmt.Printf("Name: %s,获取的参数数量:%d", name, n)
}
}

输出结果:

1
2
3
请输入姓名: lijunjie
10
Name: lijunjie,长度:2

6.14.12 Scanf函数

按格式获取输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
var name string
var age int

fmt.Print("请输入姓名和年龄,使用空格分隔: ")

if n, err := fmt.Scanf("%s %d", &name, &age); err == nil {
fmt.Printf("Name: %s, Age: %d,获取的参数数量:%d\n", name, age, n)
} else {
fmt.Println("输入错误:", err)
}
}

输出结果:

1
2
请输入姓名和年龄,使用空格分隔: lijuneji 13
Name: lijuneji, Age: 13,获取的参数数量:2

6.14.13 Scanln函数

用户需要在输入结束后按回车键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
var name string
var age int

fmt.Print("请输入姓名和年龄, 用空格分隔: ")

if n, err := fmt.Scanln(&name, &age); err == nil {
fmt.Printf("Name: %s, Age: %d,获取的参数数量:%d\n", name, age, n)
} else {
fmt.Println("输入错误:", err)
}
}

输出结果:

1
2
请输入姓名和年龄, 用空格分隔: lijunjie 123
Name: lijunjie, Age: 123,获取的参数数量:2

6.14.14 Sprint函数

将输出文本保存到一个字符串中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
name := "Alice"
age := 30

// 使用 fmt.Sprint 格式化文本并保存到字符串
result := fmt.Sprint("Name: ", name, ", Age: ", age)

// 输出结果
fmt.Println(result)
}

输出结果:

1
Name: Alice, Age: 30

6.14.15 Sprintf函数

将格式化输出文本保存到一个字符串中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
name := "Alice"
age := 30

// 使用 fmt.Sprintf 格式化文本并保存到字符串
result := fmt.Sprintf("Name: %s, Age: %d", name, age)

// 输出结果
fmt.Println(result)
}

输出结果:

1
Name: Alice, Age: 30

6.14.16 Sprintln函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func main() {
name := "Alice"
age := 30

// 使用 fmt.Sprintln 格式化文本并保存到字符串,每个参数之间自动添加换行符
result := fmt.Sprintln("Name:", name, "Age:", age)
result += fmt.Sprintln("Name2:", name, "Age2:", age)

// 输出结果
fmt.Println(result)
}

输出结果:

1
2
Name: Alice Age: 30
Name2: Alice Age2: 30

6.14.17 Sscan函数

从字符串中按照指定格式读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

func main() {
inputString := "Alice 30"

var name string
var age int

// 使用 fmt.Sscan 从字符串中按照指定格式读取数据
n, err := fmt.Sscan(inputString, &name, &age)

if err == nil {
fmt.Printf("Name: %s, Age: %d,获取的参数数量:%d\n", name, age, n)
} else {
fmt.Println("读取错误:", err)
}
}

输出结果:

1
Name: Alice, Age: 30,获取的参数数量:2

6.14.18 Sscanf函数

格式化解释字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

func main() {
inputString := "Alice 30"

var name string
var age int

// 使用 fmt.Sscanf 从字符串中按照指定格式读取数据
n, err := fmt.Sscanf(inputString, "%s %d", &name, &age)

if err == nil {
fmt.Printf("Name: %s, Age: %d,获取的参数数量:%d\n", name, age, n)
} else {
fmt.Println("读取错误:", err)
}
}

输出结果:

1
Name: Alice, Age: 30,获取的参数数量:2

6.14.19 Sscanln函数

从字符串中读取数据,遇到换行就停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
)

func main() {
inputString := "Alice 30\nxiaohong 13"

var name string
var age int

// 使用 fmt.Sscanln 从字符串中按照指定格式读取数据
n, err := fmt.Sscanln(inputString, &name, &age)

if err == nil {
fmt.Printf("Name: %s, Age: %d,获取的参数数量:%d\n", name, age, n)
} else {
fmt.Println("读取错误:", err)
}
}

输出结果:

1
Name: Alice, Age: 30,获取的参数数量:2

6.15 strconv基本类型转换包

strconv 是 Go 编程语言中的一个标准包,用于字符串和基本数据类型之间的相互转换。它的名字是 “string conversion” 的缩写。strconv 包提供了一系列函数,用于将不同类型的数据转换为字符串,以及将字符串解析为不同类型的数据。

6.15.1 AppendBool函数

往buf里追加true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"strconv"
)

func main() {
var buf []byte

buf = strconv.AppendBool(buf, true)
buf = strconv.AppendBool(buf, true)
buf = strconv.AppendBool(buf, true)
fmt.Println(string(buf))
}

输出结果:

1
truetruetrue

6.15.2 AppendFloat函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"strconv"
)

func main() {
b32 := []byte("float32:")
/*
b32字节数组中。具体参数解释如下:
b32:要附加到的目标字节数组。
3.1415926535:要转换的浮点数。
'E':指定浮点数的格式,这里使用科学计数法。
-1:小数的位数,-1表示无限精度,即根据浮点数的实际精度来转换。
32:表示要转换为float32类型的格式。
*/
b32 = strconv.AppendFloat(b32, 3.1415926535, 'E', -1, 32)
fmt.Println(string(b32))
}

输出结果:

1
float32:3.1415927E+00

6.15.3 AppendInt函数

将整数拼接到byte中去,具体参数解释如下:
b10:要附加到的目标字节数组。
-42:要转换的整数。
10:表示要使用十进制表示。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
b10 := []byte("int (base 10):")
b10 = strconv.AppendInt(b10, -42, 10)
fmt.Println(string(b10))
}

输出结果:

1
int (base 10):-42

6.15.4 AppendQuote函数

将一个字符串转换为带引号的字符串,并且附加到b缓冲中

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
b := []byte("quote:")
b = strconv.AppendQuote(b, `"小明 💖 小红"`)
fmt.Println(string(b))
}

输出结果:

1
quote:"\"小明 💖 小红\""

6.15.5 AppendQuoteRune函数

将一个字符串转换为unicode的字符串,并且附加到b2缓冲中

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
b2 := []byte("rune:")
b2 = strconv.AppendQuoteRune(b2, '🥰')
fmt.Println(string(b2))
}

输出结果:

1
rune:'🥰'

6.15.6 AppendQuoteRuneToASCII函数

将一个字符串转换为unicode的字符串,并且以Ascii字符串附加到b3缓冲中

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
b3 := []byte("rune (ascii):")
b3 = strconv.AppendQuoteRuneToASCII(b3, '🏆')
fmt.Println(string(b3))
}

输出结果:

1
rune (ascii):'\U0001f3c6

6.15.7 AppendQuoteToGraphic函数

追加字符串含双引号的到另一字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"strconv"
)

func main() {
outputBytes := []byte("Hello") // 用于存储转义后的字符串的字节切片
inputString := "世界!" // 输入的字符串

// 使用 strconv.AppendQuoteToGraphic 将字符串转义并追加到字节切片中
outputBytes = strconv.AppendQuoteToGraphic(outputBytes, inputString)

// 将转义后的字节切片转换为字符串并打印出来
outputString := string(outputBytes)
fmt.Println(outputString)
}

输出结果:

1
Hello"世界!"

6.15.8 AppendUint函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"strconv"
)

func main() {
// 追加无符号十进制到字符串末尾
b10 := []byte("uint (base 10):")
b10 = strconv.AppendUint(b10, 42, 10)
fmt.Println(string(b10))

// 追加无符号十六进制到字符串末尾
b16 := []byte("uint (base 16):")
b16 = strconv.AppendUint(b16, 42, 16)
fmt.Println(string(b16))
}

输出结果:

1
2
uint (base 10):42
uint (base 16):2a

6.15.9 Atoi函数

Atoi等价于ParseInt(s, 10, 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strconv"
)

func main() {
v := "10"
if s, err := strconv.Atoi(v); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}

输出结果:

1
int, 10

6.15.10 CanBackquote函数

CanBackquote 是 Go 语言中的一个内置常量,它用于标识字符串字面值是否支持反引号(`)包围的原始字符串字面值。

反引号包围的字符串字面值允许包含换行符和特殊字符,不进行转义。

在 Go 中,如果一个字符串字面值包含了反引号,则它被认为是一个原始字符串字面值,这时 CanBackquote 常量的值为 true

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"strconv"
)

func main() {
fmt.Println(strconv.CanBackquote("`can't backquote this`"))
}

输出结果:

1
false

6.15.11 FormatBool函数

格式化布尔值为字符串

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
vb1 := true
s := strconv.FormatBool(vb1)
fmt.Printf("%T, %v\n", s, s)
}

输出结果:

1
string, true

6.15.12 FormatComplex格式化复数

格式化复数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
complexNumber := complex(3.0, 4.0)
s2 := strconv.FormatComplex(complexNumber, 'f', -1, 64)
fmt.Println(s2)
}

输出结果:

1
(3+4i)

6.15.13 FormatFloat函数

FormatFloat 格式化输出浮点数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
vb3 := 3.1415926535
s32 := strconv.FormatFloat(vb3, 'e', -1, 32)
fmt.Printf("%T, %v\n", s32, s32)
}

输出结果:

1
string, 3.1415927e+00

6.15.14 FormatInt函数

FormatInt 格式化输出整数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
vb4 := int64(-42)
s10 := strconv.FormatInt(vb4, 10)
fmt.Printf("%T, %v\n", s10, s10)
}

输出结果:

1
string, -42

6.15.15 FormatUint函数

FormatUint 格式化输出无符号整数

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
vb5 := uint64(42)
s11 := strconv.FormatUint(vb5, 10)
fmt.Printf("%T, %v\n", s11, s11)
}

输出结果:

1
string, 42

6.15.16 IsGraphic函数

是否是图案

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
shamrock := strconv.IsGraphic('☘')
fmt.Println(shamrock)
}

输出结果:

1
true

6.15.17 IsPrint函数

IsPrint 报告该 rune 是否被 Go 定义为可打印

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
c := strconv.IsPrint('\007')
fmt.Println(c)
}

输出结果:

1
false

5.15.18 Itoa 函数

格式化输出 int

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
i1 := 10
s12 := strconv.Itoa(i1)
fmt.Printf("%T, %v\n", s12, s12)
}

输出结果:

1
string, 10

6.15.18 ParseBool函数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strconv"
)

func main() {
vb6 := "true"
if s, err := strconv.ParseBool(vb6); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}

输出结果:

1
bool, true

6.15.19 FormatComplex函数

使用 FormatComplex 将复数格式化为字符串(十进制格式)

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"strconv"
)

func main() {
complexNumber1 := complex(3.0, 4.0) // 3 + 4i
decimalString := strconv.FormatComplex(complexNumber1, 'f', -1, 64)
fmt.Println("Decimal format:", decimalString)
}

输出结果:

1
Decimal format: (3+4i)

6.15.20 ParseFloat函数

转换成浮点数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strconv"
)

func main() {
vb7 := "3.1415926535"
if s, err := strconv.ParseFloat(vb7, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}

输出结果:

1
float64, 3.1415927410125732

6.15.21 ParseInt函数

转换成整数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strconv"
)

func main() {
v32 := "-354634382"
if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}

输出结果:

1
int64, -354634382

6.15.22 ParseUint函数

转换成无符号整数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"strconv"
)

func main() {
v33 := "42"
if s, err := strconv.ParseUint(v33, 10, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}

输出结果:

1
uint64, 42

6.15.23 Quote函数

双引号括起来

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s13 := strconv.Quote(`"Fran & Freddie's Diner ☺"`)
fmt.Println(s13)
}

输出结果:

1
"\"Fran & Freddie's Diner\t☺\""

6.15.24 QuoteRune函数

转换为Rune数据类型(双引号括起来)

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s14 := strconv.QuoteRune('☺')
fmt.Println(s14)
}

输出结果:

1
'☺'

6.15.25 QuoteRuneToASCII函数

Rune类型都转换成Ascii码(双引号括起来)

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s15 := strconv.QuoteRuneToASCII('☺')
fmt.Println(s15)
}

输出结果:

1
'\u263a'

6.15.26 QuoteRuneToGraphic函数

Rune类型都转换成可打印字符(双引号括起来)

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s16 := strconv.QuoteRuneToGraphic(' ')
fmt.Println(s16)
}

输出结果:

1
'\t'

6.15.27 QuoteToASCII函数

将引号转义成ASCII

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s17 := strconv.QuoteToASCII(`"Fran & Freddie's Diner ☺"`)
fmt.Println(s17)
}

输出结果:

1
"\"Fran & Freddie's Diner\t\u263a\""

6.15.28 QuoteToGraphic函数

将双引号转换成可打印字符(双引号括起来)

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s18 := strconv.QuoteToGraphic(" ")
fmt.Println(s18)
}

输出结果:

1
"\t"

6.15.29 QuotedPrefix函数

包含双引号开头的才符合条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"strconv"
)

func main() {
str1 := `"Hello, World"`
str2 := `'你好世界'`
if length1, err1 := strconv.QuotedPrefix(str1); err1 == nil {
fmt.Println(length1)
}
if length2, err2 := strconv.QuotedPrefix(str2); err2 == nil {
fmt.Println(length2)
}
}

输出结果:

1
"Hello, World"

6.15.30 Unquote函数

引号反转义

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"strconv"
)

func main() {
s, err2 := strconv.Unquote("\"The string must be either double-quoted\"")
fmt.Println(s, err2)
}

输出结果:

1
The string must be either double-quoted <nil>

6.16 指针

指针是一种特殊类型的变量,它用于存储其他变量的内存地址。

如下图所示中的变量a本身是有内存地址的,也就是0002, 而它的内容是1008,是另一个内存地址。我们称a变量为指针变量。就像日常生活中的卡片是一样的,里面写着一个真实的地址。

1699260161555

图18 变量、内存地址、值的关系

6.16.1 指针声明

声明一个指针,可以使用*符号加类型。

为什么要加上类型?因为指针本身存储的仅仅是一个地址,如果不加类型的话,是不能的值指针所指的区域长度。

如下代码所示:

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
var p *int // 声明一个整数指针
fmt.Printf("p 的地址是:%p\n", p)
}

输出结果:

1
p 的地址是:0x0

默认情况下,未初始化的指针变量会被设置为nil,即空指针,其内存地址为0。

6.16.2 获取变量地址

我们可以通过&号获取变量的地址,并可以将该变量地址复制给指针变量。

当然指针本身也是有地址的,亦可以用&号进行获取指针本身的地址。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
x := 42
var p *int
p = &x // 将x的地址赋给p
fmt.Printf("获取x的变量地址并且赋值给p指针: %p, p指针本身的地址:%p", p, &p)
}

输出结果:

1
获取x的变量地址并且赋值给p指针: 0xc00000a0c8, p指针本身的地址:0xc000060020

6.16.3 获取指针所指向的值

我们可以通过*号获取指针所指向的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

func main() {
x := 42

var p *int
p = &x // 将x的地址赋给p

fmt.Printf("p所指向的值:%v", *p)
}

输出结果:

1
p所指向的值:42

6.16.4 修改指针所指向的值

我们可以复制给*指针变量(星号拼接指针变量在左边)来修改指针所指向的值。

注意:修改指针所指向的值会造成一个现象就是一改多改现象。

而且这种方式与声明指针变量十分容易混淆,切记要分清楚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
x := 42

var p *int
p = &x // 将x的地址赋给p

*p = 55

fmt.Printf("p所指向的值:%v, x的值:%v", *p, x)
}

输出结果:

1
p所指向的值:55, x的值:55

6.17 模块与包

Go语言的命名和导入规则如下:

包名命名规则:

  1. 包名应尽量与包所在目录一致,保持一致性。
  2. 包名应具有有意义的描述性,简短而有意义。
  3. 避免与标准库包名冲突,选择独特的包名。

变量、函数和常量命名规则:

  1. 采用驼峰命名法,即首字母小写,后续单词首字母大写。
  2. 如果变量、函数、常量名的首字母大写,它们可以被其他包访问;如果首字母小写,它们只能在本包中使用。

示例项目:

假如有如下目录结构:

1
2
3
4
5
6
`-- src
`-- prj
|-- main.go
`-- other
`-- pkg1
`-- pkg1.go

若main.go想引用pkg1.go文件,需要注意几点:

注意事项:

  1. import导入语句通常放在文件开头,位于包声明语句下面。

  2. 导入包名需要使用双引号包裹起来。

  3. 包名的路径是从$GOPATH/src/后开始计算,路径分隔符使用斜杠(“/“)。

  4. package的包名,最好与文件夹名一致。例如包名为:pkg1,那么文件夹的名字就为pkg1

  5. 若没有go.mod文件的时候,go会通过环境变量:GOPATH去进行查找,因此需要设置环境变量GOPATH如下图所示。

    1699355843938

  6. GO111MODULE 是一个用于控制 Go Modules 行为的环境变量。它的值可以设置为以下三个选项之一:

    off:这会禁用 Go Modules。在这种模式下,Go 将采用传统的 GOPATH 方式管理依赖关系,不使用 go.mod 文件。这是在 Go 1.11 之前的默认行为。

    on:这启用 Go Modules。在这种模式下,Go 将使用 go.mod 文件来管理依赖关系。如果项目目录中存在 go.mod 文件,Go 会默认启用 Go Modules。

    auto:这是默认值。在这种模式下,Go 将根据您的项目目录中是否存在 go.mod 文件来自动决定是否启用 Go Modules。如果 go.mod 存在,Go Modules 将被启用;否则,它将被禁用。

  7. 需要将导出的函数、变量等设置为手写字母大写。

d:\\goproject\src\prj\main.go文件

1
2
3
4
5
6
7
package main

import "prj/other/pkg1"

func main() {
pkg1.Func()
}

d:\\goproject\src\prj\other\pkg1\pkg1.go文件

1
2
3
4
5
6
7
package pkg1

import "fmt"

func Func() {
fmt.Println("called func in pkg1")
}

6.18 保留字与预定义标识符

① 关键字,又被称为保留字,是由编程语言的创建者规定的,用于具有特殊功能的标识符。在Go语言中,预定义标识符是一种特殊的关键字,它们具有特殊的含义和功能。

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

② 预定义标识符:一共有36个,包含基础数据类型和系统内嵌函数。

append bool byte cap close complex
complex64 complex128 uint16 copy false float32
float64 imag int int8 int16 uint32
int32 int64 iota len make new
nil panic uint64 print println real
recover string true uiint uint8 uintprt

6.19 运算符

在编程过程中,我们可以使用各种运算符来执行各种数学和逻辑操作。

以下是Go语言中一些常见的运算符,它们可以用来执行不同类型的操作:

运算符类型 运算符 描述
算术运算符 + 加法
- 减法
* 乘法
/ 除法
% 取模(取余数)
++ 自增
自减
关系运算符 == 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
逻辑运算符 && 逻辑与
|| 逻辑或
! 逻辑非
位运算符 & 按位与
| 按位或
^ 按位异或
<< 左移
>> 右移
赋值运算符 = 赋值
+= 加法赋值
-= 减法赋值
*= 乘法赋值
/= 除法赋值
%= 取模赋值
&= 按位与赋值
|= 按位或赋值
^= 按位异或赋值
<<= 左移赋值
>>= 右移赋值
其他运算符 & 取地址
* 指针解引用
<- 通道接收操作
变参操作
:: 类型断言
, 逗号运算符

这个表格包括了Go语言中的一些常见运算符,它们用于执行各种操作,包括算术运算、关系运算、逻辑运算、位运算、赋值运算以及其他一些特殊运算符。这些运算符在Go编程中非常有用,可以用于实现不同的功能。

6.19.1 运算符优先级

下面表格是运算符优先级,当然一般情况下为了可读性,会对需要优先运算的区域加圆括号。

优先级 分类 运算符 结合性
1 逗号运算符 , 从左到右
2 赋值运算符 =、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|= 从右到左
3 逻辑或 || 从左到右
4 逻辑与 && 从左到右
5 按位或 | 从左到右
6 按位异或 ^ 从左到右
7 按位与 & 从左到右
8 相等/不等 ==、!= 从左到右
9 关系运算符 <、<=、>、>= 从左到右
10 位移运算符 <<、>> 从左到右
11 加法/减法 +、- 从左到右
12 乘法/除法/取余 *(乘号)、/、% 从左到右
13 单目运算符 !、*(指针)、&、++、–、+(正号)、-(负号) 从右到左
14 后缀运算符 ( )、[ ]、-> 从左到右

6.19.2 加号运算符

1
2
3
4
5
6
7
8
9
10
11
12
// 加号的作用: 1、表示正数   2、相加操作    3、字符串拼接
package main

import "fmt"

func main() {
var n1 int = +10
var n2 int = 4 + 7

var s3 string = "ak" + "47"
fmt.Println(n1, n2, s3)
}

输出结果:

1
10 11 ak47

6.19.3 除号运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
//两个int类型数据运算,结果一定为整数类型)
fmt.Println(10 / 0)

//浮点类型参与运算,结果为浮点类型
fmt.Println(10.0 / 3)

// 除以0除不尽,因此会编译错误
// fmt.Println(10 / 0)
}

输出结果:

1
2
3
3.3333333333333335

6.19.4 取模运算

取模运算是一种数学运算,通常用百分号符号(%)表示。它用于计算一个数除以另一个数后的余数。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
fmt.Println(10 % 3)
fmt.Println(11 % 3)
fmt.Println(11 % 4)
}

输出结果:

1
2
3
1
2
3

6.19.5 自增或者自减

在Go语言中,自增和自减运算符不能用在表达式中,且++和–不能写在变量的前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
var a int = 10
a++
fmt.Println(a)

// 不存在此种操作
// ++a
// fmt.Println(a)

var b int = 5
b--
fmt.Println(b)
}

输出结果:

1
2
11
4

❌不能将++或–放到运算中

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
var x int = 10
y := x * x--
}

6.19.6 赋值运算符

赋值运算符用于将一个值赋给变量。Go 语言支持多种不同的赋值运算符,以下是一些常见的赋值运算符以及它们的示例:

  1. =(等号):将右侧的值赋给左侧的变量。

    1
    2
    a := 5  // a被赋值为5
    name := "Alice" // name被赋值为字符串"Alice"
  2. +=(加等于):将右侧的值加到左侧的变量上,并将结果赋给左侧的变量。

    1
    2
    b := 10
    b += 2 // b的值变为12
  3. -=(减等于):将右侧的值从左侧的变量中减去,并将结果赋给左侧的变量。

    1
    2
    c := 8
    c -= 3 // c的值变为5
  4. *=(乘等于):将右侧的值与左侧的变量相乘,并将结果赋给左侧的变量。

    1
    2
    d := 6
    d *= 4 // d的值变为24
  5. /=(除等于):将左侧的变量除以右侧的值,并将结果赋给左侧的变量。

    1
    2
    e := 20
    e /= 5 // e的值变为4
  6. %=(取模等于):将左侧的变量除以右侧的值后的余数赋给左侧的变量。

    1
    2
    f := 13
    f %= 5 // f的值变为3

这些赋值运算符用于方便地对变量进行操作,并将结果重新赋给同一个变量。它们可以用于各种不同的数据类型,包括整数、浮点数、字符串等。

从命令行提示符中获取值,并且赋值给a、b变量,并且交互两个数的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
var a, b int

fmt.Print("请输入整数a: ")
fmt.Scan(&a)

fmt.Print("请输入整数b: ")
fmt.Scan(&b)

// 交换两个整数的值
a, b = b, a

fmt.Println("交换后的结果:")
fmt.Printf("a: %d b: %d\n", a, b)
}

输出结果:

1
2
3
4
请输入整数a: 10
请输入整数b: 20
交换后的结果:
a: 20 b: 10

6.19.7 关系运算符

关系运算符用于比较两个值,并返回一个布尔值,表示它们之间的关系。通常用来决定程序的控制流,通过这些运算符,可以执行不同的操作,根据比较的结果来确定程序的下一步行动。

以下是一些常见的关系运算符以及它们的示例:

  1. ==(等于):检查左右两边的值是否相等。如果相等,返回 true;否则返回 false

    1
    2
    3
    a := 5
    b := 5
    result := (a == b) // result的值为true
  2. !=(不等于):检查左右两边的值是否不相等。如果不相等,返回 true;否则返回 false

    1
    2
    3
    x := 10
    y := 20
    result := (x != y) // result的值为true
  3. >(大于):检查左边的值是否大于右边的值。如果成立,返回 true;否则返回 false

    1
    2
    3
    p := 15
    q := 10
    result := (p > q) // result的值为true
  4. <(小于):检查左边的值是否小于右边的值。如果成立,返回 true;否则返回 false

    1
    2
    3
    m := 8
    n := 12
    result := (m < n) // result的值为true
  5. <=(小于等于):检查左边的值是否小于或等于右边的值。如果成立,返回 true;否则返回 false

    1
    2
    3
    r := 10
    s := 10
    result := (r <= s) // result的值为true
  6. >=(大于等于):检查左边的值是否大于或等于右边的值。如果成立,返回 true;否则返回 false

    1
    2
    3
    u := 15
    v := 10
    result := (u >= v) // result的值为true

6.19.8 逻辑运算符

逻辑运算符用于执行逻辑操作,通常涉及布尔值(truefalse)。

以下是一些常见的逻辑运算符以及它们的示例:

  1. &&(逻辑与):如果左右两边的条件都为 true,则整个表达式返回 true;否则返回 false

    1
    2
    3
    a := true
    b := false
    result := a && b // result的值为false
  2. ||(逻辑或):如果左右两边的条件中至少有一个为 true,则整个表达式返回 true;否则返回 false

    1
    2
    3
    x := true
    y := false
    result := x || y // result的值为true
  3. !(逻辑非):将条件的布尔值取反,即如果条件为 true,则返回 false;如果条件为 false,则返回 true

    1
    2
    p := true
    result := !p // result的值为false

这些逻辑运算符在编程中用于组合和操作布尔条件,以便进行决策和控制程序的流程。

例如:

您可以使用 && 来执行逻辑与操作,只有当多个条件都为 true 时才执行某个代码块;

使用 || 来执行逻辑或操作,当多个条件中至少一个为 true 时执行代码块;

使用 ! 来执行逻辑非操作,反转条件的布尔值。

逻辑运算符在条件语句、循环控制和其他需要根据条件来决定程序行为的情况下非常有用。

6.19.9 位运算符:

位运算符是用于执行二进制位操作的运算符。它们通常用于处理整数数据的各个位,对数据的二进制表示进行操作。以下是一些常见的位运算符以及它们的示例:

  1. &(按位与):将两个数的每个位进行与运算,只有在两个数的对应位都为1时,结果的相应位才为1,否则为0。

    1
    2
    3
    a := 5   // 二进制:101
    b := 3 // 二进制:011
    result := a & b // 结果为1,二进制:001
  2. |(按位或):将两个数的每个位进行或运算,只要两个数的对应位中至少有一个位为1,结果的相应位就为1。

    1
    2
    3
    x := 5   // 二进制:101
    y := 3 // 二进制:011
    result := x | y // 结果为7,二进制:111
  3. ^(按位异或):将两个数的每个位进行异或运算,只有在两个数的对应位不相同时,结果的相应位才为1,否则为0。

    1
    2
    3
    p := 5   // 二进制:101
    q := 3 // 二进制:011
    result := p ^ q // 结果为6,二进制:110

这些位运算符允许您执行各种位操作,包括清除、设置、反转和测试位。它们通常在需要直接操作二进制数据或处理位掩码时使用。位运算符在底层编程、嵌入式系统和处理硬件数据时非常有用。

6.19.10 其它运算符

除了算术运算符、关系运算符、逻辑运算符和位运算符之外,Go 语言还支持其他一些运算符,这些运算符具有特殊的功能。以下是一些常见的其他运算符:

  1. &(取地址运算符):用于获取变量的内存地址。

    1
    2
    var x int
    ptr := &x // ptr包含x的地址
  2. *(指针解引用运算符):用于访问指针指向的变量的值。

    1
    2
    3
    var y int
    ptr := &y
    value := *ptr // value包含y的值
  3. <-(通道操作符):用于发送数据到通道或从通道接收数据。

    1
    2
    3
    ch := make(chan int)
    ch <- 42 // 发送数据到通道
    data := <-ch // 从通道接收数据
  4. ...(省略号运算符):用于变参函数中,表示将可变数量的参数作为切片传递给函数。

    1
    2
    3
    4
    5
    func printValues(values ...int) {
    for _, value := range values {
    fmt.Println(value)
    }
    }
  5. ::(类型断言运算符):用于将接口类型断言为具体类型。

    1
    2
    3
    4
    5
    var val interface{}
    s, ok := val.(string)
    if ok {
    fmt.Printf("val是字符串:%s\n", s)
    }
  6. ,(逗号运算符):用于同时执行多个表达式,但只返回最后一个表达式的值。

    1
    2
    x, y := 1, 2
    result := (x, y, x+y) // result的值为3

这些其他运算符在特定的上下文中具有特殊的用途,例如指针操作、通道操作、类型断言、函数参数传递等。了解这些运算符可以帮助您更好地理解Go语言的不同方面和功能。

6.20 流程控制

Go语言的流程控制是指通过控制语句来管理程序的执行流程,以便根据不同条件执行不同的代码块。Go语言提供了条件语句和循环语句。

6.20.1 条件语句

当条件表达式为true时,执行以下的代码:

  • 条件表达式左右的小括号 () 可以不写,也建议不写。
  • 在Golang中,if 和条件表达式之间一定要有空格。
  • 在Golang中,{} 是必须有的,即使你只写一行代码。

6.20.1.1 if语句

flowchart of if else statement in C programming

允许根据条件执行不同的代码块。可以包括ifelseelse if子句。

1
2
3
4
5
6
7
if condition {
// 如果条件成立,执行此处的代码
} else if anotherCondition {
// 如果另一个条件成立,执行此处的代码
} else {
// 如果以上条件都不成立,执行此处的代码
}

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
var score = 61
if score > 90 {
fmt.Print("优秀")
} else if score > 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
}

输出结果:

1
及格

例子2:在条件语句区域定义变量并赋值

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
if score := 91; score > 90 {
fmt.Print("优秀")
}
}

输出结果:

1
优秀

6.20.1.2 switch语句

用于根据不同的表达式值选择执行的代码块,类似于C语言的switch语句。

当使用Go语言的switch语句时,需要注意以下事项:

  1. switch后是一个表达式,可以是常量值、变量、或具有返回值的函数等。

  2. case后的各个值的数据类型必须与switch表达式的数据类型一致。

  3. case后面可以带多个表达式,使用逗号间隔,例如:case 表达式1, 表达式2...

  4. 如果case后面的表达式是常量值(字面量),则要求它们不能重复。

  5. case后面不需要使用break语句,一旦匹配到一个case,对应的代码块将被执行,然后程序会退出switch语句。如果一个都没有匹配到,会执行default分支(如果存在)。

  6. default语句是可选的,用于处理未匹配到任何case的情况。

  7. switch后可以不带表达式,这时可以当作类似于if语句的分支来使用。

  8. switch后也可以直接声明/定义一个变量,以分号结束,不过这种做法并不是推荐的写法。

  9. Go语言支持switch穿透,可以使用fallthrough关键字,如果在case语句块后增加fallthrough,则会继续执行下一个case,这也被称为switch穿透。

1
2
3
4
5
6
7
8
switch expression {
case value1:
// 如果expression等于value1,执行此处的代码
case value2:
// 如果expression等于value2,执行此处的代码
default:
// 如果expression与所有case都不匹配,执行此处的代码
}

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
var errorcode = 404

switch errorcode {
case 404:
fmt.Println("找不到页面")
case 302:
fmt.Println("重定向")
default:
fmt.Println("未知错误")
}
}

输出结果:

1
找不到页面

例子2:switch当if语句来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
var score = 91

switch {
case score > 90:
fmt.Println("优秀")
case score > 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}

输出结果:

1
优秀

例子3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func main() {
switch score := 91; {
case score > 90:
fmt.Println("优秀")
case score > 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}

输出结果:

1
优秀

例子4:穿透

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func main() {
switch score := 91; {
case score > 90:
fmt.Println("优秀")
fallthrough
case score > 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
}

输出结果:

1
2
优秀
及格

6.20.2 循环语句

用于重复执行一段代码,可以是基本的for循环、for range循环,或无限循环。用于遍历切片、映射、通道等数据结构。

注意:go语言中是没有while循环的

flowchart of for loop in C programming

格式:

1
2
3
4
5
6
7
8
9
10
11
for initialization; condition; post {
// 循环体
}

for index, value := range collection {
// 遍历集合
}

for {
// 无限循环,需要在循环内部使用break语句来退出
}

6.20.2.1 for循环

Go语言中有三种主要的循环结构,分别是forrangeselect。这些循环结构分别用于不同的情况和用途:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
)

func main() {
for i := 1; i < 10; i++ {
fmt.Println(i)
}
}

输出结果:

1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

6.20.2.2 for … range循环

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
numbers := []int{1, 2, 3, 4, 5}

for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}

输出结果:

1
2
3
4
5
Index: 0, Value: 1
Index: 1, Value: 2
Index: 2, Value: 3
Index: 3, Value: 4
Index: 4, Value: 5

6.20.2.3 无限for循环

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
for {
fmt.Println(1)
}
}

输出结果:

1
2
3
4
5
6
7
8
9
1
1
1
1
......
1
1
1
1

6.20.2.4 模拟while循环

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
var i int32 = 0

for i < 10 {
fmt.Println(i)
i++
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9

6.20.2.5 与select配合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
var ch1 chan int
var ch2 chan int

for {
select {
case value := <-ch1:
fmt.Println(value)
case value := <-ch2:
fmt.Println(value)
}
}

}

6.20.3 跳转语句

6.20.3.1 break跳转语句

用于立即终止当前循环。

1
2
3
4
5
6
for i := 0; i < 5; i++ {
if i == 3 {
break // 终止循环
}
fmt.Println(i)
}

输出结果:

1
2
3
0
1
2

6.20.3.2 break具体的for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
lable2:
for i := 0; i < 9; i++ {
for j := 0; j <= 4; j++ {
fmt.Printf("i: %v, j: %v\n", i, j)
if i == 2 && j == 2 {
break lable2
}
}
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
i: 0, j: 0
i: 0, j: 1
i: 0, j: 2
i: 0, j: 3
i: 0, j: 4
i: 1, j: 0
i: 1, j: 1
i: 1, j: 2
i: 1, j: 3
i: 1, j: 4
i: 2, j: 0
i: 2, j: 1
i: 2, j: 2

6.20.3.3 continue跳转语句

用于跳过当前循环迭代并继续下一次迭代。

1
2
3
4
5
6
for i := 0; i < 5; i++ {
if i == 2 {
continue // 跳过本次循环迭代
}
fmt.Println(i)
}

输出结果:

1
2
3
4
0
1
3
4

6.20.3.4 goto跳转语句

Go语言中少用,用于无条件跳转到标签处。

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
label:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
goto label // 无条件跳转到标签处
}
}
}
}

输出结果:无限循环

例子2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
fmt.Println("1")
fmt.Println("2")
fmt.Println("3")
goto label1
fmt.Println("4")
fmt.Println("5")
fmt.Println("6")
fmt.Println("7")
label1:
fmt.Println("8")
fmt.Println("9")
fmt.Println("10")
}

输出结果:

1
2
3
4
5
6
1
2
3
8
9
10

6.21 函数

在Go语言中,函数(Function)是一种独立的代码块,用于执行特定任务或操作。函数是模块化程序设计的基本组成部分,函数是将可重用逻辑单元代码进行封装,有助于提高代码的可读性和可维护性。

6.21.1 关键特点和概念

以下是关于Go语言函数的一些关键特点和概念:

  1. 函数声明:函数通常以func关键字开始,后跟函数名、参数列表、返回类型和函数体。函数的基本声明形式如下:

    1
    2
    3
    func function_name(parameters) return_type {
    // 函数体
    }
  2. 函数名称:函数名称通常是由字母、数字和下划线组成的标识符,且函数名首字母大写表示函数是可导出的(在其他包中可访问),一般习惯为见名知意的驼峰式命名。

  3. 参数:函数可以接受零个或多个参数,参数是用于传递数据给函数的。参数列表由参数名和参数类型组成。

  4. 返回值:函数可以返回一个或多个值。返回值的类型由return_type声明。

  5. 函数体:函数体包含了函数的实际操作,是由一系列语句组成的代码块。

  6. 调用函数:要使用函数,您需要在代码中调用它。函数调用通常由函数名和传递给函数的参数组成。

示例:

1
2
3
4
5
func add(a, b int) int {
return a + b
}

result := add(3, 5) // 调用add函数并将结果存储在result中
  1. 多返回值:Go语言支持函数返回多个值。这使得函数能够方便地返回多个相关的数据。

示例:

1
2
3
4
5
6
7
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

q, r := divide(10, 3)
  1. 可变参数:Go语言支持可变参数函数,这意味着函数可以接受可变数量的参数。

示例:

1
2
3
4
5
6
7
8
9
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}

result := sum(1, 2, 3, 4, 5)
  1. 匿名函数:Go语言支持匿名函数,也称为闭包。匿名函数可以在函数内部定义,通常用于一次性操作或将函数作为变量传递。

示例:

1
2
3
4
5
6
func main() {
add := func(a, b int) int {
return a + b
}
result := add(3, 5)
}

函数在Go语言中是非常重要的概念,用于将代码模块化、可复用和可维护,是构建Go程序的基本组成部分。

6.21.2 Go函数不支持重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
result := add(1, 2, 3)
fmt.Println(result)
}

func add(num1 int, num2 int, num3 int) int {
var t int
t = num1 + num2 + num3
return t
}

func add(num1 int, num2 int) int {
var t int
t = num1 + num2
return t
}

6.21.3 可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
result := add(1, 2, 3)
fmt.Println(result)
fmt.Println("-----------------------------")

result = add(5, 6, 7, 8, 9)
fmt.Println(result)
fmt.Println("-----------------------------")

result = add(10)
fmt.Println(result)
fmt.Println("-----------------------------")

}

func add(args...int) int {
var t int

for _, value := range args {
t += value
}

return t
}

输出结果:

1
2
3
4
5
6
6
-----------------------------
35
-----------------------------
10
-----------------------------

6.21.4 值传递

基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值.

值传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
var v int = 10
r := change(v)

fmt.Printf("r: %v, v: %v\n", r, v)
}

func change(num1 int) int {
num1 = 999
return num1
}

输出结果:

1
r: 999, v: 10

引用传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
var v int = 10
change(&v)

fmt.Printf("v: %v\n", v)
}

func change(num1 *int) {
*num1 = 999
}

输出结果:

1
v: 999

对象传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Memory struct {
name string
}

func main() {
obj := &Memory{
name: "小明",
}

change(obj)
fmt.Println(obj)
}

func change(param *Memory) {
param.name = "小红"
}

输出结果:

1
&{小红}

6.21.5 函数数据类型

在Go中,函数也是一种数据类型,可以赋值给一个变量。可以通过使用该变量来调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func change(num1 int, num2 int) int {
return num1 + num2
}

func main() {
myFunc := change
a := 10
b := 5
result := myFunc(a, b)
fmt.Printf("a: %v, b: %v, result: %v\n", a, b, result)
}

输出结果:

1
a: 10, b: 5, result: 15

函数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func change(num1 int, num2 int) int {
return num1 + num2
}

func testChange(num1 int, num2 int, changeFunc func(int, int) int) int {
r := changeFunc(num1, num2)
return r
}

func main() {
myFunc := change
a := 10
b := 5
result := testChange(a, b, myFunc)
fmt.Printf("a: %v, b: %v, result: %v\n", a, b, result)
}

输出结果:

1
a: 10, b: 5, result: 15

6.21.6 自定义数据类型

Go语言支持自定义数据类型,基本语法:type 数据类型名称 数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type pi float32
type myAddFun func(int, int) int

func main() {
var p pi = 3.14159265357932384626
fmt.Printf("p: %v\n", p)

var add myAddFun
add = func(x, y int) int {
return x + y
}

result := add(5, 7)
fmt.Printf("Result: %v\n", result)
}

输出结果:

1
2
p: 3.1415927
Result: 12

6.21.7 多返回值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func addReduce(num1 int, num2 int) (int, int) {
sub := num1 - num2
sum := num1 + num2
return sum, sub
}

func main() {
sum, sub := addReduce(100, 10)
fmt.Printf("sum: %v, sub: %v\n", sum, sub)
}

输出结果:

1
sum: 110, sub: 90

函数支持对返回值进行命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func addReduce(num1 int, num2 int) (sum int, sub int) {
sub = num1 - num2
sum = num1 + num2
return
}

func main() {
sum, sub := addReduce(100, 10)
fmt.Printf("sum: %v, sub: %v\n", sum, sub)
}

输出结果:

1
sum: 110, sub: 90

6.21.8 堆栈理解

如下图所示:

堆:有序,相互叠加

栈:无特定顺序

简单描述一下:Stack用于静态内存分配,Heap用于动态内存分配。记住,堆和堆栈都存储在内存中。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Memory struct{}

func main() {
i := 1
obj := new(interface{})
memo := new(Memory)
memo.foo(obj)
}

func (m *Memory) foo(param *interface{}) {
str := fmt.Sprintf("%v", *param)
fmt.Println(str)
}

img

结合上面代码和图: 我们分析一下该段代码执行的每一行,并解释何时从堆或堆栈中分配、存储和释放对象。

第 1 行: Go Runtime为main方法在栈中创建一个内存块。

1
func main() {

第2行:创建了一个局部变量。该变量将被存储在main方法的栈内存中。

1
i := 1

第3行:创建了一个新的对象。对象存储在堆上,而对象的内存地址存储在main方法的栈上。

1
obj := new(interface{})

第4行: 与第 3 行相同

1
memo := new(Memory)

第 5 行 : Go Runtime为foo方法创建一个栈内存块。

1
memo.foo(obj)

第 6 行 :foo方法在栈内存创建一个了新对象param,param对象存储了第五行所传递的obj对象的堆内存地址。

注意:对象参数指向的是真实对象堆的地址。

1
func (m *Memory) foo(param *interface{}) {

第 7 行:在foo方法的栈中创建一个新对象str,用于存储字符串的堆内存地址。

1
str := fmt.Sprintf("%v", *param)

第8行:当到达最后一行时, foo方法终止。go runtime会将foo方法的栈块中的对象将被释放。

1
} // foo的结束

第9行:main方法执行到最后一行时终止方式与第8行相同。go runtime会将main方法栈块中的对象将被释放。

1
} // main的结束

如下图,当foo函数结束时,会将foo函数在栈内存释放。

img

如下图,同样当main函数结束时,会将main函数在栈内存释放。而右侧的堆内存,则需要借助垃圾收集器来进行回收,垃圾收集器需要检测未使用的对象,释放它们,并回收内存中的更多空间。

img

如下图,栈是一种 LIFO(后进先出)数据结构。我们可以将其视为一个盒子。通过使用这种结构,程序可以通过两个简单的操作轻松管理其所有操作:pushpop

img

6.21.9 init函数

在Go语言中,init函数是一个特殊的函数,用于在程序启动时执行一些初始化操作。每个包可以包含一个或多个init函数,这些函数会在程序执行过程中的不同阶段按照它们在代码中的顺序被自动调用。

init函数有以下特点:

  1. init函数不接受任何参数,也不返回任何值。
  2. 每个包中可以包含多个init函数,它们按照它们在代码中的顺序依次执行。
  3. init函数是在程序启动时自动执行的,无需显式调用。

通常,init函数用于执行一些包级别的初始化操作,例如初始化全局变量、执行一次性的设置或启动后台任务。这对于确保包的依赖关系和初始化顺序都正确非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

var num int = test()

func test() int {
fmt.Println("test函数被执行")
return 10
}

func init() {
fmt.Println("init函数被执行")
}

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

输出结果:

1
2
3
test函数被执行
init函数被执行
hello world

6.21.10 匿名函数

在Go语言中,匿名函数是一种无需定义函数名称的函数,通常用于以内联的方式定义和使用函数。匿名函数可以作为变量、参数传递给其他函数,或直接在代码块中使用。

匿名函数也经常用于创建闭包,可以访问其外部作用域中的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
// 在这里定义一个匿名函数并将其分配给变量add
add := func(x, y int) int {
return x + y
}

result := add(3, 4)
fmt.Println(result) // 输出 7
}

输出结果:

1
7

6.21.11 闭包

闭包是一种特殊的函数,它可以捕获并访问其外部作用域中的变量。这意味着一个闭包函数可以在其定义的范围之外使用变量,而不需要将这些变量作为参数传递给函数。闭包通常的创建方式是:函数内部返回另外一个函数,并且内部函数使用外部函数的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func outfunc() func() int {
var a int = 0
return func() int {
a++
return a
}
}

func main() {
innerFunc := outfunc()
fmt.Println(innerFunc()) // 输出 1
fmt.Println(innerFunc()) // 输出 2
}

输出结果:

1
2
1
2

6.21.12 defer关键词

在函数中,经常需要创建一些资源,为了确保这些资源能够在函数执行完毕后得到及时释放,Go的设计者引入了关键字defer。

defer的是先进后出。

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
defer fmt.Println("这是defer语句1")
defer fmt.Println("这是defer语句2")

fmt.Println("这是普通语句")
}

输出结果:

1
2
3
这是普通语句
这是defer语句2
这是defer语句1

6.22 模块与包

在Go语言中,有两个重要的概念:模块(Module)和包(Package)。

Go的模块和包是有助于组织、管理和共享代码的重要概念,它们有助于构建可维护的应用程序和库。

  1. 模块(Module): Go 1.11及更高版本引入了模块支持。模块是一个版本化的代码单元,用于组织和管理Go代码。每个模块都有一个唯一的模块路径,并且可以包含多个包。模块的主要目的是为了管理依赖关系,以确保项目的稳定性和可重现性。

    • 创建模块:你可以使用 go mod init 模块路径 命令来创建一个新的模块,其中 “模块路径” 是你的代码库的位置,通常是在你的代码库根目录执行该命令。
    • 管理依赖:模块通过 go.mod 文件来记录项目的依赖关系,可以使用 go getgo mod tidy 等命令来添加、更新或删除依赖项。
  2. 包(Package): 包是Go代码的基本组织单元,用于封装和组织相关的功能和数据。Go代码文件通常属于一个包,每个包都有一个唯一的包名。包可以包含函数、类型、变量等,它们可以在其他Go文件中引用。标准库中有很多包,而你也可以创建自己的包。

    • 创建包:每个Go文件的顶部都需要指定所属包,例如 package main 表示这个文件属于 main 包。
    • 导入包:在其他Go文件中,你可以使用 import 语句来导入其他包,然后可以使用其中的函数和类型。

包的组织结构通常按照文件夹结构来组织,例如:

1
2
3
4
5
6
7
myproject
|-- go.mod
|-- main.go
|-- math
| `-- calculations.go
`-- utils
`-- common.go

在上面的示例中,myproject 是一个模块,而 mathutils 是两个包,它们各自包含了不同的Go文件。 main.go 是项目的入口文件,可以使用 import 语句导入 mathutils 包中的函数和类型。

6.23 日期与时间函数

Golang是一种支持丰富的时间和日期处理功能的编程语言。Go标准库提供了许多用于操作时间和日期的函数和数据结构。

6.23.1 After函数

time.After 是 Go 标准库中的一个函数,用于创建一个 chan time.Time,在指定的时间间隔之后向该通道发送一个时间值。通常,它用于实现定时操作,以在一段时间后执行某些任务。以下是如何使用 time.After 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"time"
)

var c chan int

func handle(int) {}

func main() {
select {
case m := <-c:
handle(m)
case <-time.After(10 * time.Second):
fmt.Println("timed out")
}
}

输出结果:(等待十秒后)

1
timed out

6.23.2 Sleep睡眠函数

Sleep 会暂停当前 goroutine 至少持续时间 d。 负持续时间或零持续时间会导致睡眠立即返回。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
time.Sleep(10000 * time.Millisecond)
fmt.Println("时间结束")
}

输出结果:(等待十秒后)

1
时间结束

6.23.3 Tick函数

time.Tick 是 Go 标准库中的一个函数,用于创建一个通道 (chan time.Time),并以固定的时间间隔向该通道发送时间值,用于实现循环定时器。它通常用于周期性执行某个任务。

关于time.Tick函数的一些重要注意事项:

  1. TickNewTicker 的便利包装器:time.TickNewTicker 函数的一种便捷方式,提供了对定时通道的访问。NewTicker 返回一个 *time.Ticker 类型的值,而 Tick 返回的是 chan time.Time,这使得 Tick 对于某些特定的使用场景更方便。
  2. 无法手动关闭 Tick:Tick 函数的一个特点是:它创建的循环定时器无法手动关闭,这意味着不能显式停止这个定时器,因为 Tick 只返回一个时间通道,并不提供关闭定时器的方法。
  3. 定时器泄漏问题:因为 Tick 创建的循环定时器无法被手动关闭,如果你不再需要它,它将继续触发并发送时间值到通道,而不会被回收,这可能导致资源泄漏。在 Go 中,如果没有办法关闭一个不再使用的定时器,它就无法被垃圾回收器回收,这被称为”泄漏”。
  4. Tick 返回 nil 如果时间间隔小于等于0:最后一个注意事项提到,如果你尝试使用小于等于0的时间间隔来调用 Tick,它将返回 nil,因为不允许创建无效的定时器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个每1秒触发一次的定时器
ticker := time.Tick(1 * time.Second)

for t := range ticker {
fmt.Println("定时器触发:", t)
// 在这里执行你想要的周期性任务
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
定时器触发: 2023-11-14 11:06:44.8296889 +0800 CST m=+1.013546301
定时器触发: 2023-11-14 11:06:45.8299272 +0800 CST m=+2.013784601
定时器触发: 2023-11-14 11:06:46.8297704 +0800 CST m=+3.013627801
定时器触发: 2023-11-14 11:06:47.8291759 +0800 CST m=+4.013033301
定时器触发: 2023-11-14 11:06:48.8236972 +0800 CST m=+5.007554601
定时器触发: 2023-11-14 11:06:49.8242073 +0800 CST m=+6.008064701
定时器触发: 2023-11-14 11:06:50.8190338 +0800 CST m=+7.002891201
定时器触发: 2023-11-14 11:06:51.8447868 +0800 CST m=+8.028644201
定时器触发: 2023-11-14 11:06:52.8292361 +0800 CST m=+9.013093501
定时器触发: 2023-11-14 11:06:53.8301332 +0800 CST m=+10.013990601
.
.
.
.
.

6.23.4 ParseDuration函数

ParseDuration 解析持续时间字符串。

持续时间字符串是可能有符号的十进制数字序列,每个十进制数字都有可选的分数和单位后缀。

例如“300ms”、“-1.5h”或“2h45m”。 有效的时间单位为“ns”、“us”(或“μs”)、“ms”、“s”、“m”、“h”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
hours, _ := time.ParseDuration("10h")
complex, _ := time.ParseDuration("1h10m10s")

fmt.Println(hours.Seconds())
fmt.Println(complex.Seconds())
}

输出结果:

1
2
36000
4210

6.23.5 Since函数

Since函数是 time.Now().Sub(t) 的简写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func main() {
// 记录一个起始时间点
startTime := time.Now()

// 模拟一些耗时操作
for i := 0; i < 1000000; i++ {
// Do something time-consuming
}

// 计算从起始时间点到现在的时间间隔
elapsedTime := time.Since(startTime)

fmt.Printf("经过的时间:%s\n", elapsedTime)
}

输出结果:

1
经过的时间:509.8µs

6.23.6 Until函数

Util函数会返回一个直到t结束的时间间隔,它是 t.Sub(time.Now())的简写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func main() {
// 记录一个起始时间点
startTime := time.Now()

// 模拟一些耗时操作
for i := 0; i < 1000000; i++ {
// Do something time-consuming
}

// 计算从起始时间点到现在的时间间隔
elapsedTime := time.Until(startTime)

fmt.Printf("经过的时间:%s\n", elapsedTime)
}

输出结果:

1
经过的时间:-513.2µs

6.23.7 Duration类型

Duration类型是用int64纳秒记录两个时刻之间经过的时间。 该表示法将最大可表示持续时间限制为大约 290 年。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func callFunc() {
fmt.Println("你好吗?")
}

func main() {
t0 := time.Now()
callFunc()
t1 := time.Now()
fmt.Printf("The call took %v to run.\n", t1.Sub(t0))
}

输出结果:

1
2
你好吗?
The call took 0s to run.

6.23.8 Duration的Abs函数

Abs 返回 d 的绝对值。 作为一种特殊情况,math.MinInt64 会转换为 math.MaxInt64。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
t := time.Duration.Abs(-10000)
fmt.Printf("t: %v\n", t)
}

输出结果:

1
t: 10µs

6.23.9 Duration的Hours函数

Hours 以浮点数形式的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
h, _ := time.ParseDuration("4h30m")
fmt.Printf("我还剩 %.1f 小时的工作时间。", h.Hours())
}

输出结果:

1
我还剩 4.5 小时的工作时间

6.23.10 Duration的Minutes函数

Minutes函数以浮点形式返回分钟计数的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
u, _ := time.ParseDuration("1s")
fmt.Printf("一秒等于 %f 分钟。\n", u.Minutes())
}

输出结果:

1
一秒等于 0.016667 分钟。

6.23.11 Duration的Microseconds函数

Microseconds函数以整数形式返回微秒计数的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
u, _ := time.ParseDuration("1s")
fmt.Printf("一秒等于 %d 微秒。\n", u.Microseconds())
}

输出结果:

1
一秒等于 1000 微秒。

6.23.12 Duration的Milliseconds函数

Milliseconds函数以整数形式返回毫秒计数的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
u, _ := time.ParseDuration("1s")
fmt.Printf("一秒等于 %d 毫秒。\n", u.Milliseconds())
}

输出结果:

1
一秒等于 1000 毫秒。

6.23.13 Duration的Nanoseconds函数

Nanoseconds函数以整数形式返回纳秒计数的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
u, _ := time.ParseDuration("1s")
fmt.Printf("一秒等于 %d 纳秒。\n", u.Nanoseconds())
}

输出结果:

1
一秒等于 1000000000 纳秒。

6.23.14 Duration的Round函数

time包中的Round函数用于将time.Time值舍入到指定的时间精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

func main() {
// 解析时间间隔字符串
d, err := time.ParseDuration("1h15m30.918273645s")
if err != nil {
panic(err)
}

// 定义一组舍入值
roundingValues := []time.Duration{
time.Nanosecond,
time.Microsecond,
time.Millisecond,
time.Second,
2 * time.Second,
time.Minute,
10 * time.Minute,
time.Hour,
}

// 遍历舍入值并打印舍入结果
for _, r := range roundingValues {
roundedResult := d.Round(r)
fmt.Printf("d.Round(%6s) = %s\n", r, roundedResult.String())
}
}

输出结果:

1
2
3
4
5
6
7
8
d.Round(   1ns) = 1h15m30.918273645s
d.Round( 1µs) = 1h15m30.918274s
d.Round( 1ms) = 1h15m30.918s
d.Round( 1s) = 1h15m31s
d.Round( 2s) = 1h15m30s
d.Round( 1m0s) = 1h16m0s
d.Round( 10m0s) = 1h20m0s
d.Round(1h0m0s) = 1h0m0s

6.23.15 Duration的Seconds函数

Seconds函数以整数形式返回秒计数的持续时间。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
m, _ := time.ParseDuration("1m30s")
fmt.Printf("%.0f", m.Seconds())
}

输出结果:

1
90

6.23.16 Duration的String函数

String 返回表示持续时间的字符串,格式为“72h3m0.5s”。 前导零单位被省略。

特殊情况:小于一秒的持续时间格式会使用较小的单位(毫秒、微秒或纳秒)来确保前导数字非零。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"time"
)

func main() {
m, _ := time.ParseDuration("72h3m1s55ms")
fmt.Printf("%s", m.String())
}

输出结果:

1
72h3m1.055s

6.23.17 Duration的Truncate函数

Truncate函数用于将time.Time值截断到指定的时间精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

func main() {
d, err := time.ParseDuration("1h15m30.918273645s")
if err != nil {
panic(err)
}

trunc := []time.Duration{
time.Nanosecond,
time.Microsecond,
time.Millisecond,
time.Second,
2 * time.Second,
time.Minute,
10 * time.Minute,
time.Hour,
}

for _, t := range trunc {
fmt.Printf("d.Truncate(%6s) = %s\n", t, d.Truncate(t).String())
}
}

输出结果:

1
2
3
4
5
6
7
8
d.Truncate(   1ns) = 1h15m30.918273645s
d.Truncate( 1µs) = 1h15m30.918273s
d.Truncate( 1ms) = 1h15m30.918s
d.Truncate( 1s) = 1h15m30s
d.Truncate( 2s) = 1h15m30s
d.Truncate( 1m0s) = 1h15m0s
d.Truncate( 10m0s) = 1h10m0s
d.Truncate(1h0m0s) = 1h0m0s

6.23.18 Location类型

time包中的Location类型用于表示时区信息。Location类型通常用于将time.Time值与特定的时区相关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"time"
)

func main() {
loc, err := time.LoadLocation("America/New_York")
if err != nil {
panic(err)
}

fmt.Println(loc)
}

输出结果:

1
America/New_York

6.23.19 FixedZone函数

FixedZone函数是Go语言time包中的一个函数,用于创建一个固定偏移的时区(Location)。这个时区的偏移是相对于UTC的。UTC(协调世界时),全世界统一。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
loc := time.FixedZone("UTC-8", -8*60*60)
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)
fmt.Println("The time is:", t.Format(time.RFC822))
fmt.Println("The time is:", t.Format(time.RFC3339))
}

输出结果:

1
2
The time is: 10 Nov 09 23:00 UTC-8
The time is: 2009-11-10T23:00:00-08:00

6.23.20 LoadLoading函数

time.LoadLocation 函数用于根据时区的名称加载具体的 Location(时区) 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func main() {
// 根据时区名称加载时区信息
loc, err := time.LoadLocation("America/New_York")
if err != nil {
fmt.Println("Error loading location:", err)
return
}

// 获取当前时间并转换到指定时区
currentTimeInNY := time.Now().In(loc)

// 打印结果
fmt.Println("Current time in New York:", currentTimeInNY)
}

输出结果:

1
Current time in New York: 2023-11-14 02:49:27.0961699 -0500 EST

6.23.21 LoadLocationFromTZData函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"os"
"time"
)

func main() {
// 读取时区文件内容
tzData, err := os.ReadFile("/etc/localtime")
if err != nil {
fmt.Println("Error reading timezone data:", err)
return
}

// 根据时区数据加载时区信息
loc, err := time.LoadLocationFromTZData("CustomTimeZone", tzData)
if err != nil {
fmt.Println("从时区数据加载位置时出错:", err)
return
}

// 获取当前时间并转换到指定时区
currentTimeInCustomTZ := time.Now().In(loc)

// 打印结果
fmt.Println("自定义时区的当前时间:", currentTimeInCustomTZ)
}

输出结果:

1
自定义时区的当前时间:

6.23.22 String函数

(*time.Location).String 方法返回与Location相关的时区的规范名。这个规范名通常是 IANA(Internet Assigned Numbers Authority)时区数据库中的名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func main() {
// 根据时区名称加载时区信息
loc, err := time.LoadLocation("America/New_York")
if err != nil {
fmt.Println("Error loading location:", err)
return
}

// 使用 Location 的 String 方法获取规范名
locationString := loc.String()

// 打印结果
fmt.Println("Location string:", locationString)
}

输出结果:

1
Location string: America/New_York

6.23.23 Month类型

Month类型指定一年中的月份(一月 = 1,…)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

func main() {
// 使用 time.Month 类型表示月份
currentMonth := time.Now().Month()

// 打印当前月份的数值
fmt.Printf("Current month (numeric): %v\n", currentMonth)

fmt.Printf("%d\n", time.January)
fmt.Printf("%d\n", time.February)
fmt.Printf("%d\n", time.March)
fmt.Printf("%d\n", time.April)
fmt.Printf("%d\n", time.May)
fmt.Printf("%d\n", time.June)
fmt.Printf("%d\n", time.July)
fmt.Printf("%d\n", time.August)
fmt.Printf("%d\n", time.September)
fmt.Printf("%d\n", time.October)
fmt.Printf("%d\n", time.November)
fmt.Printf("%d\n", time.December)
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Current month (numeric): November
1
2
3
4
5
6
7
8
9
10
11
12

6.23.24 Month类型的String函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"time"
)

func main() {
// 使用 time.Month 类型表示月份
currentMonth := time.Now().Month()

// 打印当前月份的数值
fmt.Printf("Current month (numeric): %v\n", currentMonth)

fmt.Printf("%s\n", time.January.String())
fmt.Printf("%s\n", time.February.String())
fmt.Printf("%s\n", time.March.String())
fmt.Printf("%s\n", time.April.String())
fmt.Printf("%s\n", time.May.String())
fmt.Printf("%s\n", time.June.String())
fmt.Printf("%s\n", time.July.String())
fmt.Printf("%s\n", time.August.String())
fmt.Printf("%s\n", time.September.String())
fmt.Printf("%s\n", time.October.String())
fmt.Printf("%s\n", time.November.String())
fmt.Printf("%s\n", time.December.String())
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Current month (numeric): November
January
February
March
April
May
June
July
August
September
October
November
December

6.23.25 ParseError类型

time.ParseError 类型是 time 包中的一个错误类型,用于表示时间解析过程中的错误。它包含了详细的信息,例如解析失败的字符串、解析的格式以及发生错误的位置。

1
2
3
4
5
6
7
type ParseError struct {
Layout string
Value string
LayoutElem string
ValueElem string
Message string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"time"
)

func main() {
// 一个无效的时间字符串
invalidTimeString := "2023-13-01 12:30:00"

// 使用 time.Parse 解析时间字符串
_, err := time.Parse("2006-01-02 15:04:05", invalidTimeString)

// 检查是否发生了 time.ParseError
if pe, ok := err.(*time.ParseError); ok {
// 打印 time.ParseError 的详细信息
fmt.Printf("Error occurred during parsing:\n")
fmt.Println("Layout: ", pe.Layout)
fmt.Println("Value: ", pe.Value)
fmt.Println("LayoutElem: ", pe.LayoutElem)
fmt.Println("ValueElem: ", pe.ValueElem)
fmt.Println("Message: ", pe.Message)
} else if err != nil {
// 打印其他类型的错误
fmt.Printf("Unexpected error: %s\n", err)
} else {
// 解析成功的情况
fmt.Println("Parsing successful.")
}
}

输出结果:

1
2
3
4
5
6
Error occurred during parsing:
Layout: 2006-01-02 15:04:05
Value: 2023-13-01 12:30:00
LayoutElem: 01
ValueElem: -01 12:30:00
Message: : month out of range

6.23.26 ParseError的Error方法

(*time.ParseError).Error 方法用于实现 error 接口,将 ParseError 类型转换为字符串。这意味着你可以直接通过 Error() 方法获取 ParseError 类型的字符串表示,用于打印错误信息或其他错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"time"
)

func main() {
// 一个无效的时间字符串
invalidTimeString := "2023-13-01 12:30:00"

// 使用 time.Parse 解析时间字符串
_, err := time.Parse("2006-01-02 15:04:05", invalidTimeString)

// 检查是否发生了 time.ParseError
if pe, ok := err.(*time.ParseError); ok {
// 打印 time.ParseError 的字符串表示
fmt.Println("错误出现::", pe.Error())
} else if err != nil {
// 打印其他类型的错误
fmt.Println("Unexpected error:", err)
} else {
// 解析成功的情况
fmt.Println("Parsing successful.")
}
}

输出结果:

1
错误出现:: parsing time "2023-13-01 12:30:00": month out of range

6.23.27 Ticker类型

Ticker 类型表示一个定期触发的事件,类似于定时器(time.Timer),但它会重复触发。Ticker 以一定的时间间隔(周期)发送时间到其通道。

Ticker 类型的定义如下:

1
2
3
type Ticker struct {
C <-chan Time // 接收 定期触发的时间事件 的只读通道
}

Ticker 类型包含一个只读的通道 C,该通道会定期地发送时间事件。

6.23.28 NewTicker函数

你可以通过Ticker类型中的这个通道C接收定时的时间事件,以下例子,演示如何使用 time.NewTicker 创建一个 Ticker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个间隔为2秒的 Ticker
ticker := time.NewTicker(2 * time.Second)

// 延迟一些时间,以便观察 Ticker 的触发
time.Sleep(5 * time.Second)

// 循环接收 Ticker 的时间事件
for t := range ticker.C {
fmt.Println("Tick at", t)
}

// 注意: 当程序退出时,务必停止 Ticker,以释放资源
ticker.Stop()
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Tick at 2023-11-14 17:48:01.3523409 +0800 CST m=+2.006400801
Tick at 2023-11-14 17:48:05.3497412 +0800 CST m=+6.003801101
Tick at 2023-11-14 17:48:07.3508127 +0800 CST m=+8.004872601
Tick at 2023-11-14 17:48:09.3519613 +0800 CST m=+10.006021201
Tick at 2023-11-14 17:48:11.3482083 +0800 CST m=+12.002268201
Tick at 2023-11-14 17:48:13.351261 +0800 CST m=+14.005320901
Tick at 2023-11-14 17:48:15.3527066 +0800 CST m=+16.006766501
Tick at 2023-11-14 17:48:17.3631785 +0800 CST m=+18.017238401
Tick at 2023-11-14 17:48:19.3498538 +0800 CST m=+20.003913701
Tick at 2023-11-14 17:48:21.358262 +0800 CST m=+22.012321901
Tick at 2023-11-14 17:48:23.3504899 +0800 CST m=+24.004549801
Tick at 2023-11-14 17:48:25.3498924 +0800 CST m=+26.003952301
Tick at 2023-11-14 17:48:27.3483755 +0800 CST m=+28.002435401

在这个例子中,我们使用 time.NewTicker 创建了一个间隔为2秒的 Ticker

然后,我们等待了5秒钟,接着通过 for 循环不断地从 Ticker 的通道 C 中接收时间事件,并打印出来。

最后,我们在程序退出前调用 ticker.Stop() 停止 Ticker,以释放资源。

需要注意的是,使用完 Ticker 后,务必调用 Stop 方法,以防止资源泄漏。

6.23.29 Ticker类型中的Reset函数

(*time.Ticker).Reset 函数用于重新设置 Ticker 的时间间隔和下一个触发时间。如果 Ticker 正在运行,调用 Reset 将重置计时器的时间间隔,并从当前时间开始重新计算下一个触发时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个初始间隔为2秒的 Ticker
ticker := time.NewTicker(2 * time.Second)

// 打印前三个触发时间
for i := 0; i < 3; i++ {
fmt.Println(<-ticker.C)
}

// 重新设置 Ticker 的时间间隔为3秒
ticker.Reset(3 * time.Second)

// 打印后三个触发时间
for i := 0; i < 3; i++ {
fmt.Println(<-ticker.C)
}

// 注意: 当程序退出时,务必停止 Ticker,以释放资源
ticker.Stop()
}

输出结果:

1
2
3
4
5
6
2023-11-14 17:53:09.246818 +0800 CST m=+2.005078301
2023-11-14 17:53:11.2556259 +0800 CST m=+4.013886201
2023-11-14 17:53:13.2538786 +0800 CST m=+6.012138901
2023-11-14 17:53:16.2668979 +0800 CST m=+9.025158201
2023-11-14 17:53:19.2571464 +0800 CST m=+12.015406701
2023-11-14 17:53:22.257664 +0800 CST m=+15.015924301

6.23.30 Ticker类型的Stop函数

(*time.Ticker).Stop 方法用于停止 Ticker。调用 Stop 将停止 Ticker 的触发,关闭其通道,并释放相关资源。如果你不再需要 Ticker,应该调用 Stop 来确保释放资源,以防止资源泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个间隔为1秒的 Ticker
ticker := time.NewTicker(1 * time.Second)

// 打印前五个触发时间
for i := 0; i < 5; i++ {
fmt.Println(<-ticker.C)
}

// 停止 Ticker,关闭通道并释放资源
ticker.Stop()

// 不再接收触发时间,因为 Ticker 已经被停止
for i := 0; i < 5; i++ {
fmt.Println(<-ticker.C)
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
2023-11-14 17:55:56.5476721 +0800 CST m=+1.011014501
2023-11-14 17:55:57.5479809 +0800 CST m=+2.011323301
2023-11-14 17:55:58.5491782 +0800 CST m=+3.012520601
2023-11-14 17:55:59.542745 +0800 CST m=+4.006087401
2023-11-14 17:56:00.5404043 +0800 CST m=+5.003746701
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
D:/goproject/main.go:22 +0xf4
exit status 2

6.23.31 Time类型

在 Go 语言的 time 包中,Time 类型是用于表示时间的结构体。它包含了年、月、日、时、分、秒、纳秒等组件,用于表示一个具体的时刻。Time 类型的定义如下:

1
2
3
4
type Time struct {
// 包含年、月、日、时、分、秒、纳秒等组件
// ...
}

以下是 Time 类型的一些常见操作和用法:

  1. 获取当前时间:

    1
    currentTime := time.Now()
  2. 指定时间:

    1
    specificTime := time.Date(2023, time.November, 14, 12, 30, 0, 0, time.UTC)
  3. 时间格式化:

    1
    formattedTime := currentTime.Format("2006-01-02 15:04:05")
  4. 时间比较:

    1
    2
    3
    4
    5
    6
    7
    if currentTime.After(specificTime) {
    fmt.Println("Current time is after specific time.")
    } else if currentTime.Before(specificTime) {
    fmt.Println("Current time is before specific time.")
    } else {
    fmt.Println("Current time is equal to specific time.")
    }
  5. 时间计算:

    1
    futureTime := currentTime.Add(2 * time.Hour)
  6. 获取年、月、日等组件:

    1
    2
    3
    4
    year := currentTime.Year()
    month := currentTime.Month()
    day := currentTime.Day()
    // ...
  7. 时区操作:

    1
    2
    3
    4
    loc, err := time.LoadLocation("America/New_York")
    if err == nil {
    currentTimeInNY := currentTime.In(loc)
    }

这些是 Time 类型的一些基本用法。Time 类型提供了丰富的方法和函数,用于进行时间操作、格式化、比较等。

6.23.32 Date函数

time.Date 函数用于创建一个指定日期和时间的 time.Time 对象。

1
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
  • year:年份。
  • month:月份,使用 time.Month 类型。
  • day:日期。
  • hour:小时。
  • min:分钟。
  • sec:秒。
  • nsec:纳秒。
  • loc:时区,可以为 nil 表示使用本地时区。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个指定日期和时间的 time.Time 对象
specificTime := time.Date(2023, time.November, 14, 12, 30, 0, 0, time.UTC)

// 打印创建的时间对象
fmt.Println("Specific time:", specificTime)
}

输出结果:

1
Specific time: 2023-11-14 12:30:00 +0000 UTC

6.23.33 Now函数

time.Now函数用于获取当前的本地时间。它返回一个time.Time类型的值,表示当前时刻。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前本地时间
currentTime := time.Now()

// 打印当前本地时间
fmt.Println("Current local time:", currentTime)
}

输出结果:

1
Current local time: 2023-11-14 18:00:32.8438956 +0800 CST m=+0.002034401

6.23.34 Parse函数

time.Parse 函数用于将格式化的时间字符串解析为 time.Time 类型。它接受两个参数,第一个参数是表示时间格式的字符串,第二个参数是要解析的时间字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
// 要解析的时间字符串和布局
timeString := "2023-11-14 15:30:00"
layout := "2006-01-02 15:04:05"

// 使用 time.Parse 解析时间字符串
parsedTime, err := time.Parse(layout, timeString)
if err != nil {
fmt.Println("Error parsing time:", err)
return
}

// 打印解析后的时间
fmt.Println("Parsed time:", parsedTime)
}

输出结果:

1
Parsed time: 2023-11-14 15:30:00 +0000 UTC

6.23.35 ParseInLocation函数

time.ParseInLocation 函数与 time.Parse 类似,但它允许你在解析时指定一个特定的时区。

1
func ParseInLocation(layout, value string, loc *Location) (Time, error)
  • layout 是一个时间布局字符串,用于定义输入字符串的格式。
  • value 是要解析的时间字符串。
  • loc 是指定的时区对象(可以为 nil)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"time"
)

func main() {
// 要解析的时间字符串和布局
timeString := "2023-11-14 15:30:00"
layout := "2006-01-02 15:04:05"

// 指定时区为美国纽约
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println("Error loading location:", err)
return
}

// 使用 time.ParseInLocation 解析时间字符串
parsedTime := time.Now().In(loc)
fmt.Println("当前上海时间:", parsedTime)

parsedTime, err = time.ParseInLocation(layout, timeString, loc)
if err != nil {
fmt.Println("Error parsing time:", err)
return
}

// 打印解析后的时间
fmt.Println("解析后的上海时间:", parsedTime)
}

输出结果:

1
2
当前上海时间: 2023-11-14 18:05:04.1865888 +0800 CST
解析后的上海时间: 2023-11-14 15:30:00 +0800 CST

6.23.36 Unix函数

time.Unix 函数用于将 Unix 时间戳转换为 time.Time 对象。Unix 时间戳表示自 1970 年 1 月 1 日(UTC)以来经过的秒数,可以是正数或负数。

1
func Unix(sec int64, nsec int64) Time
  • sec 表示自 1970 年 1 月 1 日以来经过的秒数。
  • nsec 表示秒数之外的纳秒部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个 Unix 时间戳
unixTimestamp := int64(1605134515) // 2020-11-11 11:35:15 UTC
nanoseconds := int64(500000000) // 0.5 seconds

// 使用 time.Unix 转换为 time.Time 对象
convertedTime := time.Unix(unixTimestamp, nanoseconds)

// 打印转换后的时间
fmt.Println("Converted time:", convertedTime)

// 打印 Unix 时间戳
fmt.Println("Unix timestamp:", convertedTime.Unix())

// 打印纳秒部分
fmt.Println("Nanoseconds:", convertedTime.Nanosecond())
}

输出结果:

1
2
3
Converted time: 2020-11-12 06:41:55.5 +0800 CST
Unix timestamp: 1605134515
Nanoseconds: 500000000

6.23.37 UnixMicro函数

UnixMicro返回与给定 Unix 时间相对应当前时间的微秒数,可以是正数或负数。Unix 时间戳表示自 1970 年 1 月 1 日(UTC)以来经过的秒数,

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
umt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Println(umt.UnixMicro())
t := time.UnixMicro(umt.UnixMicro()).UTC()
fmt.Println(t)
}

输出结果:

1
2
1257894000000000
2009-11-10 23:00:00 +0000 UTC

6.23.38 UnixMilli函数

UnixMilli返回与给定 Unix 时间相对应当前时间的毫秒数,可以是正数或负数。Unix 时间戳表示自 1970 年 1 月 1 日(UTC)以来经过的秒数,

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
umt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Println(umt.UnixMilli())
t := time.UnixMilli(umt.UnixMilli()).UTC()
fmt.Println(t)
}

输出结果:

1
2
1257894000000
2009-11-10 23:00:00 +0000 UTC

6.23.39 UnixNano函数

UnixNano 函数是Go语言中time包中的一个函数。它返回一个Time值的纳秒级 Unix 时间戳,表示自1970年1月1日UTC以来的纳秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 获取当前时间的纳秒级 Unix 时间戳
unixNano := currentTime.UnixNano()

fmt.Println("Unix Nano Time:", unixNano)
}

输出结果:

1
Unix Nano Time: 1700033772266196400

6.23.40 Add函数

time.Add 函数用于将指定的时间间隔添加到时间对象,返回一个新的 time.Time 对象。它接受一个 time.Duration 作为参数,该参数表示要添加的时间间隔。

1
func (t Time) Add(d Duration) Time
  • t 是要添加时间间隔的 time.Time 对象。
  • d 是要添加的时间间隔,可以是正数表示向未来移动,也可以是负数表示向过去移动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 添加一小时
oneHourLater := currentTime.Add(1 * time.Hour)

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("One hour later:", oneHourLater)
}

输出结果:

1
2
Current time: 2023-11-14 18:12:37.1338859 +0800 CST m=+0.002069701
One hour later: 2023-11-14 19:12:37.1338859 +0800 CST m=+3600.002069701

6.23.41 AddDate函数

time.AddDate 函数用于将指定的年、月、日时间间隔添加到时间对象,返回一个新的 time.Time 对象。它接受三个整数参数,分别表示要添加的年、月、日的时间间隔。

1
func (t Time) AddDate(years int, months int, days int) Time
  • t 是要添加时间间隔的 time.Time 对象。
  • years 表示要添加的年数。
  • months 表示要添加的月数。
  • days 表示要添加的天数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 添加一年、两个月、三天
newTime := currentTime.AddDate(1, 2, 3)

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("New time:", newTime)
}

输出结果:

1
2
Current time: 2023-11-14 18:13:42.541421 +0800 CST m=+0.002380401
New time: 2025-01-17 18:13:42.541421 +0800 CST

6.23.42 AppendFormat函数

在 Go 语言的 time 包中,time.AppendFormat 函数用于将时间按照指定的格式追加到一个 []byte 中。它允许你将格式化的时间追加到一个切片中,而不是直接写入标准输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 创建一个字节切片
var formattedTime []byte

// 追加格式化的时间到字节切片中
formattedTime = currentTime.AppendFormat(formattedTime, "2006-01-02 15:04:05")

// 打印结果
fmt.Println("Formatted time:", string(formattedTime))
}

输出结果:

1
Formatted time: 2023-11-14 19:09:29

6.23.43 Before函数

在Go语言的time包中,time.Before 函数用于比较两个时间对象,判断第一个时间是否早于第二个时间。如果第一个时间早于第二个时间,则 Before 返回 true,否则返回 false

1
func (t Time) Before(u Time) bool
  • t 是第一个时间对象。
  • u 是第二个时间对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 创建一个比当前时间晚一小时的时间
oneHourLater := currentTime.Add(1 * time.Hour)

// 判断是否在指定时间之前
isBefore := currentTime.Before(oneHourLater)

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("One hour later:", oneHourLater)
fmt.Println("Is before:", isBefore)
}

输出结果:

1
2
3
Current time: 2023-11-14 19:11:33.0166554 +0800 CST m=+0.002534501
One hour later: 2023-11-14 20:11:33.0166554 +0800 CST m=+3600.002534501
Is before: true

6.23.44 Clock函数

Clock函数返回三个值:时分秒

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
hour, min, sec := time.Now().Clock()

fmt.Printf("hour: %d, min: %d, sec: %d", hour, min, sec)
}

输出结果:

1
hour: 19, min: 14, sec: 15

6.23.45 Compare函数

Compare将时刻 t 与 u 进行比较。 如果t在u之前,则返回-1; 如果 t 在 u 之后,则返回 +1; 如果它们相同,则返回 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
current := time.Now()

addOneHour := current.Add(1 * time.Hour)

result := addOneHour.Compare(current)

fmt.Println("result: ", result)
}

输出结果:

1
result:  1

6.23.46 Equal函数

time.Equal 函数用于比较两个时间对象是否相等。如果两个时间相等,则 Equal 返回 true;否则返回 false

1
func (t Time) Equal(u Time) bool
  • t 是第一个时间对象。
  • u 是第二个时间对象。

以下是一个简单的示例,演示了如何使用 time.Equal 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 创建一个与当前时间相同的时间
sameTime := currentTime

// 创建一个比当前时间晚一秒的时间
oneSecondLater := currentTime.Add(1 * time.Second)

// 判断两个时间是否相等
isEqual1 := currentTime.Equal(sameTime)
isEqual2 := currentTime.Equal(oneSecondLater)

// 打印结果
fmt.Println("当前时间:", currentTime)
fmt.Println("相同时间:", sameTime)
fmt.Println("一秒之后:", oneSecondLater)
fmt.Println("相等吗? (当前时间 vs 相同时间):", isEqual1)
fmt.Println("相等吗?(当前时间 vs 一秒后):", isEqual2)
}

输出结果:

1
2
3
4
5
当前时间: 2023-11-14 19:24:02.6944174 +0800 CST m=+0.002544001
相同时间: 2023-11-14 19:24:02.6944174 +0800 CST m=+0.002544001
一秒之后: 2023-11-14 19:24:03.6944174 +0800 CST m=+1.002544001
相等吗? (当前时间 vs 相同时间): true
相等吗?(当前时间 vs 一秒后): false

6.23.47 Format函数

time.Time 类型的 Format 方法用于按照指定的格式将时间对象转换为字符串。

它接受一个时间布局字符串作为参数,该字符串定义了输出字符串的格式。

1
func (t Time) Format(layout string) string
  • t 是要格式化的时间对象。
  • layout 是时间布局字符串,用于定义输出的格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 格式化时间为字符串
formattedTime := currentTime.Format("2006-01-02 15:04:05")

// 打印结果
fmt.Println("格式化时间:", formattedTime)
}

输出结果:

1
格式化时间: 2023-11-14 19:27:46

在这个例子中,我们首先使用 time.Now 获取当前时间,然后使用 Format 方法将时间对象按照指定的格式转换为字符串。最后,我们打印了格式化后的结果。

时间布局字符串中的各种格式占位符表示不同的时间组件,例如:

  • 2006 表示年份。
  • 01 表示月份。
  • 02 表示日期。
  • 15 表示小时(24小时制)。
  • 04 表示分钟。
  • 05 表示秒。

你可以根据需要组合这些格式占位符来定义自己的时间布局。

6.23.48 GoString函数

在 Go 语言的 time 包中,time.Time 类型实现了 fmt.GoStringer 接口,该接口包含了 GoString 方法。GoString 方法返回一个包含时间信息的 Go 语法表示的字符串。

1
func (t Time) GoString() string

以下是一个简单的示例,演示了如何使用 GoString 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 GoString 方法获取 Go 语法表示的字符串
goStringRepresentation := currentTime.GoString()

// 打印结果
fmt.Println("GoString representation:", goStringRepresentation)
}

输出结果:

1
GoString representation: time.Date(2023, time.November, 14, 19, 28, 38, 247379100, time.Local)

在这个例子中,我们首先使用 time.Now 获取当前时间,然后调用 GoString 方法获取该时间对象的 Go 语法表示的字符串。最后,我们打印了结果。

GoString 方法返回的字符串包含的信息通常足够详细,以便在调试或打印格式化的输出时使用。

6.23.49 GobDecode函数

在 Go 语言中,time.Time 类型实现了 encoding/gob 包中的 GobDecoder 接口,该接口包含了 GobDecode 方法。GobDecode 方法用于解码 Gob 格式的数据并填充接收者对象。

1
func (t *Time) GobDecode(data []byte) error
  • t 是接收者对象的指针,即 *time.Time 类型。
  • data 是包含 Gob 格式数据的字节切片。

以下是一个简单的示例,演示了如何使用 GobDecode 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"bytes"
"encoding/gob"
"fmt"
"time"
)

func main() {
// 创建一个时间对象
currentTime := time.Now()

// 使用 GobEncode 方法将时间对象编码为字节切片
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(currentTime)
if err != nil {
fmt.Println("Error encoding time:", err)
return
}

// 使用 GobDecode 方法将字节切片解码为时间对象
var decodedTime time.Time
decoder := gob.NewDecoder(&buffer)
err = decoder.Decode(&decodedTime)
if err != nil {
fmt.Println("Error decoding time:", err)
return
}

// 打印结果
fmt.Println("原时间:", currentTime)
fmt.Println("解码Gob格式:", decodedTime)
}

输出结果:

1
2
原时间: 2023-11-14 19:37:38.325113 +0800 CST m=+0.002258801
解码Gob格式: 2023-11-14 19:37:38.325113 +0800 CST

在这个例子中,我们首先创建了一个时间对象 currentTime。然后,我们使用 GobEncode 方法将时间对象编码为字节切片,并通过 GobDecode 方法将字节切片解码为另一个时间对象 decodedTime。最后,我们打印了原始的时间对象和解码后的时间对象。

需要注意的是,为了使用 GobDecode 方法,我们需要导入 encoding/gob 包。此外,由于 GobDecode 方法是作为指针接收者定义的,我们需要传递 *time.Time 类型的指针。

6.23.50 GobEncode函数

在 Go 语言中,time.Time 类型实现了 encoding/gob 包中的 GobEncoder 接口,该接口包含了 GobEncode 方法。GobEncode 方法用于将接收者对象编码为 Gob 格式的数据。

1
func (t Time) GobEncode() ([]byte, error)
  • t 是接收者对象,即 time.Time 类型。

GobEncode 方法返回一个字节切片和一个可能的错误。字节切片包含了 Gob 格式的数据,可以用于后续的存储或传输。

以下是一个简单的示例,演示了如何使用 GobEncode 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"bytes"
"encoding/gob"
"fmt"
"time"
)

func main() {
// 创建一个时间对象
currentTime := time.Now()

// 使用 GobEncode 方法将时间对象编码为字节切片
encodedData, err := currentTime.GobEncode()
if err != nil {
fmt.Println("Error encoding time:", err)
return
}

// 打印编码后的数据
fmt.Println("Encoded data:", encodedData)

// 使用 GobDecode 方法将字节切片解码为时间对象
var decodedTime time.Time
decoder := gob.NewDecoder(bytes.NewReader(encodedData))
err = decoder.Decode(&decodedTime)
if err != nil {
fmt.Println("Error decoding time:", err)
return
}

// 打印结果
fmt.Println("Original time:", currentTime)
fmt.Println("Decoded time:", decodedTime)
}

输出结果:

1
2
Encoded data: [1 0 0 0 14 220 229 85 83 15 104 21 40 1 224]
Error decoding time: gob: decoding into local type *time.Time, received remote type unknown type

在这个例子中,我们首先创建了一个时间对象 currentTime。然后,我们使用 GobEncode 方法将时间对象编码为字节切片,并通过 GobDecode 方法将字节切片解码为另一个时间对象 decodedTime。最后,我们打印了原始的时间对象、编码后的数据和解码后的时间对象。

需要注意的是,为了使用 GobEncode 方法,我们需要导入 encoding/gob 包。

6.23.51 Year函数

在 Go 语言的 time 包中,YearTime 类型的方法,用于返回时间中的年份。方法的签名如下:

1
func (t Time) Year() int

这个方法返回一个 Time 值所代表的年份。以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Year 方法获取年份
year := currentTime.Year()

// 输出结果
fmt.Println("Current Year:", year)
}

在这个例子中,Year 方法返回当前时间的年份,并将其打印出来。请注意,Year 方法返回的是一个 int 类型的值。

6.23.52 YearDay函数

在 Go 语言的 time 包中,YearDayTime 类型的方法,用于返回一年中的第几天。方法的签名如下:

1
func (t Time) YearDay() int

这个方法返回一个 Time 值所代表的年份中的第几天。返回的范围是1到365(或366,如果是闰年)。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 YearDay 方法获取一年中的第几天
yearDay := currentTime.YearDay()

// 输出结果
fmt.Println("Day of the Year:", yearDay)
}

在这个例子中,YearDay 方法返回当前时间是一年中的第几天,并将其打印出来。请注意,返回的值是一个 int 类型。

6.23.53 Month函数

Month 返回 t 指定的一年中的月份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Month 方法获取当前时间的小时部分
month := currentTime.Month()

// 打印结果
fmt.Println("当前时间:", currentTime)
fmt.Println("月:", month)
}

输出结果:

1
2
当前时间: 2023-11-14 21:03:57.0911811 +0800 CST m=+0.002591001
月: November

6.23.54 Weekday函数

Weekday 方法返回一个表示当前时间是星期几的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Weekday 方法获取星期几
weekday := currentTime.Weekday()

// 输出结果
fmt.Println("Today is:", weekday)
}

输出结果:

1
Today is: Wednesday

6.23.55 Day函数

Day 返回 t 指定的月份中的第几天。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
d := time.Date(2000, 2, 2, 12, 30, 0, 0, time.UTC)
day := d.Day()

fmt.Printf("day = %v\n", day)

}

输出结果:

1
day = 2

6.23.56 Hour函数

在 Go 语言的 time 包中,time.Time 类型有一个 Hour 方法,用于获取时间对象的小时部分。该方法返回一个表示小时的整数,范围从0到23。

1
func (t Time) Hour() int

以下是一个简单的示例,演示了如何使用 Hour 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Hour 方法获取当前时间的小时部分
hour := currentTime.Hour()

// 打印结果
fmt.Println("当前时间:", currentTime)
fmt.Println("时:", hour)
}

输出结果:

1
2
当前时间: 2023-11-14 19:51:45.961792 +0800 CST m=+0.002578501
时: 19

6.23.57 Minute函数

Minute 返回 t 指定的小时内的分钟偏移量,范围为 [0, 59]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Minute 方法获取当前时间的分钟部分
minute := currentTime.Minute()

// 打印结果
fmt.Println("当前时间:", currentTime)
fmt.Println("分:", minute)
}

输出结果:

1
2
当前时间: 2023-11-14 20:14:21.98869 +0800 CST m=+0.002554701
分: 14

6.23.58 Second函数

Second 返回 t 指定的分钟内的第二个偏移量,范围为 [0, 59]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Second 方法获取当前时间的秒钟部分
second := currentTime.Second()

// 打印结果
fmt.Println("当前时间:", currentTime)
fmt.Println("秒:", second)
}

输出结果:

1
2
当前时间: 2023-11-14 20:15:55.1795929 +0800 CST m=+0.002543301
秒: 55

6.23.59 Nanosecond函数

在 Go 语言的 time 包中,NanosecondTime 类型的一个方法,用于获取时间的纳秒部分。方法的签名如下:

1
func (t Time) Nanosecond() int

Nanosecond 方法返回一个整数,表示时间的纳秒部分。以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Nanosecond 方法获取时间的纳秒部分
nanoseconds := currentTime.Nanosecond()

// 输出结果
fmt.Println("Nanoseconds:", nanoseconds)
}

在这个例子中,Nanosecond 方法被用于获取当前时间的纳秒部分,并将其打印出来。注意,这个值的范围是 0 到 999999999(纳秒),表示给定时间的纳秒偏移量。

6.23.60 ISOWeek函数

可以使用 ISOWeek 函数来获取时间属于一年中的 ISO 周数。

1
func ISOWeek(t Time) (year, week int)
  • t 是一个 time.Time 对象。

以下是一个简单的示例,演示如何使用 ISOWeek 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 ISOWeek 函数获取 ISO 周数
year, week := currentTime.ISOWeek()

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Printf("ISO Week: Year %d, Week %d\n", year, week)
}

输出结果:

1
2
Current time: 2023-11-14 19:54:54.9437003 +0800 CST m=+0.002550701
ISO Week: Year 2023, Week 46

在这个例子中,我们首先使用 time.Now 获取当前时间,然后使用 ISOWeek 函数获取当前时间的 ISO 周数,包括所属的年份和周数。最后,我们打印了当前时间和 ISO 周数的结果。

需要注意的是,ISOWeek 函数并非 time.Time 类型的方法,而是一个独立的函数。

6.23.61 In函数

在 Go 语言的 time 包中,time.Time 类型提供了 In 方法,用于将时间对象从一个时区转换为另一个时区。

1
func (t Time) In(loc *Location) Time
  • t 是要转换时区的时间对象。
  • loc 是目标时区的 Location 对象。

以下是一个简单的示例,演示了如何使用 In 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 创建一个新的时区
anotherLocation, err := time.LoadLocation("America/Los_Angeles")
if err != nil {
fmt.Println("Error loading location:", err)
return
}

// 使用 In 方法将时间对象转换到新时区
convertedTime := currentTime.In(anotherLocation)

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("Converted time:", convertedTime)
}

输出结果:

1
2
Current time: 2023-11-14 19:57:13.9589443 +0800 CST m=+0.002562401
Converted time: 2023-11-14 03:57:13.9589443 -0800 PST

在这个例子中,我们首先使用 time.Now 获取当前时间,然后创建了一个新的时区对象 anotherLocation(表示美国洛杉矶时区)。接着,我们使用 In 方法将当前时间对象转换到新的时区,并打印了原始时间和转换后的时间。

需要注意的是,In 方法不会修改原始的 time.Time 对象,而是返回一个新的对象。

6.23.62 IsDST函数

可以使用 time.LoadLocation 函数和 time.TimeIn 方法来判断某个时间是否处于夏令时(Daylight Saving Time,DST)。

以下是一个示例,演示如何检查某个时间是否处于夏令时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

isdst := currentTime.IsDST()
fmt.Println("是否为夏令时:", isdst)
}

输出结果:

1
是否为夏令时: false

6.23.63 IsZero函数

IsZero 判断时间 t 是否代表零时刻,即 1 年 1 月 1 日 00:00:00 UTC。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"time"
)

func main() {
thistime := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)

iszero := thistime.IsZero()
fmt.Println("是否是零时刻:", iszero)
}

输出结果:

1
是否是零时刻: true

6.23.64 Local函数

Local 返回 t,并将位置设置为当地时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func main() {
anotherLocation, _ := time.LoadLocation("America/Los_Angeles")

// 获取当前时间
currentTime := time.Date(2023, 11, 14, 0, 0, 0, 0, anotherLocation)

// 使用 Local 方法将时间转换为本地时区
localTime := currentTime.Local()

// 打印结果
fmt.Println("美国时间:", currentTime)
fmt.Println("本地时间:", localTime)
}

输出结果:

1
2
美国时间: 2023-11-14 00:00:00 -0800 PST
本地时间: 2023-11-14 16:00:00 +0800 CST

6.23.65 MarshalBinary函数

在 Go 语言的 time 包中,time.Time 类型实现了 encoding.BinaryMarshaler 接口,该接口包含了 MarshalBinary 方法。MarshalBinary 方法用于将时间对象编码为二进制数据。

1
func (t Time) MarshalBinary() ([]byte, error)
  • t 是要编码的时间对象。

该方法返回一个字节切片和可能的错误。字节切片包含了二进制编码的时间数据,可以用于后续的存储或传输。

以下是一个简单的示例,演示如何使用 MarshalBinary 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"encoding/base64"
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 MarshalBinary 方法将时间对象编码为二进制数据
binaryData, err := currentTime.MarshalBinary()
if err != nil {
fmt.Println("Error encoding time:", err)
return
}

// 将二进制数据转换为 base64 编码的字符串以便打印
base64Encoded := base64.StdEncoding.EncodeToString(binaryData)

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("Binary data (base64 encoded):", base64Encoded)
}

输出结果:

1
2
Current time: 2023-11-14 20:09:54.8877244 +0800 CST m=+0.002037801
Binary data (base64 encoded): AQAAAA7c5VqSNOmZcAHg

在这个例子中,我们首先使用 time.Now 获取当前时间,然后使用 MarshalBinary 方法将时间对象编码为二进制数据。为了方便打印,我们将二进制数据进行了 base64 编码。最后,我们打印了原始时间和编码后的数据。

需要注意的是,MarshalBinary 方法返回的是二进制编码的数据,可以按需进行存储或传输。

6.23.64 MarshalJSON函数

在 Go 语言的 time 包中,time.Time 类型实现了 encoding/json 包中的 Marshaler 接口,该接口包含了 MarshalJSON 方法。MarshalJSON 方法用于将时间对象转换为 JSON 格式的数据。

1
func (t Time) MarshalJSON() ([]byte, error)
  • t 是要编码的时间对象。

该方法返回一个字节切片和可能的错误。字节切片包含了时间对象的 JSON 表示,可以用于 JSON 编码。

以下是一个简单的示例,演示如何使用 MarshalJSON 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"encoding/json"
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 MarshalJSON 方法将时间对象转换为 JSON 格式的数据
jsonData, err := currentTime.MarshalJSON()
if err != nil {
fmt.Println("Error encoding time:", err)
return
}

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("JSON data:", string(jsonData))
}

输出结果:

1
2
Current time: 2023-11-14 20:11:28.6785208 +0800 CST m=+0.002038001
JSON data: "2023-11-14T20:11:28.6785208+08:00"

在这个例子中,我们首先使用 time.Now 获取当前时间,然后使用 MarshalJSON 方法将时间对象转换为 JSON 格式的数据。最后,我们打印了原始时间和 JSON 数据。

需要注意的是,MarshalJSON 方法返回的是时间对象的 JSON 表示,可以按需用于 JSON 编码。

6.23.65 MarshalText函数

在 Go 语言的 time 包中,time.Time 类型实现了 encoding.TextMarshaler 接口,该接口包含了 MarshalText 方法。MarshalText 方法用于将时间对象转换为文本格式的数据。

1
func (t Time) MarshalText() ([]byte, error)
  • t 是要编码的时间对象。

该方法返回一个字节切片和可能的错误。字节切片包含了时间对象的文本表示,可以用于文本编码。

以下是一个简单的示例,演示如何使用 MarshalText 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 MarshalText 方法将时间对象转换为文本格式的数据
textData, err := currentTime.MarshalText()
if err != nil {
fmt.Println("Error encoding time:", err)
return
}

// 打印结果
fmt.Println("Current time:", currentTime)
fmt.Println("Text data:", string(textData))
}

输出结果:

1
2
Current time: 2023-11-14 20:12:53.3251831 +0800 CST m=+0.002559401
Text data: 2023-11-14T20:12:53.3251831+08:00

在这个例子中,我们首先使用 time.Now 获取当前时间,然后使用 MarshalText 方法将时间对象转换为文本格式的数据。最后,我们打印了原始时间和文本数据。

需要注意的是,MarshalText 方法返回的是时间对象的文本表示,可以按需用于文本编码。

6.23.66 Round函数

Round 方法将时间 t 四舍五入到距离零时刻最近的 d 的倍数,对于处于中间值的舍入行为是向上舍入。
如果 d <= 0,Round 返回 t,除了去除任何单调时钟读数外,保持不变。
Round 操作的是时间自零时刻以来的绝对持续时间;它不操作时间的表示形式。
因此,Round(Hour) 可能返回一个具有非零分钟的时间,这取决于时间的 Location。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"time"
)

func main() {
t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC)
round := []time.Duration{
time.Nanosecond,
time.Microsecond,
time.Millisecond,
time.Second,
2 * time.Second,
time.Minute,
10 * time.Minute,
time.Hour,
}

for _, d := range round {
fmt.Printf("t.Round(%6s) = %s\n", d, t.Round(d).Format("15:04:05.999999999"))
}
}

输出结果:

1
2
3
4
5
6
7
8
t.Round(   1ns) = 12:15:30.918273645
t.Round( 1µs) = 12:15:30.918274
t.Round( 1ms) = 12:15:30.918
t.Round( 1s) = 12:15:31
t.Round( 2s) = 12:15:30
t.Round( 1m0s) = 12:16:00
t.Round( 10m0s) = 12:20:00
t.Round(1h0m0s) = 12:00:00

6.23.67 String函数

字符串返回使用格式字符串格式化的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
timeWithNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 15, time.UTC)
withNanoseconds := timeWithNanoseconds.String()

timeWithoutNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 0, time.UTC)
withoutNanoseconds := timeWithoutNanoseconds.String()

fmt.Printf("有纳秒 = %v\n", string(withNanoseconds))
fmt.Printf("没纳秒 = %v\n", string(withoutNanoseconds))
}

输出结果:

1
2
有纳秒 = 2000-02-01 12:13:14.000000015 +0000 UTC
没纳秒 = 2000-02-01 12:13:14 +0000 UTC

6.23.68 Sub函数

Sub方法返回时间 t 减去时间 u 后的时间差。如果结果超过了 Duration 可以存储的最大(或最小)值,则会返回最大(或最小)的时间差。要计算 t 减去持续时间 d,可以使用 t.Add(-d)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"time"
)

func main() {
start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)

difference := end.Sub(start)
fmt.Printf("差距 = %v\n", difference)
}

输出结果:

1
差距 = 12h0m0s

6.23.69 Truncate函数

Truncate 方法将时间 t 向下舍入到距离零时刻最近的 d 的倍数后的时间,如果 d <= 0,Truncate 返回 t,除了去除任何单调时钟读数外,保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"time"
)

func main() {
t, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 12:15:30.918273645")
trunc := []time.Duration{
time.Nanosecond,
time.Microsecond,
time.Millisecond,
time.Second,
2 * time.Second,
time.Minute,
10 * time.Minute,
}

for _, d := range trunc {
fmt.Printf("t.Truncate(%5s) = %s\n", d, t.Truncate(d).Format("15:04:05.999999999"))
}
// To round to the last midnight in the local timezone, create a new Date.
midnight := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local)
_ = midnight
}

输出结果:

1
2
3
4
5
6
7
t.Truncate(  1ns) = 12:15:30.918273645
t.Truncate( 1µs) = 12:15:30.918273
t.Truncate( 1ms) = 12:15:30.918
t.Truncate( 1s) = 12:15:30
t.Truncate( 2s) = 12:15:30
t.Truncate( 1m0s) = 12:15:00
t.Truncate(10m0s) = 12:10:00

6.23.70 UTC函数

UTC 返回 t,并将位置设置为 UTC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"time"
)

func main() {
// Get the current time in the local timezone
localTime := time.Now()
fmt.Println("Local Time:", localTime)

// Get the current time in UTC
utcTime := localTime.UTC()
fmt.Println("UTC Time:", utcTime)
}

输出结果:

1
2
Local Time: 2023-11-15 14:25:53.6590843 +0800 CST m=+0.002991101
UTC Time: 2023-11-15 06:25:53.6590843 +0000 UTC

6.23.71 UnmarshalBinary函数

在Go语言中,UnmarshalBinary 是一个接口方法,用于自定义类型的二进制反序列化。具体而言,它是由 encoding.BinaryUnmarshaler 接口定义的方法之一。该接口有一个方法:

1
2
3
type BinaryUnmarshaler interface {
UnmarshalBinary(data []byte) error
}

这表示,如果你的类型实现了 UnmarshalBinary([]byte) error 方法,它就满足了 BinaryUnmarshaler 接口,从而可以被用于二进制反序列化。

以下是一个简单的例子,演示了如何实现 UnmarshalBinary 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"encoding/binary"
"fmt"
)

type MyStruct struct {
Field1 int
Field2 string
}

func (m *MyStruct) UnmarshalBinary(data []byte) error {
if len(data) < 4 {
return fmt.Errorf("insufficient data for Field1")
}

m.Field1 = int(binary.LittleEndian.Uint32(data[:4]))

if len(data) < 4+len(m.Field2) {
return fmt.Errorf("insufficient data for Field2")
}

m.Field2 = string(data[4 : 4+len(m.Field2)])

return nil
}

func main() {
// 二进制数据,例如从某处读取的数据
binaryData := []byte{0x01, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f}

// 创建一个 MyStruct 类型的变量
myInstance := MyStruct{}

// 调用 UnmarshalBinary 方法进行反序列化
err := myInstance.UnmarshalBinary(binaryData)
if err != nil {
fmt.Println("Error during UnmarshalBinary:", err)
return
}

// 输出结果
fmt.Printf("Field1: %d\n", myInstance.Field1)
fmt.Printf("Field2: %s\n", myInstance.Field2)
}

在这个例子中,MyStruct 类型实现了 UnmarshalBinary 方法,该方法按照特定的规则从二进制数据中提取字段值。这个例子中的 MyStruct 类型有一个整数字段 Field1 和一个字符串字段 Field2

6.23.72 UnmarshalJSON函数

在 Go 语言中,UnmarshalJSON 是一个接口方法,用于自定义类型的 JSON 反序列化。具体而言,它是由 encoding/json 包中的 Unmarshaler 接口定义的方法之一。该接口有一个方法:

1
2
3
type Unmarshaler interface {
UnmarshalJSON(data []byte) error
}

这表示,如果你的类型实现了 UnmarshalJSON([]byte) error 方法,它就满足了 Unmarshaler 接口,从而可以被用于 JSON 反序列化。

以下是一个简单的例子,演示了如何实现 UnmarshalJSON 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"encoding/json"
"fmt"
)

type MyStruct struct {
Field1 int `json:"field1"`
Field2 string `json:"field2"`
}

func (m *MyStruct) UnmarshalJSON(data []byte) error {
var temp struct {
Field1 int `json:"field1"`
Field2 string `json:"field2"`
}

if err := json.Unmarshal(data, &temp); err != nil {
return err
}

m.Field1 = temp.Field1
m.Field2 = temp.Field2

return nil
}

func main() {
// JSON 数据
jsonData := []byte(`{"field1": 42, "field2": "Hello, JSON!"}`)

// 创建一个 MyStruct 类型的变量
myInstance := MyStruct{}

// 调用 UnmarshalJSON 方法进行 JSON 反序列化
err := json.Unmarshal(jsonData, &myInstance)
if err != nil {
fmt.Println("Error during UnmarshalJSON:", err)
return
}

// 输出结果
fmt.Printf("Field1: %d\n", myInstance.Field1)
fmt.Printf("Field2: %s\n", myInstance.Field2)
}

在这个例子中,MyStruct 类型实现了 UnmarshalJSON 方法,该方法使用临时结构体进行 JSON 反序列化,然后将结果赋值给目标类型。这个例子中的 MyStruct 类型有一个整数字段 Field1 和一个字符串字段 Field2,并且通过 JSON 标签指定了字段名。

6.23.73 UnmarshalText函数

在 Go 语言中,UnmarshalText 是一个接口方法,用于自定义类型的文本(text)反序列化。具体而言,它是由 encoding 包中的 TextUnmarshaler 接口定义的方法之一。该接口有一个方法:

1
2
3
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}

这表示,如果你的类型实现了 UnmarshalText([]byte) error 方法,它就满足了 TextUnmarshaler 接口,从而可以被用于文本反序列化。

以下是一个简单的例子,演示了如何实现 UnmarshalText 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"fmt"
)

type MyStruct struct {
Field1 int
Field2 string
}

func (m *MyStruct) UnmarshalText(text []byte) error {
var temp struct {
Field1 int
Field2 string
}

// 在实际应用中,这里可能需要一个更复杂的文本解析逻辑
_, err := fmt.Sscanf(string(text), "%d %s", &temp.Field1, &temp.Field2)
if err != nil {
return err
}

m.Field1 = temp.Field1
m.Field2 = temp.Field2

return nil
}

func main() {
// 文本数据
textData := []byte("42 Hello")

// 创建一个 MyStruct 类型的变量
myInstance := MyStruct{}

// 调用 UnmarshalText 方法进行文本反序列化
err := myInstance.UnmarshalText(textData)
if err != nil {
fmt.Println("Error during UnmarshalText:", err)
return
}

// 输出结果
fmt.Printf("Field1: %d\n", myInstance.Field1)
fmt.Printf("Field2: %s\n", myInstance.Field2)
}

在这个例子中,MyStruct 类型实现了 UnmarshalText 方法,该方法使用 fmt.Sscanf 函数进行文本解析,然后将结果赋值给目标类型。这个例子中的 MyStruct 类型有一个整数字段 Field1 和一个字符串字段 Field2。在实际应用中,UnmarshalText 的实现可能需要更复杂的文本解析逻辑,具体取决于你的需求。

6.23.74 Zone函数

在 Go 语言的 time 包中,Zone 是一个函数,而不是 Time 类型的方法。该函数用于获取给定时间的时区信息。

1
func Zone(name string, offset int) (*Location, error)

Zone 函数接受两个参数:name 是时区的名称,offset 是与 UTC 时间的偏移秒数。它返回一个 Location 类型和一个错误。Location 类型表示一个地理时区的信息。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 获取当前时间的时区信息
location, err := time.LoadLocation("America/New_York")
if err != nil {
fmt.Println("Error loading location:", err)
return
}

// 使用 Zone 函数获取时区信息
zone, _ := time.Zone()

// 输出结果
fmt.Println("Current Location:", location)
fmt.Println("Zone Information:", zone)
}

在这个例子中,time.LoadLocation 函数加载一个特定的时区信息(在这里是纽约时区),然后 time.Zone 函数用于获取当前时间的时区信息。请注意,Zone 函数一般不直接使用,而是通过 LoadLocationFixedZone 这样的函数来获取时区信息。

6.23.75 ZoneBounds函数

在 Go 语言的 time 包中,没有名为 ZoneBounds 的函数。然而,Go 1.15 引入了 LoadLocationFromTZData 函数,它可以用于加载自定义时区信息并返回该时区在给定时间的边界。

1
func LoadLocationFromTZData(name string, data []byte) (*Location, error)

这个函数接受时区的名称和与时区相关的 TZ 数据(通常是时区文件的内容),然后返回一个 Location 类型和一个错误。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func main() {
// 示例时区数据(纽约时区)
nyData, err := time.LoadLocation("America/New_York")
if err != nil {
fmt.Println("Error loading New York location:", err)
return
}

// 获取当前时间
currentTime := time.Now()

// 获取边界信息
start, end := nyData.ZoneBounds(currentTime)

// 输出结果
fmt.Println("Time Zone Start:", start)
fmt.Println("Time Zone End:", end)
}

在这个例子中,ZoneBounds 方法被用于获取给定时间在指定时区的边界信息。请注意,这个函数在 Go 1.15 版本中引入,因此如果你使用的是较早的版本,你可能需要升级 Go 语言版本。

6.23.76 AfterFunc函数

在 Go 语言的 time 包中,AfterFunc 是一个函数,用于创建一个在指定时间后执行的定时器。它的签名如下:

1
func AfterFunc(d Duration, f func()) *Timer

AfterFunc 接受两个参数:d 是一个时间段 (time.Duration),表示多长时间后执行回调函数 ff 是一个函数,表示定时器到期时要执行的回调操作。该函数返回一个 Timer 对象,你可以用来取消定时器。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个定时器,在2秒后执行回调函数
timer := time.AfterFunc(2*time.Second, func() {
fmt.Println("Timer expired!")
})

// 等待一段时间,以确保有足够时间执行回调
time.Sleep(3 * time.Second)

// 取消定时器
timer.Stop()

// 输出结果
fmt.Println("Program completed.")
}

在这个例子中,AfterFunc 创建了一个在2秒后执行的定时器,当定时器到期时,会执行传递的匿名函数,输出 “Timer expired!”。程序等待3秒以确保定时器有足够的时间执行,然后调用 timer.Stop() 取消定时器。最后,程序输出 “Program completed.”。

6.23.77 NewTimer函数

在 Go 语言的 time 包中,NewTimer 是一个函数,用于创建一个定时器,它在指定的时间段之后触发。它的签名如下:

1
func NewTimer(d Duration) *Timer

NewTimer 接受一个参数 d,表示定时器的持续时间(time.Duration),并返回一个 Timer 对象。你可以使用这个对象来等待定时器的到期并执行相应的操作。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个定时器,设置持续时间为2秒
timer := time.NewTimer(2 * time.Second)

fmt.Println("Waiting for the timer to expire...")

// 等待定时器的到期
<-timer.C

fmt.Println("Timer expired!")
}

在这个例子中,NewTimer 创建了一个定时器,设置了持续时间为2秒。程序等待定时器的到期,使用 <-timer.C 来阻塞等待直到定时器触发。一旦定时器触发,程序输出 “Timer expired!”。

需要注意的是,如果你只是需要等待一段时间而不需要执行特定的操作,可以使用 time.Sleep 函数,而不必创建一个定时器。 time.Sleep 会阻塞当前 goroutine 的执行,而 NewTimer 提供了更灵活的方式来处理定时事件。

6.23.78 Reset函数

在 Go 语言的 time 包中,ResetTimer 类型的一个方法,而不是一个单独的函数。Timer 类型表示一个单次的定时器。当 Timer 到期时,它会发送一个时间到期事件给一个通道(C)。

Reset 方法的签名如下:

1
func (t *Timer) Reset(d Duration) bool

Reset 方法用于重置定时器,使其持续时间从当前时间开始重新计算。如果 Reset 返回 true,表示定时器已经被重置,并且之前的定时事件(如果存在)被取消。如果 Reset 返回 false,表示定时器已经被触发过,且未被重置。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个定时器,设置持续时间为2秒
timer := time.NewTimer(2 * time.Second)

fmt.Println("Waiting for the timer to expire...")

// 使用协程等待定时器的到期
go func() {
<-timer.C
fmt.Println("Timer expired!")
}()

// 等待一秒钟,然后重置定时器
time.Sleep(time.Second)
resetSuccess := timer.Reset(3 * time.Second)

if resetSuccess {
fmt.Println("Timer reset successfully.")
} else {
fmt.Println("Timer reset failed. It may have already expired.")
}

// 等待一段时间,确保输出被显示
time.Sleep(5 * time.Second)
}

在这个例子中,程序创建了一个2秒的定时器,等待1秒后重置了定时器为3秒。由于重置前的1秒已经过去,所以 Reset 返回 false,表示定时器已经被触发过。程序最后等待一段时间,确保输出被显示。请注意,在实际应用中,要小心处理并发访问定时器的情况。

6.23.79 Stop函数

在 Go 语言的 time 包中,StopTimer 类型的一个方法,而不是一个单独的函数。Timer 类型表示一个单次的定时器。当 Timer 到期时,它会发送一个时间到期事件给一个通道(C)。

Stop 方法的签名如下:

1
func (t *Timer) Stop() bool

Stop 方法用于停止定时器。如果 Stop 返回 true,表示定时器已经被成功停止,而且之前的定时事件(如果存在)被取消。如果 Stop 返回 false,表示定时器已经被触发过,无法停止。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

func main() {
// 创建一个定时器,设置持续时间为2秒
timer := time.NewTimer(2 * time.Second)

fmt.Println("Waiting for the timer to expire...")

// 使用协程等待定时器的到期
go func() {
<-timer.C
fmt.Println("Timer expired!")
}()

// 等待一秒钟,然后停止定时器
time.Sleep(time.Second)
stopSuccess := timer.Stop()

if stopSuccess {
fmt.Println("Timer stopped successfully.")
} else {
fmt.Println("Timer has already expired and cannot be stopped.")
}

// 等待一段时间,确保输出被显示
time.Sleep(5 * time.Second)
}

在这个例子中,程序创建了一个2秒的定时器,等待1秒后停止了定时器。由于停止前的1秒已经过去,所以 Stop 返回 false,表示定时器已经被触发过。程序最后等待一段时间,确保输出被显示。请注意,在实际应用中,要小心处理并发访问定时器的情况。

6.23.80 Weekday类型

在 Go 语言的 time 包中,Weekday 是一个类型,表示星期几。这个类型定义如下:

1
type Weekday int

Weekday 类型有一组常量,分别表示星期天到星期六:

1
2
3
4
5
6
7
8
9
const (
Sunday Weekday = 0
Monday Weekday = 1
Tuesday Weekday = 2
Wednesday Weekday = 3
Thursday Weekday = 4
Friday Weekday = 5
Saturday Weekday = 6
)

6.23.81 Weekday类型的String函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"time"
)

func main() {
// 获取当前时间
currentTime := time.Now()

// 使用 Weekday 方法获取星期几
weekday := currentTime.Weekday()

// 输出结果
fmt.Println("Today is:", weekday.String())
}

输出结果:

1
Today is: Wednesday

6.24 内置函数

当你使用Golang(Go语言)进行编程时,Golang的设计者提供了一些方便的函数,这些函数无需导入特定的包,即可直接使用。这些被称为Golang的内置函数或内建函数。以下是一些常见的Golang内建函数:

6.24.1 append函数

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
// Append elements to a slice
slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
fmt.Println(slice) // Output: [1 2 3 4 5]
}

输出结果:

1
[1 2 3 4 5]

6.24.2 cap函数

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
// Get the capacity of a slice
slice := make([]int, 3, 5)
fmt.Println(cap(slice)) // Output: 5
}

输出结果:

1
5

6.24.3 Clear函数

go1.21.0版本新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

// create a function main
func main() {
var array []int // create an array to store values which can be removed
array = append(array, 2) // use append function to store values
array = append(array, 3)
array = append(array, 4)
array = append(array, 8)

clear(array)

fmt.Println(array)
}

输出结果:

1
[0 0 0 0]

6.24.4 close函数

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
// Close a channel
c := make(chan int)
close(c)
}

6.24.5 complex函数

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
// Create a complex number
c := complex(1.0, 2.0)
fmt.Println(c) // Output: (1+2i)
}

输出结果:

1
(1+2i)

6.24.6 copy函数

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
// Copy elements from one slice to another
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
fmt.Println(dst) // Output: [1 2 3]
}

输出结果:

1
[1 2 3]

6.24.7 delete函数

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
// Delete a key from a map
m := map[string]int{"a": 1, "b": 2}
delete(m, "a")
fmt.Println(m) // Output: map[b:2]
}

输出结果:

1
map[b:2]

6.25 defer和recover错误处理机制

在Golang中,deferrecover 是一对用于错误处理的机制,通常与 panic 配合使用。这组机制主要用于在发生异常情况时进行优雅的错误处理,而不是直接导致程序崩溃。

6.25.1 defer关键字

defer 用于推迟函数的执行,使其在包含 defer 语句的函数执行完毕之后再执行。defer 常用于确保一些资源的释放或清理工作。

1
2
3
4
func example() {
defer fmt.Println("This will be executed last.")
fmt.Println("This will be executed first.")
}

在上述例子中,fmt.Println("This will be executed last.") 语句会在 example 函数执行完毕之后执行,即使中途发生了 panic

6.25.2 recover函数

recover 用于从 panic 中恢复。在 defer 函数中使用 recover 可以捕获 panic 并防止它传播到调用函数。

1
2
3
4
5
6
7
8
9
10
func recoverExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()

// Some code that may cause panic
panic("Oops, something went wrong!")
}

在上述例子中,recover 函数会捕获发生在 defer 中的 panic,并打印一条错误消息。

通常,deferrecover 结合使用,以确保程序在发生异常时能够进行适当的清理工作,而不是立即退出。这样可以提高程序的健壮性,尽量避免未处理的 panic 导致整个程序崩溃。

6.26 自定义错误

6.26.1 As函数

As 函数是 Go 语言中 errors 包的一部分,用于在错误链中进行类型断言。

主要用于提取错误链中特定的错误类型,赋值到目标变量。

如下例子,在错误链err上寻找与fs.PathError相同的错误。如果有就将错误提取到pathError变量中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"io/fs"
"os"
)

func main() {
if _, err := os.Open("non-existing"); err != nil {
var pathError *fs.PathError

if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
}

6.26.2 Is函数

errors.Is 函数来检查错误链中是否包含特定的错误值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"errors"
"fmt"
"io/fs"
"os"
)

func main() {
if _, err := os.Open("non-existing"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
}
}

6.26.3 Join函数

errors.Join 的作用是将多个错误串联起来,形成一个包含所有错误信息的新错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"errors"
"fmt"
)

func main() {
err1 := errors.New("err1")
err2 := errors.New("err2")
err := errors.Join(err1, err2)
fmt.Println(err)
if errors.Is(err, err1) {
fmt.Println("err is err1")
}
if errors.Is(err, err2) {
fmt.Println("err is err2")
}
}

6.26.4 New函数

errors.New 是 Go 语言中 errors 包提供的一个函数,用于创建一个新的错误。它接受一个字符串作为参数,该字符串表示错误的文本描述,并返回一个新的错误值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"errors"
"fmt"
)

func main() {
// 使用 errors.New 创建一个新的错误
err := errors.New("emit macho dwarf: elf header corrupted")

// 检查错误是否为 nil,如果不是,则打印错误的文本描述
if err != nil {
fmt.Print(err)
}
}

6.26.5 Errorf函数

与New一样,通过格式化函数创建错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
const name, id = "bimmler", 17
err := fmt.Errorf("user %q (id %d) not found", name, id)
if err != nil {
fmt.Print(err)
}
}

6.26.6 Unwrap函数

Unwrap 主要用于处理实现了错误包装机制的类型。如果错误类型有一个 Unwrap 方法,该方法返回错误,那么通过调用 Unwrap 函数,你可以获取到这个错误链中的下一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"errors"
"fmt"
)

func main() {
err1 := errors.New("error1")
err2 := fmt.Errorf("error2: [%w]", err1)

fmt.Println(err2)
fmt.Println(errors.Unwrap(err2))

// Output
// error2: [error1]
// error1
}

6.26.7 Panic函数

可以用内置函数Panic来停止程序执行。panic通常用于表示发生了无法恢复的错误或异常情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
// 在这个例子中,当传递一个负数给divide函数时,会引发panic。
result, err := divide(10, -2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}

func divide(a, b int) (int, error) {
if b == 0 {
// 使用panic函数引发一个运行时恐慌,表示除数为零。
panic("Cannot divide by zero")
}

// 在正常情况下,返回除法结果和nil作为错误。
return a / b, nil
}

6.27 数组

在Go语言中,数组是具有固定长度且元素类型相同的数据结构。数组提供了一种在内存中连续存储元素的方式,这使得对数组的访问非常高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

func main() {
var scores [6]int
scores[0] = 0
scores[1] = 1
scores[2] = 2
scores[3] = 3
scores[4] = 4
scores[5] = 5

for i := 0; i < len(scores); i++ {
fmt.Println(scores[i])
}
}

6.27.1 数组内存结构

由下图,数组的长度是在数组声明时指定的,对于var arr [5]int的数组,其长度可由len函数来获取:

array representation

数组的地址与数组的第一个元素的地址是一致的:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
var arr [3]int16

fmt.Printf("arr的地址为:%p\n", &arr)
fmt.Printf("arr第一个元素的地址为:%p\n", &arr[0])
}

输出结果:
arr的地址为:0xc000096068
arr第一个元素的地址为:0xc000096068

6.27.2 数组初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
// 第一种:
var arr1 [3]int = [3]int{3, 6, 9}
fmt.Println(arr1)

// 第二种:
var arr2 = [3]int{1, 4, 7}
fmt.Println(arr2)

// 第三种:
var arr3 = [...]int{4, 5, 6, 7}
fmt.Println(arr3)

// 第四种:
var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3: 88}
fmt.Println(arr4)
}

6.27.3 数组的遍历

在 Go 语言中,有两种常见的迭代结构:普通 for 循环和键值循环(for range 结构)。

键值循环是 Go 语言特有的一种迭代方式,适用于数组、切片、字符串、映射(map)以及通道。

键值循环的语法:

1
2
3
for key, val := range coll {
// 循环体
}

注意事项:

  1. coll 是你要遍历的数组、切片、字符串、映射或通道。
  2. 每次遍历得到的索引由 key 接收,每次遍历得到的索引位置上的值由 val 接收。
  3. keyval 的名字可以根据需要随意命名,如 kvkeyvalue 等。
  4. 在这个循环中,keyval 是局部变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

func main() {
// 实现的功能: 给出五个学生的成绩,求出成绩的总和,平均数:
// 给出五个学生的成绩:--->数组存储:
// 定义一个数组:
var scores [5]int

// 将成绩存入数组:(循环 + 终端输入)
for i := 0; i < len(scores); i++ {
// i:数组的下标
fmt.Printf("请录入第%d个学生的成绩: ", i+1)
fmt.Scanln(&scores[i])
}

// 展示一下班级的每个学生的成绩: (数组进行遍历)
// 方式1: 普通 for 循环
fmt.Println("方式1: 普通 for 循环")
for i := 0; i < len(scores); i++ {
fmt.Printf("第%d个学生的成绩为: %d\n", i+1, scores[i])
}

fmt.Println("-----------------------------")

// 方式2: for-range 循环
fmt.Println("方式2: for-range 循环")
for key, value := range scores {
fmt.Printf("第%d个学生的成绩为: %d\n", key+1, value)
}

// 如果不需要使用 key,可以用下划线(_)代替
fmt.Println("-----------------------------")
fmt.Println("方式3: for-range 循环,不使用 key")
for _, value := range scores {
fmt.Printf("学生的成绩为: %d\n", value)
}
}

6.27.4 数组注意事项

  1. 长度是类型的一部分:
  • 在 Go 语言中,数组的长度是数组类型的一部分,不同长度的数组被视为不同的类型。
  1. 数组是值类型,进行值传递:
  • 在 Go 中,数组是值类型。默认情况下,对数组的操作是值拷贝,即在函数传递时会复制整个数组,而不是传递对原始数组的引用。
  1. 使用引用传递(指针方式)修改原数组:

    • 如果需要在其他函数中修改原始数组,可以使用引用传递,通常通过传递数组的指针实现。这样,函数内对数组的修改会影响原始数组。

6.27.5 二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

func main() {
// 定义二维数组:
var arr [2][3]int16
fmt.Println(arr)

fmt.Printf("arr的地址是: %p\n", &arr)
fmt.Printf("arr[0]的地址是: %p\n", &arr[0])
fmt.Printf("arr[0][0]的地址是: %p\n", &arr[0][0])
fmt.Printf("arr[1]的地址是: %p\n", &arr[1])
fmt.Printf("arr[1][0]的地址是: %p\n", &arr[1][0])

// 赋值:
arr[0][1] = 47
arr[0][0] = 82
arr[1][1] = 25
fmt.Println(arr)

// 初始化操作:
var arr1 [2][3]int = [2][3]int{{1, 4, 7}, {2, 5, 81}}
fmt.Println(arr1)
}

6.27.6 二维数组的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
// 定义二维数组:
var arr [3][3]int = [3][3]int{{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}

// 第一种遍历方式
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Println(arr[i][j])
}
}

// 第二种遍历方式
for _, row := range arr {
for _, column := range row {
fmt.Printf("value: %d\n", column)
}
}
}

6.28 切片

6.28.1 切片简介

在 Go 语言中,切片是一种对数组的动态窗口引用,它提供了灵活的方式来处理数组片段。

  1. 切片的数据结构:
  • 切片是一个数据结构,包含三个字段:指向底层数组的指针切片的长度len切片的容量cap。这三个字段一起描述了切片的属性和范围。
  1. 底层数组:
  • 切片并不是自包含的数据结构,而是建立在底层数组之上的引用。底层数组是切片的实际数据存储位置。
  1. 切片长度和容量:
  • 切片的长度表示当前切片中的元素数量,容量表示切片底层数组中的可访问元素数量。
    • 容量不会超过底层数组的长度。
  1. 动态扩展和收缩:
  • 切片支持动态扩展和收缩。当切片的长度超过容量时,系统会创建一个新的底层数组,并将原有的元素复制到新数组中。这使得切片可以根据需要动态调整大小。
  1. 切片传递的是引用:

    • 切片是引用类型,传递切片时实际上传递的是指向底层数组的指针。
    • 对切片的修改会影响底层数组和其他引用该底层数组的切片。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
// 创建一个数组
arr := [5]int{1, 2, 3, 4, 5}

// 创建切片,引用数组的一部分
slice := arr[1:4]

// 打印切片的长度和容量
fmt.Printf("切片长度:%d, 切片容量:%d\n", len(slice), cap(slice))

// 修改切片的第一个元素,影响底层数组
slice[0] = 99

// 打印原始数组,会看到原始数组被修改
fmt.Println("原始数组:", arr)
}
1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"
func main() {
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}

slice := intarr[1:3]
fmt.Println("intarr:", intarr)
fmt.Println("slice:", slice)
fmt.Println("slice的元素个数:", len(slice))
fmt.Println("slice的容量:", cap(slice))
}

6.28.2 切片内存分析

slice memory representation

6.28.3 使用make函数定义切片

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
// 定义切片:make函数的三个参数:
// 1、切片类型
// 2、切片长度
// 3、切片容量
slice := make([]int, 4, 20)
fmt.Println(slice)
fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
}

6.28.4 切片遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
slice := make([]int, 4, 20)
slice[0] = 66
slice[1] = 88
slice[2] = 99
slice[3] = 100

for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v] = %v \t", i, slice[i])
}

fmt.Println("\n----------------------------------")
for i, v := range slice {
fmt.Printf("下标: %v, 元素: %v\n", i, v)
}
}

6.28.5 切片的注意事项

  1. 切片的定义和使用:切片在定义后不能直接使用,需要将其引用到一个数组,或者使用 make 函数为切片分配空间。
  2. 切片越界检查:切片在使用时要注意防止越界,即访问超过切片长度的元素。
  3. 切片的简写:切片的定义可以使用简写形式:
    1. var slice = arr[0:end] 等同于 var slice = arr[:end]
    2. var slice = arr[start:len(arr)] 等同于 var slice = arr[start:]
    3. var slice = arr[0:len(arr)] 等同于 var slice = arr[:]
  4. 切片的连续切片:切片可以继续切片,形成对原切片的连续引用。
  5. 切片的元素追加:切片支持在尾部追加元素,使用 append 函数实现。
  6. 切片的复制:切片可以通过 copy 函数进行复制,将一个切片的内容复制到另一个切片。

6.29 映射

6.29.1 映射简介

  1. 映射(Map)概述:映射是 Go 语言中内置的一种类型,用于将键和值相关联。

  2. 通过键(key),我们可以获取对应的值(value)。

  3. 基本语法:

    • 声明一个映射的基本语法为:

      1
      var 变量名 map[key类型]value类型
    • 其中,keyvalue 可以是布尔型、数字、字符串、指针、通道等类型,也可以是只包含前述类型的接口、结构体、数组等。

  4. 常见的 key 和 value 类型:

    • key 通常为 int 或 string 类型,而 value 通常为数字(整数、浮点数)、字符串、映射、结构体等。
    • 但不可以使用切片、映射、函数作为映射的 keyvalue 类型。
  5. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package main

    import "fmt"

    func main() {
    // 声明一个映射,key 类型为 string,value 类型为 int
    var studentScores map[string]int

    // 初始化映射
    studentScores = make(map[string]int)

    // 添加键值对
    studentScores["Alice"] = 95
    studentScores["Bob"] = 87
    studentScores["Charlie"] = 92

    // 打印映射
    fmt.Println("学生成绩映射:", studentScores)

    // 获取值
    score := studentScores["Bob"]
    fmt.Println("Bob 的成绩:", score)
    }

6.29.2 映射的三种创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
// 方式1
var map1 map[int]string
map1 = make(map[int]string, 10)
map1[1] = "hello"
fmt.Println(map1)

// 方式2
var map2 = make(map[int]string)
map2[2] = "tttttt"
fmt.Println(map2)

// 方式3
map3 := map[int]string{1: "aaaaa", 2: "bbbbbbbb"}
fmt.Println(map3)
}

6.29.3 映射的基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
// 定义
b := make(map[int]string)

// 增加
b[20231206] = "张三"
b[20231207] = "李四"
b[20231208] = "王五"
fmt.Println(b)

// 修改
b[20231208] = "赵六"

// 删除
delete(b, 20231207)
fmt.Println(b)

// 查找
value, flag := b[200]
fmt.Println(value)
fmt.Println(flag)
}

6.29.4 映射的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import "fmt"

func main() {
// 定义
a := make(map[int]string)
a[1] = "全金发"
a[2] = "成不忧"
a[3] = "高明根"
a[4] = "哲罗星"
a[5] = "仇松年"
a[6] = "无崖子"
a[7] = "李春来"
a[8] = "萧远山"

for k, v := range a {
fmt.Printf("key: %d value: %s\n", k, v)
}

// 定义
c := make(map[int]map[int]string)
c[1] = make(map[int]string)
c[1][1] = "狮鼻子"
c[1][2] = "韩非"
c[1][3] = "宗赞王子"

c[2] = make(map[int]string)
c[2][1] = "游坦之"
c[2][2] = "舒奇"
c[2][3] = "李世民"

c[3] = make(map[int]string)
c[3][1] = "桑昆"
c[3][2] = "焦木和尚"
c[3][3] = "貂禅"

for k, v := range c {
for k2, v2 := range v {
fmt.Printf("key1: %d key2: %d value: %s\n", k, k2, v2)
}
}
}

6.30 面向对象

什么是类?类是属性和方法的抽象。而golang中是没有类的概念的,但是可以用结构体来替代类的功能。

Golang语言面向对象编程说明:

  1. Golang支持面向对象编程:
  • Golang支持面向对象编程,但与传统的面向对象编程有一些区别,因此并不是纯粹的面向对象语言。
  1. 没有类,使用结构体:
  • Golang没有类的概念,而是使用结构体(struct)来实现面向对象编程。结构体在Golang中与其他编程语言的类有同等的地位。
  1. 简洁的面向对象特性:
  • Golang的面向对象编程非常简洁,省略了传统OOP语言中的方法重载、构造函数和析构函数、隐藏的this指针等概念。
  1. 继承、封装和多态的实现方式:
    • Golang仍然支持面向对象编程的继承、封装和多态特性,但实现方式与其他OOP语言不同。
      • 继承: Golang使用匿名字段来实现继承,而不是通过关键字extends。
      • 封装: 封装在Golang中通过结构体字段的可见性来实现,使用首字母大小写来控制成员的访问权限。
      • 多态: 多态的实现方式也不同,Golang通过接口(interface)来实现多态性。

6.30.1 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Teacher struct {
Name string
Age int
School string
}

func main() {
var t1 Teacher
fmt.Println(t1)

t1.Name = "李俊杰"
t1.Age = 31
t1.School = "华南理工大学"

fmt.Println(t1)
}

6.30.2 结构体内存分析

Memory Alignment. Today we're going to talk about memory… | by 👨‍💻 Dillen  Meijboom | Dev Genius

6.30.3 结构体的四种创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type Teacher struct {
Name string
Age int
School string
}

func main() {
// 方式1
var t1 Teacher
t1.Name = "小明"
t1.Age = 18
t1.School = "清华大学"
fmt.Println(t1)

// 方式2
var t2 Teacher = Teacher{"李四", 15, "广东大学"}
fmt.Println(t2)

// 方式3
var t3 *Teacher = new(Teacher)
(*t3).Name = "王五"
(*t3).Age = 16
(*t3).School = "北京大学"
fmt.Println(*t3)

// 方式4
var t4 *Teacher = &Teacher{"马六", 12, "广西大学"}
fmt.Println(*t4)
}

6.30.4 结构体之间的转换

结构体转换时,被转换的结构体需要与目标结构体拥有完全相同的字段和类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type Student struct {
Age int
}

type Person struct {
Age int
}

func main() {
var s Student = Student{10}
var p Person = Person{10}
s = Student(p)
fmt.Println(s)
fmt.Println(p)
}

结构体用type取别名后,Golang会认为这是一个新的数据类型,但是相互间可以强转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Student struct {
Age int
}

type Stu Student

func main() {
var s1 Student = Student{19}
var s2 Stu = Stu{19}
s1 = Student(s2)
fmt.Println(s1)
fmt.Println(s2)
}

将方法与结构体挂钩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type Person struct {
Name string
Age int
}

func (p Person) SayHello() {
fmt.Printf("我的名字:%s, 我的年龄:%d\n", p.Name, p.Age)
}

func main() {
var p Person
p.Name = "李俊男"
p.Age = 31
p.SayHello()
}

结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式。

也就是func (p Person) SayHello() {中的p变量是值传递过来的,如果对该值进行修改,不会影响外部的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type Person struct {
Name string
Age int
}

func (p Person) SayHello() {
p.Name = "李丑男"
fmt.Printf("我的名字:%s, 我的年龄:%d\n", p.Name, p.Age)
}

func main() {
var p Person
p.Name = "李俊男"
p.Age = 31
p.SayHello()

// 可以发现此处的值 不受到 SayHello 的影响
fmt.Println(p.Name)
}

如程序员希望在方法中,改变结构体中的字段值,则可以通过指针的方式来处理。

可以发现上面的func (p Person) SayHello() {修改为func (p *Person) SayHello() {

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type Person struct {
Name string
Age int
}

func (p *Person) SayHello() {
p.Name = "李丑男"
fmt.Printf("我的名字:%s, 我的年龄:%d\n", p.Name, p.Age)
}

func main() {
var p Person
p.Name = "李俊男"
p.Age = 31
p.SayHello()

// 可以发现此处的值 不受到 SayHello 的影响
fmt.Println(p.Name)
}

Golang中的方法可以作用在指定的数据类型上的。因此自定义类型,都可以有方法,而不仅仅是struct,比如int
float32等都可以有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type integer int

func (i integer) print() {
fmt.Println("这是:", i)
}

func (i *integer) change() {
*i = 100
}

func main() {
var i integer = 20
i.print()
i.change()
fmt.Println(i)
}

方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

type integer int

func (i integer) String() string {
return "she is beautiful!"
}

func main() {
var i integer = 20
fmt.Println(i)
}

6.30.5 方法与函数的区别

函数与方法区别:

绑定指定类型:

  • 方法:需要绑定到特定的数据类型。
  • 函数:不需要绑定到特定的数据类型。

调用方式:

  • 函数:使用函数名后跟参数列表进行调用。
      • 示例:函数名(参数列表)
  • 方法:使用对象变量后跟方法名和参数列表进行调用。
      • 示例:变量.方法名(参数列表)

参数类型要求:

  • 函数:参数必须与指定的类型匹配。
    • 示例:如果函数期望整数,则必须传递整数。
  • 方法:遵循方法期望的参数类型。

类型接受性:

  • 方法:
    • 如果接收者是值类型,可以传递指针类型。
    • 如果接收者是指针类型,可以传递值类型。
  • 函数:
    • 没有特定的类型绑定,因此值类型和指针类型之间没有区别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

type Student struct {
Name string
}

//定义方法:
func (s Student) test01() {
fmt.Println(s.Name)
}

//定义函数:
func method01(s Student) {
fmt.Println(s.Name)
}

func main() {
//调用函数:
var s Student = Student{"杰"}
method01(s)

//方法调用:
s.test01()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Student struct {
Name string
}

func method01(s Student) {
fmt.Println(s.Name)
}

func method02(s *Student) {
fmt.Println((*s).Name)
}

func main() {
var s Student = Student{"杰"}
method01(s)
method02(&s)
}

6.31 创建结构体方式

创建结构体方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type Student struct {
Name string
Age int
}

func main() {
// 方式1:按照属性顺序赋值
var s1 Student = Student{"小李", 19}
fmt.Println(s1)

// 方式2:按照指定类型
var s2 Student = Student{
Name: "丽丽",
Age: 20,
}
fmt.Println(s2)

// 方式3:返回结构体的指针类型
var s3 *Student = &Student{"明明", 26}
fmt.Println(*s3)
var s4 *Student = &Student{
Name: "娜娜",
Age: 29,
}
fmt.Println(*s4)
}

6.32 封装的实现

以下是对你提供的封装概念和Golang实现的整理和优化:

封装的定义:

封装(Encapsulation)是一种面向对象编程的概念,它将数据和对数据的操作封装在一起,以实现数据的保护和隐藏。在封装中,只有通过授权的操作方法才能访问和修改数据,从而隐藏了实现的细节,提高了程序的安全性和合理性。

封装的好处:

  1. 隐藏实现细节: 将数据和实现细节隐藏在内部,使外部模块只能通过指定的接口进行访问。
  2. 数据验证: 允许对数据进行验证,确保数据的安全和合理性。

Golang中的封装实现:

在Golang中,封装可以通过以下步骤实现:

  1. 结构体和字段的命名: 建议将结构体和字段的首字母小写,以限制其在其他包中的访问。
  2. 工厂函数: 提供一个首字母大写的工厂函数,用于创建结构体实例,类似于构造函数。
  3. Set方法: 提供一个首字母大写的Set方法,用于对属性进行赋值,并包含数据验证的业务逻辑。
  4. Get方法: 提供一个首字母大写的Get方法,用于获取属性的值。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package mypackage

// MyStruct 结构体定义
type MyStruct struct {
age int // 小写首字母,实现封装
}

// NewMyStruct 工厂函数,创建 MyStruct 的实例
func NewMyStruct() *MyStruct {
return &MyStruct{}
}

// SetAge 设置年龄的方法,包含数据验证的业务逻辑
func (s *MyStruct) SetAge(newAge int) {
// 添加数据验证逻辑,例如确保年龄在合理范围内
if newAge >= 0 && newAge <= 120 {
s.age = newAge
} else {
// 可以进行错误处理或记录日志
// 这里简单地打印一条消息
fmt.Println("Invalid age value")
}
}

// GetAge 获取年龄的方法
func (s *MyStruct) GetAge() int {
return s.age
}

在上面的示例中,结构体MyStruct的字段age被封装在内部,只能通过SetAgeGetAge方法进行访问。SetAge方法包含了数据验证的逻辑,确保年龄在合理范围内。通过这种方式,实现了对数据的封装和保护。

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
p := NewMyStruct()
p.SetAge(100)

fmt.Println(p.age)
}

6.33 继承

6.33.1 继承介绍

在Golang中,当多个结构体具有相同的属性(字段)和方法时,可以通过引入继承的概念来提高代码的复用性和扩展性。

通过将这些相同的属性和方法抽象到一个结构体中,其他结构体可以通过嵌套这个匿名结构体来实现继承。

即如果一个结构体嵌套了另一个匿名结构体,该结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

继承的优点:
继承的引入在Golang中带来以下优点:

  • 提高代码的复用性: 通过将公共属性和方法抽象到基础结构体中,避免了在每个结构体中重复定义相同的代码。
  • 提高代码的扩展性: 可以轻松地在基础结构体上构建新的结构体,添加特定的属性和方法,而无需修改基础结构体的代码。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type Animal struct {
Age int
Weight float32
}

func (an *Animal) Shout() {
fmt.Println("我可以大声叫")
}

func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%d, 动物的体重:%v \n", an.Age, an.Weight)
}

type Cat struct {
Animal
}

func (c *Cat) scratch() {
fmt.Println("我是小猫,我可以挠人")
}

func main() {
cat := &Cat{}
cat.Animal.Age = 3
cat.Animal.Weight = 10.6
cat.Animal.Shout()
cat.Animal.ShowInfo()
cat.scratch()
}

6.33.2 继承的注意事项

**嵌套匿名结构体的字段和方法访问
在Golang中,结构体可以通过嵌套匿名结构体来继承其所有字段和方法,无论字段和方法的首字母是大写还是小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type Animal struct {
age int
weight float32
}

func (an *Animal) shout() {
fmt.Println("我可以大声叫")
}

func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%d, 动物的体重:%v \n", an.age, an.weight)
}

type Cat struct {
Animal
}

func (c *Cat) scratch() {
fmt.Println("我是小猫,我可以挠人")
}

func main() {
cat := &Cat{}
cat.Animal.age = 3
cat.Animal.weight = 10.6
cat.Animal.shout()
cat.Animal.ShowInfo()
cat.scratch()
}

**简化匿名结构体字段访问
可以直接访问嵌套匿名结构体的字段,无需显式使用匿名结构体名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type Animal struct {
age int
weight float32
}

func (an *Animal) shout() {
fmt.Println("我可以大声叫")
}

func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%d, 动物的体重:%v \n", an.age, an.weight)
}

type Cat struct {
Animal
}

func (c *Cat) scratch() {
fmt.Println("我是小猫,我可以挠人")
}

func main() {
cat := &Cat{}
cat.age = 3
cat.weight = 10.6
cat.shout()
cat.ShowInfo()
cat.scratch()
}

就近访问原则:
当结构体和匿名结构体有相同的字段或方法时,编译器采用就近访问原则。可以通过匿名结构体名来区分访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

type Animal struct {
age int
weight float32
}

func (an *Animal) shout() {
fmt.Println("我可以大声叫")
}

func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%d, 动物的体重:%v \n", an.age, an.weight)
}

type Cat struct {
Animal
}

func (c *Cat) scratch() {
fmt.Println("我是小猫,我可以挠人")
}

func (an *Cat) ShowInfo() {
fmt.Printf("~~~~~动物的年龄:%d, 动物的体重:%v \n", an.age, an.weight)
}

func main() {
cat := &Cat{}
cat.age = 3
cat.weight = 10.6
cat.shout()
cat.ShowInfo() // 通过匿名结构体名来区分访问
cat.scratch()
}

Golang中支持多继承

如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
}

type C struct {
A
B
}

func main() {
c := C{A{10, "aaa"}, B{20, "ccc"}}
fmt.Println(c)
}

如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时需要通过匿名结构体类型名来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
a int
}

type C struct {
A
B
}

func main() {
c := C{A{10, "aaa"}, B{20, "ccc", 50}}
fmt.Println(c.b)
fmt.Println(c.d)
// fmt.Println(c.a) // 错误
fmt.Println(c.A.a)
}

结构体的匿名字段可以是基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
a int
}

type C struct {
A
B
int
}

func main() {
c := C{A{10, "aaa"}, B{20, "ccc", 50}, 888}
fmt.Println(c.b)
fmt.Println(c.d)
// fmt.Println(c.a) // 错误
fmt.Println(c.A.a)

fmt.Println(c.int)
}

嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
a int
}

type C struct {
A
B
int
}

func main() {
c := C{
A{
a: 10,
b: "aaa",
},
B{
c: 20,
d: "ccc",
a: 50,
},
888,
}
fmt.Println(c.b)
fmt.Println(c.d)
// fmt.Println(c.a) // 错误
fmt.Println(c.A.a)

fmt.Println(c.int)
}

嵌入匿名结构体的指针也是可以的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
a int
}

type C1 struct {
*A
*B
int
}

func main() {
c := C1{&A{10, "hello"}, &B{20, "world", 5}, 123}
fmt.Println(c)
fmt.Println((*(c.A)).a)
}

结构体的字段可以是结构体类型的。(组合模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type A struct {
a int
b string
}

type B struct {
c int
d string
a int
}

type C struct {
a int
b string
c B
}

func main() {
c := C{}
c.a = 100
c.b = "hello world"
c.c.c = 10
c.c.d = "qqq"
c.c.a = 30
fmt.Println(c)
}

6.34 接口

6.34.1 接口简介

  1. 接口定义与实现:接口可以定义一组方法,但这些方法不需要实现具体的方法体,并且接口中不能包含任何变量。自定义类型在实现接口时,根据具体情况具体实现接口中定义的方法。
  2. 接口实现的规则:实现接口时,必须实现接口中定义的所有方法,否则编译器会报错。
  3. Golang中的接口实现方式:Golang中不需要显式使用implement关键字来实现接口,而是基于方法的实现。这种方式让接口的实现更加灵活,没有强制的接口关键字。
  4. 多接口实现:一个结构体只需实现了多个接口中定义的全部方法,即可被认为实现了这些接口。实现接口的耦合性较低,这种灵活性比一对一的接口实现更为松散。
  5. 接口的目的:定义规范,为其他类型提供一组方法的契约。实现接口的具体类型可以根据需要来灵活地实现这些方法。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

type SayHello interface {
SayHello()
}

type Chinese struct {
}

func (person Chinese) SayHello() {
fmt.Println("你好")
}

type American struct {
}

func (person American) SayHello() {
fmt.Println("hi")
}

func greet(s SayHello) {
s.SayHello()
}

func main() {
a := American{}
c := Chinese{}

greet(a)
greet(c)
}

6.34.2 接口的注意事项

关于接口的注意事项:

接口实例化和指向:接口本身不能被实例化,但可以指向实现了该接口的自定义类型的变量。

自定义类型实现接口:任何自定义数据类型,而不仅仅是结构体,都可以实现接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type SayHello interface {
SayHello()
}

type integer int

func (i integer) SayHello() {
fmt.Println("hello", i)
}

func main() {
var i integer = 10
var s SayHello = i
s.SayHello()
}

多接口实现:

  • 一个自定义类型可以实现多个接口,从而提供不同的方法集。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type AInteraface interface {
a()
}

type BInterface interface {
b()
}

type Stu struct {
}

func (s Stu) a() {
fmt.Println("aaaaaa")
}

func (s Stu) b() {
fmt.Println("bbbbbb")
}

func main() {
var s Stu
var a AInteraface = s
var b BInterface = s

a.a()
b.b()
}

接口的继承:一个接口可以继承多个其他接口。如果要实现某个接口,必须实现其继承的所有接口的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

type Binterface interface {
b()
}

type Cinterface interface {
c()
}

type Ainterface interface {
Binterface
Cinterface
a()
}

type Stu struct {
}

func (s Stu) b() {
fmt.Println("bbbbbb")
}

func (s Stu) c() {
fmt.Println("ccccccc")
}

func (s Stu) a() {
fmt.Println("aaaaaa")
}

func main() {
s := Stu{}
var a Ainterface = s
a.a()
a.b()
a.c()
}

interface类型和指针interface 类型默认是一个指针(引用类型)。如果未对 interface 进行初始化就使用,将会输出 nil

空接口:空接口没有任何方法,因此可以理解为所有类型都实现了空接口。也可以将任何变量赋值给空接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type Binterface interface {
b()
}

type Cinterface interface {
c()
}

type Ainterface interface {
Binterface
Cinterface
a()
}

type Stu struct {
}

func (s Stu) b() {
fmt.Println("bbbbbb")
}

func (s Stu) c() {
fmt.Println("ccccccc")
}

func (s Stu) a() {
fmt.Println("aaaaaa")
}

func main() {
s := Stu{}
var a Ainterface = s
var d interface{} = a
fmt.Println(d)
}

6.35 多态

在Go语言中,虽然没有传统面向对象语言中的类和继承的概念,但它支持接口和类型组合,通过这些特性可以实现多态性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

// 定义一个接口
type Shape interface {
Area() float64
}

// 定义一个矩形类型
type Rectangle struct {
Width float64
Height float64
}

// 定义一个圆形类型
type Circle struct {
Radius float64
}

// 实现 Shape 接口的 Area 方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}

// 实现 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}

// 函数接受任何实现了 Shape 接口的类型
func PrintArea(shape Shape) {
fmt.Printf("Area: %0.2f\n", shape.Area())
}

func main() {
// 创建一个矩形实例
rectangle := Rectangle{Width: 4, Height: 5}

// 创建一个圆形实例
circle := Circle{Radius: 3}

// 通过接口实现多态
PrintArea(rectangle)
PrintArea(circle)
}

多态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import "fmt"

// 定义一个接口
type Shape interface {
Area() float64
}

// 定义一个矩形类型
type Rectangle struct {
Width float64
Height float64
}

// 定义一个圆形类型
type Circle struct {
Radius float64
}

// 实现 Shape 接口的 Area 方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}

// 实现 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}

func main() {
// 创建一个包含不同类型的 Shape 接口的切片
shapes := []Shape{
Rectangle{Width: 4, Height: 5},
Circle{Radius: 3},
}

// 遍历切片并调用 Area 方法
for _, shape := range shapes {
fmt.Printf("Area: %0.2f\n", shape.Area())
}
}

6.36 断言

在Go语言中,断言通常指的是类型断言(Type Assertion)。

类型断言用于在运行时判断一个接口值的实际类型,并将其转换为该类型。

这在使用接口时经常会遇到,因为接口可以包含任何类型的值。

类型断言的基本语法如下:

1
value, ok := someInterface.(SomeType)

其中,someInterface是一个接口类型的变量,SomeType是你期望的具体类型。

如果断言成功,value将包含被断言的值,而ok将为true

如果断言失败,value将为该类型的零值,而ok将为false

下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func printInt(value interface{}) {
// 尝试将接口值断言为整数类型
if intValue, ok := value.(int); ok {
fmt.Printf("The value is an integer: %d\n", intValue)
} else {
fmt.Println("The value is not an integer.")
}
}

func main() {
printInt(42) // The value is an integer: 42
printInt("hello") // The value is not an integer.
}

6.37 文件的操作

在Go语言中,File类型通常指的是os.File类型,它是os包中的一个结构体类型。os.File代表一个打开的文件,提供了对文件的读写操作。

6.37.1 Create函数

Create函数用于创建或截断指定文件。

如果文件已存在,将截断该文件

如果不存在,则以模式0666(在umask之前)创建

成功时,返回的File对象可用于文件I/O,关联的文件描述符模式为O_RDWR。

发生错误时,类型为*PathError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"os"
)

func main() {
file, err := os.Create("example.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()

_, err = file.WriteString("Hello, Golang!")
if err != nil {
fmt.Println("Error:", err)
return
}

fmt.Println("文件创建并且写入成功!")
}

6.37.2 CreateTemp函数

CreateTemp函数在目录dir中创建一个新的临时文件,以读写方式打开该文件,并返回生成的文件。

CreateTemp函数的第一个参数是目录,第二个参数是文件名。

TempDir函数返回的是系统的临时目录路径,通常是C:\Users\<username>\AppData\Local\Temp

调用者有责任在不再需要文件时删除它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"log"
"os"
)

func main() {
f, err := os.CreateTemp("", "example")
if err != nil {
log.Fatal(err)
}
defer os.Remove(f.Name()) // clean up

if _, err := f.Write([]byte("content")); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"log"
"os"
)

func main() {
f, err := os.CreateTemp("", "example.*.txt")
if err != nil {
log.Fatal(err)
}
defer os.Remove(f.Name()) // clean up

if _, err := f.Write([]byte("content")); err != nil {
f.Close()
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}

6.37.3 NewFile函数

NewFile 返回一个具有给定文件描述符和名称的新文件。

如果 fd 不是有效的文件描述符,则返回值将为 nil。

在 Unix 系统上,如果文件描述符处于非阻塞模式,NewFile 将尝试返回一个可轮询文件(SetDeadline 方法适用于该文件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func Pipe() (master *os.File, slave *os.File, err error) {
fd_master := C.int(-1)
fd_slave := C.int(-1)

C.create_pipe(&fd_master, &fd_slave)

if fd_master == -1 || fd_slave == -1 {
return nil, nil, errors.New("Failed to create a new pipe")
}

master = os.NewFile(uintptr(fd_master), "master")
slave = os.NewFile(uintptr(fd_slave), "slave")

return master, slave, nil
}

6.37.4 Open函数

打开打开指定的文件进行读取。

如果成功,则可以使用返回文件上的方法进行读取;

关联的文件描述符的模式为 O_RDONLY。

如果有错误,则其类型为 *PathError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"io"
"os"
)

func main() {
// 打开文件,返回 *os.File 对象和错误信息
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 延迟关闭文件,确保在函数返回前关闭

// 读取文件内容
content, err := io.ReadAll(file)
if err != nil {
fmt.Println("Error reading file:", err)
return
}

// 将文件内容打印到控制台
fmt.Println("File Content:", string(content))
}

6.37.5 OpenFile函数

OpenFile 是广义的 open 调用; 大多数用户会使用“打开”或“创建”。

它打开具有指定标志(O_RDONLY 等)的命名文件。

如果文件不存在,并且传递了 O_CREATE 标志,则使用 perm 模式(在 umask 之前)创建该文件。

如果成功,返回的 File 上的方法可用于 I/O。

如果有错误,则其类型为 *PathError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"log"
"os"
)

func main() {
f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}

追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"log"
"os"
)

func main() {
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
if _, err := f.Write([]byte("appended some data\n")); err != nil {
f.Close() // ignore error; Write error takes precedence
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}

6.37.6 Chdir函数

Chdir 函数用于改变当前的工作目录。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"os"
)

func main() {
file, _ := os.Open("./anotherdir")
file.Chdir() // 改变当前工作目录到文件目录 - /anotherdir
os.WriteFile("abc", []byte("tttttt"), 0755)
}

6.37.7 Chmod函数

chmod函数可修改文件的模式。

1
2
3
4
5
6
7
8
9
10
package main

import (
"os"
)

func main() {
file, _ := os.Open("./filename")
file.Chmod(0600)
}

6.37.8 Chown函数

chown函数可以修改文件所属的用户、所属组。传递的是用户ID和组ID,在Linux系统可以在/etc/passwd文件查看。

1
2
3
4
5
6
7
8
9
10
package main

import (
"os"
)

func main() {
file, _ := os.Open("./filename")
file.Chown(1000, 1000)
}

6.37.9 Close函数

Close函数用于关闭文件,使其无法用于 I/O。

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"os"
)

func main() {
file, _ := os.Open("./filename")
defer file.Close()
}

6.37.10 Fd函数

Fd 返回引用打开文件的整数 Unix 文件描述符。

如果 f 关闭,文件描述符将变得无效。

如果 f 被垃圾回收,终结器可能会关闭文件描述符,使其无效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"os"
)

func main() {
// 打开一个文件
file, err := os.Open("filename")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()

// 获取文件对象的底层文件描述符
fileDescriptor := file.Fd()

// 打印文件描述符
fmt.Println("File Descriptor:", fileDescriptor)
}

6.37.11 Name函数

Name函数会返回呈现给“打开”的文件的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"os"
)

func main() {
// 打开一个文件
file, err := os.Open("filename")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()

name := file.Name()
fmt.Println(name)
}

6.37.12 Read函数

Read函数从 File 中读取最多 len(b) 个字节并将它们存储在 b 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"os"
)

func main() {
// 打开一个文件
file, err := os.Open("filename")
if err != nil {
fmt.Println("出现错误,不能打开文件:", err)
return
}
defer file.Close()

var b []byte = make([]byte, 30)
n, err := file.Read(b)
if err != nil {
fmt.Println("读取出现问题")
}
fmt.Printf("读取长度:%d, 内容:%s\n", n, b)
}

6.37.13 ReadAt函数

ReadAt函数从文件中从字节偏移量 off 处开始读取 len(b) 个字节。

它返回读取的字节数和错误(如果有)。

当 n < len(b) 时,ReadAt 始终返回非零错误。

在文件末尾,该错误是 io.EOF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"os"
)

func main() {
// 打开一个文件
file, err := os.Open("filename")
if err != nil {
fmt.Println("出现错误,不能打开文件:", err)
return
}
defer file.Close()

var b []byte = make([]byte, 30)
n, err := file.ReadAt(b, 10)
if err != nil {
fmt.Println("读取出现问题")
}
fmt.Printf("读取长度:%d, 内容:%s\n", n, b)
}

6.37.14 ReadDir函数

ReadDir 函数读取与文件 f 关联的目录的内容,并按目录顺序返回 DirEntry 值的切片。

对同一文件的后续调用将在目录中产生稍后的 DirEntry 记录。

如果 n > 0,ReadDir 最多返回 n 个 DirEntry 记录。

如果 ReadDir 返回一个空切片,它将返回一个错误并解释原因。 在目录末尾,错误为 io.EOF。

如果 n <= 0,ReadDir 返回目录中剩余的所有 DirEntry 记录。 当成功时,它返回一个 nil 错误(不是 io.EOF)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"log"
"os"
)

func main() {
// 指定要读取的目录
dirPath := "./abc"

// 使用 ReadDir 读取目录信息
fileInfos, err := os.ReadDir(dirPath)
if err != nil {
log.Fatal(err)
}

// 遍历文件和子目录的信息列表
for _, fileInfo := range fileInfos {
fmt.Println(fileInfo.Name())
}
}

6.37.15 ReadFrom函数

ReadFrom 函数实现于 io.ReaderFrom。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"io"
"os"
"strings"
)

func main() {
// 创建一个字符串作为示例数据
data := "Hello, Golang!"

// 打开一个文件用于写入
file, err := os.Create("example.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()

// 创建一个包含示例数据的字符串读取器
reader := strings.NewReader(data)

// 使用 ReadFrom 方法从字符串读取器中读取数据并写入文件
bytesWritten, err := file.ReadFrom(reader)
if err != nil {
fmt.Println("Error writing to file:", err)
return
}

fmt.Printf("Bytes written to file: %d\n", bytesWritten)
}

6.37.16 Readdir函数

Readdir 函数读取与文件关联的目录的内容,并返回最多包含 n 个 FileInfo 值的片段,

大多数客户端可以通过更高效的 ReadDir 方法得到更好的服务。

Readdir 是之前版本的函数,而 ReadDir 是在 Go 1.16 版本引入的用于替代 Readdir 的方法。它们的功能基本相同,都是用于读取目录并返回文件信息的切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"log"
"os"
)

func main() {
dirPath := "/path/to/directory"

// 打开目录
dir, err := os.Open(dirPath)
if err != nil {
log.Fatal(err)
}
defer dir.Close()

// 读取目录
fileInfos, err := dir.Readdir(0)
if err != nil {
log.Fatal(err)
}

// 遍历文件信息
for _, fileInfo := range fileInfos {
fmt.Println(fileInfo.Name())
}
}

6.37.17 Readdirnames函数

Readdirnames 读取与 file 关联的目录的内容,并按目录顺序返回目录中最多 n 个文件名称的切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"log"
"os"
)

func main() {
dirPath := "./abc"

// 打开目录
dir, err := os.Open(dirPath)
if err != nil {
log.Fatal(err)
}
defer dir.Close()

// 读取目录
fileInfos, err := dir.Readdirnames(0)
if err != nil {
log.Fatal(err)
}

// 遍历文件信息
for _, fileInfo := range fileInfos {
fmt.Println(fileInfo)
}
}

6.37.18 Seek函数

Seek函数 设置文件上读写的偏移量(即光标指针),根据来源进行解释:

0 表示相对于文件的原点

1 表示相对于当前偏移量

2 表示相对于结尾

它返回新的偏移量和错误(如果有) 未指定使用 O_APPEND 打开的文件上的 Seek 行为。

example.txt的内容为:1111122abcdefghijkl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"fmt"
"log"
"os"
)

func main() {
// 打开一个文件用于读取
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

// 从文件开头偏移 5 个字节
offset, err := file.Seek(5, 0)
if err != nil {
log.Fatal(err)
}

fmt.Printf("第一次偏移: %d\n", offset)

// 读取从偏移量开始的内容
buffer := make([]byte, 5)
n, err := file.Read(buffer)
if err != nil {
log.Fatal(err)
}

fmt.Printf("读取 %d 字节: %s\n", n, buffer)

// 从当前偏移量再次偏移 2 个字节
offset, err = file.Seek(2, 1)
if err != nil {
log.Fatal(err)
}

fmt.Printf("从当前位置开始偏移,当前位置: %d\n", offset)

// 读取从当前偏移量开始的内容
n, err = file.Read(buffer)
if err != nil {
log.Fatal(err)
}

fmt.Printf("读取 %d 字节: %s\n", n, buffer)
}

6.37.19 SetDeadline函数

SetDeadline 函数设置文件的读写截止时间。 它相当于同时调用 SetReadDeadline 和 SetWriteDeadline。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"log"
"os"
"time"
)

func main() {
// 打开一个文件用于读取
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

t := time.Now()
e := file.SetDeadline(t)
if err != nil {
fmt.Println(e)
}
}

6.37.20 SetReadDeadline函数

SetReadDeadline 函数设置未来 Read 调用和任何当前阻止的 Read 调用的截止日期。

6.37.21 SetWriteDeadline函数

SetWriteDeadline 函数设置任何未来 Write 调用和任何当前阻止的 Write 调用的截止时间。

6.37.22 Stat函数

Stat 函数返回描述文件的 FileInfo 结构。 如果有错误,则其类型为 *PathError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"log"
"os"
)

func main() {
// 打开一个文件用于读取
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

fmt.Println(file.Stat())
}

6.37.23 Sync函数

同步将文件的当前内容提交到稳定存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"os"
)

func main() {
// 打开或创建一个文件用于写入
file, err := os.Create("example.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()

// 写入一些数据到文件
_, err = file.WriteString("Hello, Golang!")
if err != nil {
fmt.Println("Error writing to file:", err)
return
}

// 调用 Sync 方法将文件内容刷新到磁盘
err = file.Sync()
if err != nil {
fmt.Println("Error syncing file:", err)
return
}

fmt.Println("File content has been synced to disk.")
}

6.37.24 SyscallConn函数

SyscallConn函数返回原始文件。 这实现了 syscall.Conn 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"os"
)

func main() {
// 打开一个文件
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()

rc, e := file.SyscallConn()
if e != nil {
fmt.Println(e)
}

rc.Read(func(fd uintptr) (done bool) {
return false
})
}

6.37.25 Truncate函数

6.37.26 Write函数

6.37.27 WriteAt函数

6.37.28 WriteString函数

6.x 网络编程的引入

网络编程是指通过计算机网络实现程序之间的通信和数据交换的过程。在网络编程中,程序可以运行在不同的计算机上,并通过网络传输数据进行交互。网络编程通常涉及到两个主要组件:客户端和服务器。

在学习网络编程前,我们需要对通讯协议进行了解:

TCP/IP(Transmission Control Protocol/Internet Protocol)是一组通信协议,用于在网络中实现数据传输。它是互联网上使用最广泛的协议套件,负责将数据分割、传输和重新组装。

TCP/IP协议族包括多个层次,每个层次都负责不同的功能。与之相对的是OSI(Open Systems Interconnection)模型,也是一种网络协议的抽象参考模型。

TCP/IP model vs OSI model |

6.x.1 TCP编程-创建客户端

6.x.2 TCP编程-创建服务器端

6.x.3 TCP编程-连接测试

6.x.4 TCP编程-发送终端数据

常见错误汇总:

如何取消代理?

在 Windows 中:

1
2
bashCopy codeset HTTP_PROXY=
set HTTPS_PROXY=

在 macOS 和 Linux 中:

1
2
bashCopy codeexport HTTP_PROXY=
export HTTPS_PROXY=

修改env环境的GOPROXY代理:

1
go env -w GOPROXY=direct