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语言的开发受到了以下核心动机和优势的驱动:
应对不断提高的硬件性能:计算机硬件技术不断更新,性能迅猛提升。传统编程语言在充分利用多核和多CPU的硬件优势方面表现不佳。Go语言的目标是更好地利用现代硬件,提供更高的性能。
降低代码复杂性:现代软件系统变得越来越复杂,维护成本不断攀升。Go语言应运而生,通过提供简洁而高效的编程语言,应对这一挑战。
项目兼容性:许多企业继续维护着使用C/C++编写的项目。这些项目在性能方面表现出色,但编译速度较慢,同时存在内存泄漏等问题。Go语言为这些企业提供了一种现代化的解决方案,平衡了性能和开发效率。
02 环境搭建 2.1 Visual Studio 安装: Visual Studio是微软推出的编码工具。
安装vscode,下载地址:https://code.visualstudio.com/ 下载好安装包,一路点击下一步即可。
安装完vscode后,如图2所示,点击左侧的扩展工具按钮 ,搜索golang,安装一个名为Go的扩展使VS Code为Go编程语言提供了丰富的语言支持。
图2 Go扩展安装
2.2 golang SDK安装: SDK的全称(Software Development Kit 软件开发工具包)
下载 编译器下载地址:https://go.dev/dl/
如果你是系统是Windows系统,则点击下图红框处进行下载即可:
图3 Golang下载
安装 安装过程如图3所示,即一路下一步即可。
图4 Golang安装过程:从左到右
测试是否安装成功 按win+r,输入cmd
图5 呼出cmd命令提示符
在终端中输入:go version
,若出现对应的版本号即可可认为安装:成功 。
图6 golang版本查看
若上方没有出现golang的版本号,可以检测环境变量:
按win键输入”环境变量”
依次按照如下步骤,查看环境变量中的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 mainimport "fmt" func main () { fmt.Println("Hello, World!" ) }
如图7用鼠标右键点击main文件夹可以看到new file选项,创建一个test.go文件,然后在右侧输入上方代码。
图8 代码编写
3.2 运行代码
图9 打开集成终端
在终端环境下输入go run test.go
即可看到输出:Hello,world!
图10 运行go代码
这是一个非常简单的Go程序,但它演示了Go语言的基本结构和语法。
04 Go语言编译到运行的原理 4.1 Go语言编译原因 Go语言是需要编译才能运行的,主要原因包括以下几点:
静态类型语言:Go是一种静态类型语言,这意味着变量的类型在编译时就已经确定,而不是在运行时。这有助于提前检测类型错误和提高程序的性能。
编译优化:Go编译器可以执行各种编译优化,包括内联函数、消除未使用的变量和代码等,以提高程序的性能。这些优化通常是在编译时完成的,从而减少了运行时的性能开销。
生成本地机器码:Go编译器将Go源代码编译为本地机器码,这意味着生成的可执行文件可以直接在目标计算机上运行,而无需依赖额外的解释器或虚拟机。
依赖管理:Go编译器能够有效地处理程序的依赖关系,确保所需的库和包在编译时正确链接到程序中。这有助于简化依赖管理和程序分发的过程。
跨平台支持:Go编译器支持跨不同操作系统和架构的交叉编译。这意味着您可以在一台机器上编译Go程序,然后将生成的可执行文件移植到其他操作系统或架构上运行。
4.2 Go语言编译步骤 在Hello world例子中,我们使用了go run
命令可以直接运行Go源代码文件而无需显式编译。
这是因为Go编译器在执行go run
时会在后台自动进行必要的编译和链接操作 。
当运行go run test.go
时,Go编译器会自动执行以下步骤,如图:
编译:Go编译器将test.go
文件编译成中间代码(通常是一个临时的二进制文件),而不是生成最终的可执行文件。如果单独对文件进行编译则可以执行:
1 go build -o test.exe test.go
这段命令的意思是用test.go源码编译test.exe文件。
链接:编译完成后,Go编译器会自动链接所需的库和依赖项,以生成可执行文件。
运行:最后,编译器会运行生成的可执行文件。
这种方式允许您以一种非常便捷的方式运行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 示例 目录结构:
创建一个文件夹: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 }
返回上一层
创建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
运行
06 Go语言基本语法 6.1 注释 在Go中,注释是为了提供代码的解释、文档和可读性,编译器会忽略注释部分。通常,Go代码中的注释用于文档生成工具(例如,GoDoc)以及代码维护和协作。良好的注释可以帮助其他开发者理解您的代码并提供有用的信息。
Go语言支持两种主要类型的注释:单行注释和多行注释。
单行注释:单行注释以双斜杠 //
开头,可以用于注释单行代码或单行注释。单行注释后的文本将被编译器忽略,不会对程序产生任何影响。
1 2 fmt.Println("Hello, World!" )
多行注释:多行注释以斜杠加星号 /*
开头,以星号加斜杠 */
结尾,可以用于注释多行代码块或多行注释。多行注释可以跨越多行,并且可以包含多行文本。
6.2 代码风格 Effective Go代码风格链接: https://go.dev/doc/effective_go
Go语言有一套官方的代码风格指南,通常称为”Effective Go”,它目的是帮助Go开发者编写清晰、一致和易于阅读的代码。以下是一些关于Go代码风格的主要原则和建议:
使用短小的命名:Go鼓励使用简洁而具有描述性的变量和函数名称。通常,Go使用短小的单词或缩写,而不是过于冗长的名称。
使用驼峰命名法:Go代码通常使用驼峰命名法(camelCase)来命名变量、函数和方法。例如,myVariableName
或 myFunctionName
。
一行代码长度不要超过80个字符:虽然Go语言不强制限制行的长度,但建议在80个字符左右断行,以便代码在不同的编辑器和屏幕上更容易阅读。
注释:良好的注释对于代码的可读性非常重要。Go鼓励编写清晰、简洁的注释,用于解释代码的目的、行为和使用方式。
使用格式化工具:Go附带了一个格式化工具 gofmt
,它可以自动调整代码的格式以符合官方的代码风格指南。使用 gofmt
可以保持代码的一致性。
导入分组:Go的import
语句应该按照一定的分组方式组织,标准库导入应该在最上面,然后是第三方库,最后是项目自身的包。每个分组之间应该有空行。
错误处理:Go鼓励使用明确的错误处理机制,通常使用多值返回,例如,result, err := someFunction()
。错误应该被检查和处理,不要忽略错误。
使用函数字面量:Go支持函数字面量,这使得函数式编程在Go中更加方便。使用函数字面量来简化代码。
避免全局变量:尽量避免使用全局变量,而是使用函数参数和返回值来传递数据。全局变量应该被谨慎使用。
可读性优于智能性:Go鼓励代码的可读性优于智能性。编写易于理解和维护的代码是更重要的。
6.3 内置标准库 内置标准库文档链接:https://pkg.go.dev/std
在国内,有时候编程语言自带的函数也被称为API,这是错误的。 在编程中,API通常指的是用于与外部组件、服务或库进行交互的接口,而不是编程语言本身提供的内置函数。
尽管在某些语境下出现了这种用法,但在国际编程社区中,API的定义通常更为明确,指的是与外部组件进行交互的接口。编程语言自身的内置函数和功能通常不被称为API。不同地区和文化可能会有一些不同的编程术语使用方式,因此在具体的语境中,对术语的理解可能会有所不同。
6.4 变量 Go语言中的变量是用来存储数据的标识符。在Go中,变量的声明通常包括变量名、变量类型和可选的初始化值。
图12 内存与变量的关系
在Go(也称为Golang)编程语言中,标识符是用来命名变量、函数、类型和其他程序实体的名称 。
标识符必须遵循一些规则和约定,以便在代码中使用。以下是关于Go中标识符的一些规则:
标识符由字母、数字和下划线组成,但必须以字母或下划线开头。
标识符区分大小写,因此myVar
和myvar
被视为不同的标识符。
Go中有一些预定义的标识符,如if
、else
、for
、func
等,它们具有特殊含义,不能用作自定义标识符。
Go的关键字(reserved words)也不能用作标识符。
标识符应该具有描述性,以便提高代码的可读性。通常采用驼峰命名法,例如myVariableName
。
6.5 变量类型 Go的类型系统是强类型的,这意味着变量的类型必须在编译时明确定义,不能在运行时随意改变,以下是Go语言的基本数据类型、复合数据类型
6.5.1 基本数据类型
整数类型(int) :Go语言提供了不同大小的整数类型,如int
、int8
、int16
、int32
和int64
,分别表示不同位数的有符号整数。还有uint
、uint8
、uint16
、uint32
和uint64
表示无符号整数。例如:
1 2 var x int = 42 var y uint8 = 10
浮点数类型(float) :float32
和float64
,分别表示单精度和双精度浮点数。例如:
1 var pi float64 = 3.141592
复数类型(complex) :complex64
和complex128
,分别表示单精度和双精度复数。例如:
1 var z complex128 = 2 + 3i
字符串类型(string) :字符串是不可变的字节切片。例如:
1 var str string = "Hello, World!"
布尔类型(bool) :布尔类型只有两个值:true
和false
,用于表示逻辑真和逻辑假。例如:
字符类型(rune) :字符类型rune
用于表示Unicode字符,通常用单引号括起来。例如:
6.5.2 复合数据类型
数组类型(array) :数组是具有固定长度的同一类型元素的集合。例如:
切片类型(slice) :切片是对数组的一种引用,它允许动态增加或减少其长度。例如:
字典类型(map) :字典是键-值对的集合,用于实现哈希表。例如:
1 2 3 4 5 6 var myMap map [string ]int myMap["Alice" ] = 92 myMap["Bob" ] = 85 myMap["Charlie" ] = 78
结构体类型(struct) :结构体是一种自定义的复合类型,用于组织不同类型的数据。例如:
1 2 3 4 type Person struct { Name string Age int }
接口类型(interface) :接口是一种定义了一组方法签名的类型,用于实现多态性。例如:
1 2 3 type Shape interface { Area() float64 }
指针类型(Pointer Type) :指针类型表示指向某种数据类型的指针。指针变量包含了一个内存地址,该地址指向存储了特定数据类型的值的内存位置。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { var x int = 42 var y *int y = &x *y = 100 fmt.Println("x =" , x) }
管道类型(Channel) :它是一种用于在协程(goroutine)之间进行通信和同步的数据结构,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { ch := make (chan int ) go func () { ch <- 42 }() value := <-ch fmt.Println("接收到的值:" , value) }
函数类型(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 mainimport "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
包的一些主要功能和应用:
类型检查和类型查询 :你可以使用 reflect.TypeOf()
函数获取变量的类型信息,或者使用 reflect.ValueOf()
获取变量的值信息。这对于在运行时动态地检查变量的类型非常有用。
结构字段访问 :reflect
包允许你检查结构体类型的字段,并获取或设置字段的值。
方法调用 :你可以使用 reflect.Value
类型的 MethodByName
方法来调用结构体的方法。
创建和修改变量 :reflect
包提供了一些方法,允许你在运行时创建和修改变量的值。
枚举、遍历和操作集合 :你可以使用 reflect
包来检查和操作数组、切片、映射等集合类型的元素。
反射接口值 :reflect
包允许你将接口值转换为反射值,并进行类型断言和类型转换。
尽管reflect
包提供了很多强大的功能,但需要小心使用,因为它会增加代码的复杂性,并且可能导致性能开销。通常情况下,你应该尽量避免在普通应用程序中过度使用反射,只在需要动态处理类型的高级应用中使用它。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "reflect" ) func main () { num := 42 t := reflect.TypeOf(num) fmt.Println("变量的类型是:" , t) }
以下让我们针对这些类型进行详细讲解一下它们的使用方法。
6.6 整数类型 如变量类型中介绍,Go语言提供了不同大小的整数类型,如int
、int8
、int16
、int32
和int64
,分别表示不同位数的有符号整数。还有uint
、uint8
、uint16
、uint32
和uint64
表示无符号整数。
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
int
和 uint
的大小取决于平台,通常为32位或64位。这些范围反映了整数类型可以表示的最小和最大值。当选择整数类型时,需要根据应用程序的需求和所需的数据范围来选择适当的整数类型。
6.6.2 整数最大最小值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "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) fmt.Printf("uint16 -> 最小值:0,最大值:%d\n" , math.MaxUint16) fmt.Printf("uint32 -> 最小值:0,最大值:%d\n" , uint32 (math.MaxUint32)) fmt.Printf("uint64 -> 最小值:0,最大值:%d\n" , uint64 (math.MaxUint64)) }
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 mainimport "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
),以下表格显示了float32
和float64
浮点数类型的类型、大小、范围。有助于你了解浮点数类型的范围和精度,以便在编写数值计算代码时选择合适的类型。但注意的是浮点数在进行精确计算时可能会引入舍入误差,这需要在设计算法时考虑。
类型
大小
范围
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具有三个基本组成部分:
符号位 :0表示正数,而1表示负数。
指数位 :指数字段需要表示正指数和负指数。为了获得存储的指数,需要添加一个偏置值。
尾数位 :尾数是科学记数法或浮点数的一部分,由其有效数字组成。在这里,我们只有2个数字,即0和1。因此,规范化的尾数是指在小数点左侧只有一个1的尾数。
这个标准使浮点数计算在计算机中更一致,同时允许在不同计算机平台之间进行更可靠的数值计算。需要注意的是,浮点数在处理某些数值时可能引入舍入误差,因此在关键应用中需要谨慎处理这些问题。
图13 IEEE 754标准的32位浮点型内存分配
图14 IEEE 754标准的64位浮点型内存分配
6.7.3 IEEE 754示例: 当将十进制的 浮点数18.75 存储为IEEE 754单精度浮点数时,需要将它转换为二进制表示,并使用规定的格式存储,它包括三个主要部分:符号位、指数位和尾数位。。
转换为二进制 :
将它们合并在一起,得到二进制表示:10010.11
规范化 :规范化是将二进制中的小数点移动到左边只剩一个1 的形式。在这种情况下,我们可以将小数点向左移动,直到只有一个非零数字 1
位于小数点左侧。所以,将小数点移到 1
的右边,得到规范化的形式:1.001011
。
确定符号位 :18.75 是正数,所以符号位为 0。
确定指数 :
规范化后,小数点移到了 1
的右边,所以我们需要记录移动的位数。在这种情况下,小数点右移了 4 位。
IEEE 754使用偏置指数来表示指数部分。单精度浮点数的偏置值通常是 127。所以,实际指数为 4,加上偏置值 127,得到存储的指数值为 131(以二进制表示为 10000011
)。
存储尾数、指数和符号位 :
符号位:0(表示正数)
指数位:131(以二进制表示为 10000011
)
尾数位:001011(可以对照规范化的结果类进行查看)
综合起来,18.75 的单精度IEEE 754浮点数表示如下:
符号位:0(正数)
指数位:131(以二进制表示为 10000011
)
尾数位:001011(可以对照规范化的结果类进行查看)
这个表示将用于在计算机中存储和运算浮点数的值。请注意,具体的二进制表示可能会因计算机体系结构而异,但IEEE 754规定了这些表示的标准化方式。这个表示形式允许有效地表示广泛的实数范围。
图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 mainimport ( "fmt" "reflect" ) func main () { var pi1 float32 = 3.14 fmt.Println("单精度浮点数:" , pi1) var pi2 float64 = 3.14 fmt.Println("双精度浮点数:" , pi2) 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语言提供了内置的复数类型 complex64
和 complex128
。下面是一个使用复数类型的示例:
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 mainimport "fmt" func main () { z := complex (3 , 4 ) fmt.Println("复数 z =" , z) realPart := real (z) imagPart := imag (z) fmt.Println("实部:" , realPart) fmt.Println("虚部:" , imagPart) a := complex (1 , 2 ) b := complex (3 , 5 ) sum := a + b product := a * b fmt.Println("a + b =" , sum) fmt.Println("a * b =" , product) }
理解复数乘法:
下面我将简要解释实部的乘积是如何推导出来的:
假设有两个复数 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 码的一些重要特点:
字符表示 :ASCII 码为每个字符分配一个唯一的数字值,范围从 0 到 127。这些字符包括大写字母、小写字母、数字、标点符号、控制字符和一些特殊字符。
7位编码 :最初的 ASCII 标准使用7位编码,从 0 到 127,用于表示128个不同的字符。这意味着一个 ASCII 字符可以用7位二进制数表示。
扩展的 ASCII :随着计算机和国际化的发展,扩展的 ASCII 标准(如 ISO-8859)使用8位编码,以支持更多字符集,包括不同语言中的特殊字符。
Unicode :尽管 ASCII 是一种重要的字符编码标准,但它主要用于英语文本。但在现实生活中,对于不同的语言文化来说,需要的编码来一一对应。因此,通常使用 Unicode 编码标准,它支持全球范围内的字符。
字符映射 :ASCII 将每个字符映射到一个整数值,例如,字母 “A” 的 ASCII 值是 65,而数字 “0” 的 ASCII 值是 48。
ASCII 码是计算机和通信系统中的基本字符编码,使得计算机能够存储、传输和显示文本信息。虽然它最初用于英语文本,但在许多应用中仍然广泛使用。Unicode 包括了ASCII 码中的字符,并且扩展了字符集,以满足不同语言和文化的需求。
图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 mainimport "fmt" func main () { var a byte = 'A' fmt.Println(a) fmt.Printf("具体字符:%c" , a) fmt.Print("\n你好:\n今天是星期天!\n" ) fmt.Println("i 💖 u\b 小明" ) fmt.Println("abc\r123456789" ) 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 mainimport "fmt" func main () { var b bool = 3 > 4 fmt.Println("3 大于 4 吗?" , b) }
6.11 字符串类型 在Go语言中,字符串类型被表示为双引号 "
或反引号 ``` 中的文本。
以下是一些关于Go语言中字符串类型的特点和操作:
不可变性 :在Go语言中,字符串是不可变的,意味着一旦创建,它们的内容不能被更改。如果需要修改字符串,通常需要创建一个新的字符串。
字符串长度 :可以使用内置的 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 mainimport ( "fmt" ) func main () { var hello string = "world" fmt.Println(hello) }
输出结果:
6.11.3 字符串加法拼接 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { var s1 string = "你好吗?" var s2 string = "小蓝" fmt.Println(s1 + s2) }
输出结果:
6.11.4 通过下标进行获取字符 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { var s3 string = "abcdefg" fmt.Printf("获取的字符是:%c\n" , s3[2 ]) }
输出结果:
6.11.5 多行字符串输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "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 mainimport ( "fmt" ) func main () { s6 := "Hello, World!" s7 := s6[7 :12 ] fmt.Println("切片结果:" , s7) }
输出结果:
6.11.7 Clone字符串克隆函数 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strings" ) func main () { var s8 string = "abc" copys8 := strings.Clone(s8) fmt.Println("克隆后的结果:" , copys8) }
输出结果:
6.11.8 Compar字符串对比函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("a > b吗?" , strings.Compare("a" , "b" )) fmt.Println("a = a吗?" , strings.Compare("a" , "a" )) 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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("seafood是否包含foo:" , strings.Contains("seafood" , "foo" )) }
输出结果:
6.11.10 ContainsAny函数 字符串是否包含任一这些子字符串
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("字符串是否包含任一这些子字符串:" , strings.ContainsAny("ure" , "ui" )) }
输出结果:
6.11.11 ContainsFunc自定义检查包含函数 根据回调函数返回值来检查字符是否是大写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "strings" ) func main () { isUpperCase := func (r rune ) bool { return 'A' <= r && r <= 'Z' } result := strings.ContainsFunc("123A456" , isUpperCase) fmt.Println("是否包含大写字母:" , result) }
输出结果:
6.11.12 ContainsRune函数 使用 ContainsRune 函数检查字符串是否包含指定的 Unicode 字符
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strings" ) func main () { s9 := "Hello, 世界" contains := strings.ContainsRune(s9, '界' ) fmt.Println("是否包含 '界':" , contains) }
输出结果:
6.11.13 Count字符统计函数 统计字符出现次数
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("e出现次数:" , strings.Count("cheese" , "e" )) }
输出结果:
6.11.14 Cut函数 切割,并返回切断后的 前后部分 和 是否能切割标识
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { equal := strings.EqualFold("Hello, World!" , "hello, world!" ) fmt.Println("是否相等:" , equal) }
输出结果:
6.11.18 Fields以空格分割 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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 mainimport ( "fmt" "strings" "unicode" ) func main () { 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 mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("是否包含后缀: " , strings.HasSuffix("Amigo" , "go" )) }
输出结果:
6.11.22 Index寻找字符串并返回下标 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("ken的下标位置:" , strings.Index("chicken" , "ken" )) }
输出结果:
6.11.23 IndexAny寻找任一子字符串并返回下标 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("包含aeiouy任一字母,且下标位置为:" , strings.IndexAny("chicken" , "aeiouy" )) }
输出结果:
6.11.24 IndexByte寻找某个字节并返回下标 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("查找单个字符g, 下标为:" , strings.IndexByte("golang" , 'a' )) }
输出结果:
6.11.25 IndexFunc根据自定义函数寻找目标并返回下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "strings" "unicode" ) func main () { f := func (c rune ) bool { return unicode.Is(unicode.Han, c) } fmt.Println("\"Hello, 世界\"字符串是否包含汉字?" , strings.IndexFunc("Hello, 世界" , f)) }
输出结果:
6.11.26 IndexRune寻找单个unicode字符并返回下标 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("✨的位置在:" , strings.IndexRune("我爱你,小✨✨" , '✨' )) }
输出结果:
6.11.27 Join字符串拼接 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strings" ) func main () { s := []string {"foo" , "bar" , "baz" } fmt.Println("字符串拼接结果:" , strings.Join(s, ", " )) }
输出结果:
6.11.28 LastIndex寻找最后子字符并返回下标 查找子字符串最后出现的位置,并返回其下标
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("go最后出现在:" , strings.LastIndex("go gopher" , "go" )) }
输出结果:
6.11.29 LastIndexAny寻找任一最后子字符并返回下标 查找go任一字符最后出现的位置,并返回其下标
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("g或o最后出现在:" , strings.LastIndexAny("go gopher" , "go" )) }
输出结果:
6.11.30 LastIndexByte寻找最后出现的字节并返回下标 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("最后出现的字节r:" , strings.LastIndexByte("Hello, world" , 'r' )) }
输出结果:
6.11.31 LastIndexFunc根据自定义函数寻找并返回下标 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strings" "unicode" ) func main () { fmt.Println("数字的位置为:" , strings.LastIndexFunc("go 123" , unicode.IsNumber)) }
输出结果:
6.11.32 Map将某个字符映射成另一字符函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "strings" ) func main () { mapFunc := func (r rune ) rune { return r + 1 } newStr := strings.Map(mapFunc, "abc" ) fmt.Println("映射后的结果:" , newStr) }
输出结果:
6.11.33 Repeat重复字符串函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("我恨你 替换成:" , strings.Replace("我恨你" , "恨" , "爱" , 1 )) }
输出结果:
6.11.35 ReplaceAll替换全部字符串 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("我很 坏坏坏 替换成:" , strings.ReplaceAll("我很 坏坏坏" , "坏" , "好" )) }
输出结果:
6.11.36 Split字符串分割函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Printf("字符串分割结果:%q\n" , strings.Split("a,b,c" , "," )) }
输出结果:
6.11.37 SplitAfter分割字符串保留逗号作为结尾 1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("字符串转小写:" , strings.ToLower("Gopher" )) }
输出结果:
6.11.41 ToLowerSpecial特殊字符串转小写函数 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strings" "unicode" ) func main () { fmt.Println("土耳其语转小写:" , strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş" )) }
输出结果:
6.11.42 ToTitle首字母大写函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strings" ) func main () { fmt.Println("首字母大写:" , strings.ToTitle("i love you" )) }
输出结果:
6.11.43 ToTitleSpecial特殊字符串首字母大写函数 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("字符串转大写:" , strings.ToUpper("Gopher" )) }
输出结果:
6.11.45 ToUpperSpecial特殊字符串转大写函数 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strings" "unicode" ) func main () { fmt.Println("土耳其语转大写:" , strings.ToUpperSpecial(unicode.TurkishCase, "Önnek İş" )) }
输出结果:
6.11.46 ToValidUTF8修复无效UTF-8序列 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strings" ) func main () { str := "Hello, \xed\xa0\x80 World!" 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 mainimport ( "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 mainimport ( "fmt" "strings" "unicode" ) func main () { trimFunc := func (r rune ) bool { return unicode.IsSpace(r) } fmt.Println("去除两边的空格: " , strings.TrimFunc(" Hello, World! " , trimFunc)) }
输出结果:
6.11.49 TrimLeft去除左边的指定字符函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "strings" ) func main () { fmt.Println("去除字符串前缀:" , strings.TrimPrefix("https://google.com" , "https://" )) }
输出结果:
6.11.52 TrimRight去除右边的指定字符数据函数 1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "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 mainimport ( "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 Builder
、type Reader
和 type Replacer
是 strings
包中的类型,用于不同的字符串处理任务。
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
。
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
接口的目标中。
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 mainimport ( "fmt" "strings" ) func main () { var builder strings.Builder fmt.Println("before容量:" , builder.Cap()) builder.WriteString("Hello, " ) builder.WriteString("World!" ) builder.WriteByte('-' ) builder.WriteRune('💖' ) fmt.Println("after容量:" , builder.Cap()) builder.Grow(8 ) fmt.Println("扩大容量后:" , builder.Cap()) fmt.Println("已写入的字符串长度:" , builder.Len()) result := builder.String() builder.Reset() fmt.Println("重置后容量:" , builder.Cap()) fmt.Println("重置后字符串长度:" , builder.Len()) fmt.Println(result) }
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 mainimport ( "fmt" "io" "strings" ) func main () { text := "111112222233333" 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])) } }
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 mainimport ( "fmt" "io" "strings" ) func main () { text := "111112222233333" reader := strings.NewReader(text) buffer := make ([]byte , 3 ) 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 mainimport ( "fmt" "strings" ) func main () { text := "Hello, World!" reader := strings.NewReader(text) for { char, err := reader.ReadByte() if err != nil { 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 mainimport ( "fmt" "strings" ) func main () { text := "Hello, 你好💖🤩👉👍🫖😍!" reader := strings.NewReader(text) for { char, size, err := reader.ReadRune() if err != nil { 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 mainimport ( "fmt" "io" "strings" ) func main () { text := "Hello, World!" reader := strings.NewReader(text) 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 mainimport ( "fmt" "os" "strings" ) func main () { text := "Hello, World!" 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 mainimport ( "fmt" "io" "strings" ) func main () { text := "Hello, 🫖World!" reader := strings.NewReader(text) char, err := reader.ReadByte() if err != nil { fmt.Printf("发生错误:%v\n" , err) return } fmt.Printf("读取字节: %c\n" , char) 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) char2, size2, err2 := reader.ReadRune() if err != nil { fmt.Printf("发生错误:%v\n" , err2) return } fmt.Printf("读取unicode字符: %c (字节大小:%d)\n" , char2, size2) err2 = reader.UnreadRune() if err2 != nil { fmt.Printf("回退unicode字符时发生错误:%v\n" , err2) return } 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 mainimport ( "fmt" "strings" ) func main () { r := strings.NewReplacer("apple" , "orange" , "banana" , "grape" ) text := "I have an apple and a banana. An apple a day keeps the doctor away." result := r.Replace(text) fmt.Println(result) }
6.12 基本类型默认值 在 Go 语言中,基础类型(如整数、浮点数、布尔等)的默认值通常为零值。以下是一些常见基础类型的默认零值:
整数类型(int、int8、int16、int32、int64):默认值为 0
。
无符号整数类型(uint、uint8、uint16、uint32、uint64):默认值为 0
。
浮点数类型(float32、float64):默认值为 0.0
。
布尔类型(bool):默认值为 false
。
字符串类型(string):默认值为空字符串 ""
。
字符类型(rune):默认值为 0
(表示空字符,对应 Unicode 中的 NUL 字符)。
指针类型(如 *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 mainimport "fmt" func main () { var i int fmt.Println("整数默认值:" , i) var f float64 fmt.Println("浮点数默认值:" , f) var b bool fmt.Println("布尔默认值:" , b) var s string fmt.Println("字符串默认值:" , s) var r rune fmt.Println("字符 (rune) 默认值:" , r) 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 mainimport ( "fmt" ) func main () { var n1 int = 100 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) 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函数。以下是一些常见的数字占位符:
%d
: 用于整数,例如,fmt.Printf("%d", 42)
将输出整数42。
%f
: 用于浮点数,例如,fmt.Printf("%f", 3.14159)
将输出浮点数3.141590。
%s
: 用于字符串,例如,fmt.Printf("%s", "Hello, World")
将输出字符串”Hello, World”。
%x
: 用于格式化为十六进制,例如,fmt.Printf("%x", 255)
将输出”ff”。
%o
: 用于格式化为八进制,例如,fmt.Printf("%o", 64)
将输出”100”。
%b
: 用于格式化为二进制,例如,fmt.Printf("%b", 8)
将输出”1000”。
这些是一些常见的占位符,还有其他占位符可用于不同的数据类型和格式化选项。您可以将这些占位符与其他格式化标志一起使用,以实现更复杂的输出格式。例如,您可以使用 %04d
来输出一个整数,并确保它占据至少4个字符的宽度,不足的部分用零填充。
6.14.1 Append追加 1 2 3 4 5 6 7 8 9 package mainimport "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 mainimport "fmt" func main () { b1 := []byte ("你好," ) b2 := fmt.Appendf(b1, "%s %d %x %b" , "def" , 15 , 15 , 15 ) fmt.Println(string (b2)) }
输出结果:
6.14.3 Appendln换行追加 1 2 3 4 5 6 7 8 9 package mainimport "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 mainimport ( "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 mainimport ( "fmt" "os" ) func main () { const name, age = "Kim" , 22 n, err := fmt.Fprint(os.Stdout, name, " is " , age, " years old.\n" ) 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文件
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 mainimport ( "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) }
输出结果:
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 mainimport ( "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 mainimport ( "fmt" ) func main () { const name, age = "Kim" , 22 fmt.Print(name, " is " , age, " years old.\n" ) }
输出结果:
6.14.9 Printf函数 格式化输出
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { const name, age = "Kim" , 22 fmt.Printf("%s is %d years old.\n" , name, age) }
输出结果:
6.14.10 Println函数 输出文本后面加换行
1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" ) func main () { name := "Alice" age := 30 result := fmt.Sprint("Name: " , name, ", Age: " , age) fmt.Println(result) }
输出结果:
6.14.15 Sprintf函数 将格式化输出文本保存到一个字符串中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" ) func main () { name := "Alice" age := 30 result := fmt.Sprintf("Name: %s, Age: %d" , name, age) fmt.Println(result) }
输出结果:
6.14.16 Sprintln函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" ) func main () { name := "Alice" age := 30 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 mainimport ( "fmt" ) func main () { inputString := "Alice 30" var name string var age int 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 mainimport ( "fmt" ) func main () { inputString := "Alice 30" var name string var age int 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 mainimport ( "fmt" ) func main () { inputString := "Alice 30\nxiaohong 13" var name string var age int 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 mainimport ( "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)) }
输出结果:
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 mainimport ( "fmt" "strconv" ) func main () { b32 := []byte ("float32:" ) b32 = strconv.AppendFloat(b32, 3.1415926535 , 'E' , -1 , 32 ) fmt.Println(string (b32)) }
输出结果:
6.15.3 AppendInt函数 将整数拼接到byte中去,具体参数解释如下: b10:要附加到的目标字节数组。 -42:要转换的整数。 10:表示要使用十进制表示。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { b10 := []byte ("int (base 10):" ) b10 = strconv.AppendInt(b10, -42 , 10 ) fmt.Println(string (b10)) }
输出结果:
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)) }
输出结果:
6.15.5 AppendQuoteRune函数 将一个字符串转换为unicode的字符串,并且附加到b2缓冲中
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { b2 := []byte ("rune:" ) b2 = strconv.AppendQuoteRune(b2, '🥰' ) fmt.Println(string (b2)) }
输出结果:
6.15.6 AppendQuoteRuneToASCII函数 将一个字符串转换为unicode的字符串,并且以Ascii字符串附加到b3缓冲中
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "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 mainimport ( "fmt" "strconv" ) func main () { outputBytes := []byte ("Hello" ) inputString := "世界!" outputBytes = strconv.AppendQuoteToGraphic(outputBytes, inputString) outputString := string (outputBytes) fmt.Println(outputString) }
输出结果:
6.15.8 AppendUint函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "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 mainimport ( "fmt" "strconv" ) func main () { v := "10" if s, err := strconv.Atoi(v); err == nil { fmt.Printf("%T, %v\n" , s, s) } }
输出结果:
6.15.10 CanBackquote函数 CanBackquote
是 Go 语言中的一个内置常量,它用于标识字符串字面值是否支持反引号(`)包围的原始字符串字面值。
反引号包围的字符串字面值允许包含换行符和特殊字符,不进行转义。
在 Go 中,如果一个字符串字面值包含了反引号,则它被认为是一个原始字符串字面值,这时 CanBackquote
常量的值为 true
。
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "strconv" ) func main () { fmt.Println(strconv.CanBackquote("`can't backquote this`" )) }
输出结果:
格式化布尔值为字符串
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { vb1 := true s := strconv.FormatBool(vb1) fmt.Printf("%T, %v\n" , s, s) }
输出结果:
格式化复数
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { complexNumber := complex (3.0 , 4.0 ) s2 := strconv.FormatComplex(complexNumber, 'f' , -1 , 64 ) fmt.Println(s2) }
输出结果:
FormatFloat 格式化输出浮点数
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { vb3 := 3.1415926535 s32 := strconv.FormatFloat(vb3, 'e' , -1 , 32 ) fmt.Printf("%T, %v\n" , s32, s32) }
输出结果:
FormatInt 格式化输出整数
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { vb4 := int64 (-42 ) s10 := strconv.FormatInt(vb4, 10 ) fmt.Printf("%T, %v\n" , s10, s10) }
输出结果:
FormatUint 格式化输出无符号整数
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { vb5 := uint64 (42 ) s11 := strconv.FormatUint(vb5, 10 ) fmt.Printf("%T, %v\n" , s11, s11) }
输出结果:
6.15.16 IsGraphic函数 是否是图案
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strconv" ) func main () { shamrock := strconv.IsGraphic('☘' ) fmt.Println(shamrock) }
输出结果:
6.15.17 IsPrint函数 IsPrint 报告该 rune 是否被 Go 定义为可打印
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strconv" ) func main () { c := strconv.IsPrint('\007' ) fmt.Println(c) }
输出结果:
5.15.18 Itoa 函数
格式化输出 int
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { i1 := 10 s12 := strconv.Itoa(i1) fmt.Printf("%T, %v\n" , s12, s12) }
输出结果:
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) } }
输出结果:
使用 FormatComplex 将复数格式化为字符串(十进制格式)
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "strconv" ) func main () { complexNumber1 := complex (3.0 , 4.0 ) decimalString := strconv.FormatComplex(complexNumber1, 'f' , -1 , 64 ) fmt.Println("Decimal format:" , decimalString) }
输出结果:
6.15.20 ParseFloat函数 转换成浮点数
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "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 mainimport ( "fmt" "strconv" ) func main () { v32 := "-354634382" if s, err := strconv.ParseInt(v32, 10 , 32 ); err == nil { fmt.Printf("%T, %v\n" , s, s) } }
输出结果:
6.15.22 ParseUint函数 转换成无符号整数
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "strconv" ) func main () { v33 := "42" if s, err := strconv.ParseUint(v33, 10 , 32 ); err == nil { fmt.Printf("%T, %v\n" , s, s) } }
输出结果:
6.15.23 Quote函数 双引号括起来
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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 mainimport ( "fmt" "strconv" ) func main () { s14 := strconv.QuoteRune('☺' ) fmt.Println(s14) }
输出结果:
6.15.25 QuoteRuneToASCII函数 Rune类型都转换成Ascii码(双引号括起来)
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strconv" ) func main () { s15 := strconv.QuoteRuneToASCII('☺' ) fmt.Println(s15) }
输出结果:
6.15.26 QuoteRuneToGraphic函数 Rune类型都转换成可打印字符(双引号括起来)
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "strconv" ) func main () { s16 := strconv.QuoteRuneToGraphic(' ' ) fmt.Println(s16) }
输出结果:
6.15.27 QuoteToASCII函数 将引号转义成ASCII
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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 mainimport ( "fmt" "strconv" ) func main () { s18 := strconv.QuoteToGraphic(" " ) fmt.Println(s18) }
输出结果:
6.15.29 QuotedPrefix函数 包含双引号开头的才符合条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "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) } }
输出结果:
6.15.30 Unquote函数 引号反转义
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "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变量为指针变量。就像日常生活中的卡片是一样的,里面写着一个真实的地址。
图18 变量、内存地址、值的关系
6.16.1 指针声明 声明一个指针,可以使用*
符号加类型。
为什么要加上类型?因为指针本身存储的仅仅是一个地址,如果不加类型的话,是不能的值指针所指的区域长度。
如下代码所示:
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { var p *int fmt.Printf("p 的地址是:%p\n" , p) }
输出结果:
默认情况下,未初始化的指针变量会被设置为nil
,即空指针,其内存地址为0。
6.16.2 获取变量地址 我们可以通过&
号获取变量的地址,并可以将该变量地址复制给指针变量。
当然指针本身也是有地址的,亦可以用&号进行获取指针本身的地址。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" ) func main () { x := 42 var p *int p = &x 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 mainimport ( "fmt" ) func main () { x := 42 var p *int p = &x fmt.Printf("p所指向的值:%v" , *p) }
输出结果:
6.16.4 修改指针所指向的值 我们可以复制给*指针变量(星号拼接指针变量在左边)来修改指针所指向的值。
注意:修改指针所指向的值会造成一个现象就是一改多改现象。
而且这种方式与声明指针变量十分容易混淆,切记要分清楚。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" ) func main () { x := 42 var p *int p = &x *p = 55 fmt.Printf("p所指向的值:%v, x的值:%v" , *p, x) }
输出结果:
6.17 模块与包 Go语言的命名和导入规则如下:
包名命名规则:
包名应尽量与包所在目录一致,保持一致性。
包名应具有有意义的描述性,简短而有意义。
避免与标准库包名冲突,选择独特的包名。
变量、函数和常量命名规则:
采用驼峰命名法,即首字母小写,后续单词首字母大写。
如果变量、函数、常量名的首字母大写,它们可以被其他包访问;如果首字母小写,它们只能在本包中使用。
示例项目:
假如有如下目录结构:
1 2 3 4 5 6 `-- src `-- prj |-- main.go `-- other `-- pkg1 `-- pkg1.go
若main.go想引用pkg1.go文件,需要注意几点:
注意事项:
import导入语句通常放在文件开头,位于包声明语句下面。
导入包名需要使用双引号包裹起来。
包名的路径是从$GOPATH/src/后开始计算,路径分隔符使用斜杠(“/“)。
package的包名,最好与文件夹名一致。例如包名为:pkg1,那么文件夹的名字就为pkg1
若没有go.mod文件的时候,go会通过环境变量:GOPATH去进行查找,因此需要设置环境变量GOPATH如下图所示。
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 将被启用;否则,它将被禁用。
需要将导出的函数、变量等设置为手写字母大写。
d:\\goproject\src\prj\main.go
文件
1 2 3 4 5 6 7 package mainimport "prj/other/pkg1" func main () { pkg1.Func() }
d:\\goproject\src\prj\other\pkg1\pkg1.go
文件
1 2 3 4 5 6 7 package pkg1import "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) }
输出结果:
6.19.3 除号运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { fmt.Println(10 / 0 ) fmt.Println(10.0 / 3 ) }
输出结果:
6.19.4 取模运算 取模运算是一种数学运算,通常用百分号符号(%)表示。它用于计算一个数除以另一个数后的余数。
1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { fmt.Println(10 % 3 ) fmt.Println(11 % 3 ) fmt.Println(11 % 4 ) }
输出结果:
6.19.5 自增或者自减 在Go语言中,自增和自减运算符不能用在表达式中,且++和–不能写在变量的前面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { var a int = 10 a++ fmt.Println(a) var b int = 5 b-- fmt.Println(b) }
输出结果:
❌不能将++或–放到运算中
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 语言支持多种不同的赋值运算符,以下是一些常见的赋值运算符以及它们的示例:
=
(等号):将右侧的值赋给左侧的变量。
+=
(加等于):将右侧的值加到左侧的变量上,并将结果赋给左侧的变量。
-=
(减等于):将右侧的值从左侧的变量中减去,并将结果赋给左侧的变量。
*=
(乘等于):将右侧的值与左侧的变量相乘,并将结果赋给左侧的变量。
/=
(除等于):将左侧的变量除以右侧的值,并将结果赋给左侧的变量。
%=
(取模等于):将左侧的变量除以右侧的值后的余数赋给左侧的变量。
这些赋值运算符用于方便地对变量进行操作,并将结果重新赋给同一个变量。它们可以用于各种不同的数据类型,包括整数、浮点数、字符串等。
从命令行提示符中获取值,并且赋值给a、b变量,并且交互两个数的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "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 关系运算符 关系运算符用于比较两个值,并返回一个布尔值,表示它们之间的关系。通常用来决定程序的控制流,通过这些运算符,可以执行不同的操作,根据比较的结果来确定程序的下一步行动。
以下是一些常见的关系运算符以及它们的示例:
==
(等于):检查左右两边的值是否相等。如果相等,返回 true
;否则返回 false
。
1 2 3 a := 5 b := 5 result := (a == b)
!=
(不等于):检查左右两边的值是否不相等。如果不相等,返回 true
;否则返回 false
。
1 2 3 x := 10 y := 20 result := (x != y)
>
(大于):检查左边的值是否大于右边的值。如果成立,返回 true
;否则返回 false
。
1 2 3 p := 15 q := 10 result := (p > q)
<
(小于):检查左边的值是否小于右边的值。如果成立,返回 true
;否则返回 false
。
1 2 3 m := 8 n := 12 result := (m < n)
<=
(小于等于):检查左边的值是否小于或等于右边的值。如果成立,返回 true
;否则返回 false
。
1 2 3 r := 10 s := 10 result := (r <= s)
>=
(大于等于):检查左边的值是否大于或等于右边的值。如果成立,返回 true
;否则返回 false
。
1 2 3 u := 15 v := 10 result := (u >= v)
6.19.8 逻辑运算符 逻辑运算符用于执行逻辑操作,通常涉及布尔值(true
和 false
)。
以下是一些常见的逻辑运算符以及它们的示例:
&&
(逻辑与):如果左右两边的条件都为 true
,则整个表达式返回 true
;否则返回 false
。
1 2 3 a := true b := false result := a && b
||
(逻辑或):如果左右两边的条件中至少有一个为 true
,则整个表达式返回 true
;否则返回 false
。
1 2 3 x := true y := false result := x || y
!
(逻辑非):将条件的布尔值取反,即如果条件为 true
,则返回 false
;如果条件为 false
,则返回 true
。
这些逻辑运算符在编程中用于组合和操作布尔条件,以便进行决策和控制程序的流程。
例如:
您可以使用 &&
来执行逻辑与操作,只有当多个条件都为 true
时才执行某个代码块;
使用 ||
来执行逻辑或操作,当多个条件中至少一个为 true
时执行代码块;
使用 !
来执行逻辑非操作,反转条件的布尔值。
逻辑运算符在条件语句、循环控制和其他需要根据条件来决定程序行为的情况下非常有用。
6.19.9 位运算符: 位运算符是用于执行二进制位操作的运算符。它们通常用于处理整数数据的各个位,对数据的二进制表示进行操作。以下是一些常见的位运算符以及它们的示例:
&
(按位与):将两个数的每个位进行与运算,只有在两个数的对应位都为1时,结果的相应位才为1,否则为0。
1 2 3 a := 5 b := 3 result := a & b
|
(按位或):将两个数的每个位进行或运算,只要两个数的对应位中至少有一个位为1,结果的相应位就为1。
1 2 3 x := 5 y := 3 result := x | y
^
(按位异或):将两个数的每个位进行异或运算,只有在两个数的对应位不相同时,结果的相应位才为1,否则为0。
1 2 3 p := 5 q := 3 result := p ^ q
这些位运算符允许您执行各种位操作,包括清除、设置、反转和测试位。它们通常在需要直接操作二进制数据或处理位掩码时使用。位运算符在底层编程、嵌入式系统和处理硬件数据时非常有用。
6.19.10 其它运算符 除了算术运算符、关系运算符、逻辑运算符和位运算符之外,Go 语言还支持其他一些运算符,这些运算符具有特殊的功能。以下是一些常见的其他运算符:
&
(取地址运算符):用于获取变量的内存地址。
*
(指针解引用运算符):用于访问指针指向的变量的值。
1 2 3 var y int ptr := &y value := *ptr
<-
(通道操作符):用于发送数据到通道或从通道接收数据。
1 2 3 ch := make (chan int ) ch <- 42 data := <-ch
...
(省略号运算符):用于变参函数中,表示将可变数量的参数作为切片传递给函数。
1 2 3 4 5 func printValues (values ...int ) { for _, value := range values { fmt.Println(value) } }
::
(类型断言运算符):用于将接口类型断言为具体类型。
1 2 3 4 5 var val interface {}s, ok := val.(string ) if ok { fmt.Printf("val是字符串:%s\n" , s) }
,
(逗号运算符):用于同时执行多个表达式,但只返回最后一个表达式的值。
1 2 x, y := 1 , 2 result := (x, y, x+y)
这些其他运算符在特定的上下文中具有特殊的用途,例如指针操作、通道操作、类型断言、函数参数传递等。了解这些运算符可以帮助您更好地理解Go语言的不同方面和功能。
6.20 流程控制 Go语言的流程控制是指通过控制语句来管理程序的执行流程,以便根据不同条件执行不同的代码块。Go语言提供了条件语句和循环语句。
6.20.1 条件语句 当条件表达式为true
时,执行以下的代码:
条件表达式左右的小括号 ()
可以不写,也建议不写。
在Golang中,if
和条件表达式之间一定要有空格。
在Golang中,{}
是必须有的,即使你只写一行代码。
6.20.1.1 if语句
允许根据条件执行不同的代码块。可以包括if
、else
和else 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 mainimport ( "fmt" ) func main () { var score = 61 if score > 90 { fmt.Print("优秀" ) } else if score > 60 { fmt.Println("及格" ) } else { fmt.Println("不及格" ) } }
输出结果:
例子2:在条件语句区域定义变量并赋值
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { if score := 91 ; score > 90 { fmt.Print("优秀" ) } }
输出结果:
6.20.1.2 switch语句 用于根据不同的表达式值选择执行的代码块,类似于C语言的switch
语句。
当使用Go语言的switch
语句时,需要注意以下事项:
switch
后是一个表达式,可以是常量值、变量、或具有返回值的函数等。
case
后的各个值的数据类型必须与switch
表达式的数据类型一致。
case
后面可以带多个表达式,使用逗号间隔,例如:case 表达式1, 表达式2...
。
如果case
后面的表达式是常量值(字面量),则要求它们不能重复。
在case
后面不需要使用break
语句,一旦匹配到一个case
,对应的代码块将被执行,然后程序会退出switch
语句。如果一个都没有匹配到,会执行default
分支(如果存在)。
default
语句是可选的,用于处理未匹配到任何case
的情况。
switch
后可以不带表达式,这时可以当作类似于if
语句的分支来使用。
switch
后也可以直接声明/定义一个变量,以分号结束,不过这种做法并不是推荐的写法。
Go语言支持switch
穿透,可以使用fallthrough
关键字,如果在case
语句块后增加fallthrough
,则会继续执行下一个case
,这也被称为switch
穿透。
1 2 3 4 5 6 7 8 switch expression {case value1: case value2: default : }
例子1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" ) func main () { var errorcode = 404 switch errorcode { case 404 : fmt.Println("找不到页面" ) case 302 : fmt.Println("重定向" ) default : fmt.Println("未知错误" ) } }
输出结果:
例子2:switch当if语句来使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" ) func main () { var score = 91 switch { case score > 90 : fmt.Println("优秀" ) case score > 60 : fmt.Println("及格" ) default : fmt.Println("不及格" ) } }
输出结果:
例子3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" ) func main () { switch score := 91 ; { case score > 90 : fmt.Println("优秀" ) case score > 60 : fmt.Println("及格" ) default : fmt.Println("不及格" ) } }
输出结果:
例子4:穿透
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" ) func main () { switch score := 91 ; { case score > 90 : fmt.Println("优秀" ) fallthrough case score > 60 : fmt.Println("及格" ) default : fmt.Println("不及格" ) } }
输出结果:
6.20.2 循环语句 用于重复执行一段代码,可以是基本的for
循环、for range
循环,或无限循环。用于遍历切片、映射、通道等数据结构。
注意:go语言中是没有while循环的
格式:
1 2 3 4 5 6 7 8 9 10 11 for initialization; condition; post { } for index, value := range collection { } for { }
6.20.2.1 for循环 Go语言中有三种主要的循环结构,分别是for
、range
和select
。这些循环结构分别用于不同的情况和用途:
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { for i := 1 ; i < 10 ; i++ { fmt.Println(i) } }
输出结果:
6.20.2.2 for … range循环 1 2 3 4 5 6 7 8 9 10 11 package mainimport "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 mainimport "fmt" func main () { for { fmt.Println(1 ) } }
输出结果:
6.20.2.4 模拟while循环 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { var i int32 = 0 for i < 10 { fmt.Println(i) i++ } }
输出结果:
6.20.2.5 与select配合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "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) }
输出结果:
6.20.3.2 break具体的for循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "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) }
输出结果:
6.20.3.4 goto跳转语句 Go语言中少用,用于无条件跳转到标签处。
例子1:
1 2 3 4 5 6 7 8 9 10 11 12 package mainfunc 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 mainimport "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" ) }
输出结果:
6.21 函数 在Go语言中,函数(Function)是一种独立的代码块,用于执行特定任务或操作。函数是模块化程序设计的基本组成部分,函数是将可重用逻辑单元代码进行封装,有助于提高代码的可读性和可维护性。
6.21.1 关键特点和概念 以下是关于Go语言函数的一些关键特点和概念:
函数声明 :函数通常以func
关键字开始,后跟函数名、参数列表、返回类型和函数体。函数的基本声明形式如下:
1 2 3 func function_name (parameters) return_type { }
函数名称 :函数名称通常是由字母、数字和下划线组成的标识符,且函数名首字母大写表示函数是可导出的(在其他包中可访问),一般习惯为见名知意的驼峰式命名。
参数 :函数可以接受零个或多个参数,参数是用于传递数据给函数的。参数列表由参数名和参数类型组成。
返回值 :函数可以返回一个或多个值。返回值的类型由return_type
声明。
函数体 :函数体包含了函数的实际操作,是由一系列语句组成的代码块。
调用函数 :要使用函数,您需要在代码中调用它。函数调用通常由函数名和传递给函数的参数组成。
示例:
1 2 3 4 5 func add (a, b int ) int { return a + b } result := add(3 , 5 )
多返回值 :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 )
可变参数 :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 )
匿名函数 :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 mainimport "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 mainimport "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 mainimport "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 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var v int = 10 change(&v) fmt.Printf("v: %v\n" , v) } func change (num1 *int ) { *num1 = 999 }
输出结果:
对象传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" type Memory struct { name string } func main () { obj := &Memory{ name: "小明" , } change(obj) fmt.Println(obj) } func change (param *Memory) { param.name = "小红" }
输出结果:
6.21.5 函数数据类型 在Go中,函数也是一种数据类型,可以赋值给一个变量。可以通过使用该变量来调用函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "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) }
输出结果:
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 mainimport "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) }
输出结果:
6.21.7 多返回值函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "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 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "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) }
输出结果:
6.21.8 堆栈理解 如下图所示:
堆:有序,相互叠加
栈:无特定顺序
简单描述一下:Stack用于静态内存分配,Heap用于动态内存分配。记住,堆和堆栈都存储在内存中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "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) }
结合上面代码和图: 我们分析一下该段代码执行的每一行,并解释何时从堆或堆栈中分配、存储和释放对象。
第 1 行: Go Runtime为main方法在栈中创建一个内存块。
第2行: 创建了一个局部变量。该变量将被存储在main方法的栈内存中。
第3行: 创建了一个新的对象。对象存储在堆上,而对象的内存地址存储在main方法的栈上。
第4行: 与第 3 行相同
第 5 行 : Go Runtime为foo方法创建一个栈内存块。
第 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方法的栈块中的对象将被释放。
第9行 :main方法执行到最后一行时终止方式与第8行相同。go runtime会将main方法栈块中的对象将被释放。
如下图,当foo函数结束时,会将foo函数在栈内存释放。
如下图,同样当main函数结束时,会将main函数在栈内存释放。而右侧的堆内存,则需要借助垃圾收集器来进行回收,垃圾收集器需要检测未使用的对象,释放它们,并回收内存中的更多空间。
如下图,栈是一种 LIFO(后进先出)数据结构。我们可以将其视为一个盒子。通过使用这种结构,程序可以通过两个简单的操作轻松管理其所有操作:push 和pop 。
6.21.9 init函数 在Go语言中,init
函数是一个特殊的函数,用于在程序启动时执行一些初始化操作。每个包可以包含一个或多个init
函数,这些函数会在程序执行过程中的不同阶段按照它们在代码中的顺序被自动调用。
init
函数有以下特点:
init
函数不接受任何参数,也不返回任何值。
每个包中可以包含多个init
函数,它们按照它们在代码中的顺序依次执行。
init
函数是在程序启动时自动执行的,无需显式调用。
通常,init
函数用于执行一些包级别的初始化操作,例如初始化全局变量、执行一次性的设置或启动后台任务。这对于确保包的依赖关系和初始化顺序都正确非常有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "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 mainimport "fmt" func main () { add := func (x, y int ) int { return x + y } result := add(3 , 4 ) fmt.Println(result) }
输出结果:
6.21.11 闭包 闭包是一种特殊的函数,它可以捕获并访问其外部作用域中的变量。这意味着一个闭包函数可以在其定义的范围之外使用变量,而不需要将这些变量作为参数传递给函数。闭包通常的创建方式是:函数内部返回另外一个函数,并且内部函数使用外部函数的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func outfunc () func () int { var a int = 0 return func () int { a++ return a } } func main () { innerFunc := outfunc() fmt.Println(innerFunc()) fmt.Println(innerFunc()) }
输出结果:
6.21.12 defer关键词 在函数中,经常需要创建一些资源,为了确保这些资源能够在函数执行完毕后得到及时释放,Go的设计者引入了关键字defer。
defer的是先进后出。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "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的模块和包是有助于组织、管理和共享代码的重要概念,它们有助于构建可维护的应用程序和库。
模块(Module): Go 1.11及更高版本引入了模块支持。模块是一个版本化的代码单元,用于组织和管理Go代码。每个模块都有一个唯一的模块路径,并且可以包含多个包。模块的主要目的是为了管理依赖关系,以确保项目的稳定性和可重现性。
创建模块:你可以使用 go mod init 模块路径
命令来创建一个新的模块,其中 “模块路径” 是你的代码库的位置,通常是在你的代码库根目录执行该命令。
管理依赖:模块通过 go.mod
文件来记录项目的依赖关系,可以使用 go get
或 go mod tidy
等命令来添加、更新或删除依赖项。
包(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
是一个模块,而 math
和 utils
是两个包,它们各自包含了不同的Go文件。 main.go
是项目的入口文件,可以使用 import
语句导入 math
和 utils
包中的函数和类型。
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 mainimport ( "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" ) } }
输出结果:(等待十秒后)
6.23.2 Sleep睡眠函数 Sleep 会暂停当前 goroutine 至少持续时间 d。 负持续时间或零持续时间会导致睡眠立即返回。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { time.Sleep(10000 * time.Millisecond) fmt.Println("时间结束" ) }
输出结果:(等待十秒后)
6.23.3 Tick函数 time.Tick
是 Go 标准库中的一个函数,用于创建一个通道 (chan time.Time
),并以固定的时间间隔向该通道发送时间值,用于实现循环定时器。它通常用于周期性执行某个任务。
关于time.Tick
函数的一些重要注意事项:
Tick
是 NewTicker
的便利包装器:time.Tick
是 NewTicker
函数的一种便捷方式,提供了对定时通道的访问。NewTicker
返回一个 *time.Ticker
类型的值,而 Tick
返回的是 chan time.Time
,这使得 Tick
对于某些特定的使用场景更方便。
无法手动关闭 Tick:Tick
函数的一个特点是:它创建的循环定时器无法手动关闭,这意味着不能显式停止这个定时器,因为 Tick
只返回一个时间通道,并不提供关闭定时器的方法。
定时器泄漏问题:因为 Tick
创建的循环定时器无法被手动关闭,如果你不再需要它,它将继续触发并发送时间值到通道,而不会被回收,这可能导致资源泄漏。在 Go 中,如果没有办法关闭一个不再使用的定时器,它就无法被垃圾回收器回收,这被称为”泄漏”。
Tick
返回 nil 如果时间间隔小于等于0:最后一个注意事项提到,如果你尝试使用小于等于0的时间间隔来调用 Tick
,它将返回 nil
,因为不允许创建无效的定时器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "time" ) func main () { 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 mainimport ( "fmt" "time" ) func main () { hours, _ := time.ParseDuration("10h" ) complex , _ := time.ParseDuration("1h10m10s" ) fmt.Println(hours.Seconds()) fmt.Println(complex .Seconds()) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { startTime := time.Now() for i := 0 ; i < 1000000 ; i++ { } elapsedTime := time.Since(startTime) fmt.Printf("经过的时间:%s\n" , elapsedTime) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { startTime := time.Now() for i := 0 ; i < 1000000 ; i++ { } elapsedTime := time.Until(startTime) fmt.Printf("经过的时间:%s\n" , elapsedTime) }
输出结果:
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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { t := time.Duration.Abs(-10000 ) fmt.Printf("t: %v\n" , t) }
输出结果:
6.23.9 Duration的Hours函数 Hours 以浮点数形式的持续时间。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { h, _ := time.ParseDuration("4h30m" ) fmt.Printf("我还剩 %.1f 小时的工作时间。" , h.Hours()) }
输出结果:
6.23.10 Duration的Minutes函数 Minutes函数以浮点形式返回分钟计数的持续时间。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { u, _ := time.ParseDuration("1s" ) fmt.Printf("一秒等于 %f 分钟。\n" , u.Minutes()) }
输出结果:
6.23.11 Duration的Microseconds函数 Microseconds函数以整数形式返回微秒计数的持续时间。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { u, _ := time.ParseDuration("1s" ) fmt.Printf("一秒等于 %d 微秒。\n" , u.Microseconds()) }
输出结果:
6.23.12 Duration的Milliseconds函数 Milliseconds函数以整数形式返回毫秒计数的持续时间。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { u, _ := time.ParseDuration("1s" ) fmt.Printf("一秒等于 %d 毫秒。\n" , u.Milliseconds()) }
输出结果:
6.23.13 Duration的Nanoseconds函数 Nanoseconds函数以整数形式返回纳秒计数的持续时间。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { u, _ := time.ParseDuration("1s" ) fmt.Printf("一秒等于 %d 纳秒。\n" , u.Nanoseconds()) }
输出结果:
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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { m, _ := time.ParseDuration("1m30s" ) fmt.Printf("%.0f" , m.Seconds()) }
输出结果:
6.23.16 Duration的String函数 String 返回表示持续时间的字符串,格式为“72h3m0.5s”。 前导零单位被省略。
特殊情况:小于一秒的持续时间格式会使用较小的单位(毫秒、微秒或纳秒)来确保前导数字非零。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "time" ) func main () { m, _ := time.ParseDuration("72h3m1s55ms" ) fmt.Printf("%s" , m.String()) }
输出结果:
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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { loc, err := time.LoadLocation("America/New_York" ) if err != nil { panic (err) } fmt.Println(loc) }
输出结果:
6.23.19 FixedZone函数 FixedZone
函数是Go语言time
包中的一个函数,用于创建一个固定偏移的时区(Location
)。这个时区的偏移是相对于UTC的。UTC(协调世界时),全世界统一。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "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 mainimport ( "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 mainimport ( "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) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { loc, err := time.LoadLocation("America/New_York" ) if err != nil { fmt.Println("Error loading location:" , err) return } 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 mainimport ( "fmt" "time" ) func main () { 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 mainimport ( "fmt" "time" ) func main () { 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 mainimport ( "fmt" "time" ) func main () { invalidTimeString := "2023-13-01 12:30:00" _, err := time.Parse("2006-01-02 15:04:05" , invalidTimeString) if pe, ok := err.(*time.ParseError); ok { 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 mainimport ( "fmt" "time" ) func main () { invalidTimeString := "2023-13-01 12:30:00" _, err := time.Parse("2006-01-02 15:04:05" , invalidTimeString) if pe, ok := err.(*time.ParseError); ok { 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 mainimport ( "fmt" "time" ) func main () { ticker := time.NewTicker(2 * time.Second) time.Sleep(5 * time.Second) for t := range ticker.C { fmt.Println("Tick at" , t) } 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 mainimport ( "fmt" "time" ) func main () { ticker := time.NewTicker(2 * time.Second) for i := 0 ; i < 3 ; i++ { fmt.Println(<-ticker.C) } ticker.Reset(3 * time.Second) for i := 0 ; i < 3 ; i++ { fmt.Println(<-ticker.C) } 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 mainimport ( "fmt" "time" ) func main () { ticker := time.NewTicker(1 * time.Second) for i := 0 ; i < 5 ; i++ { fmt.Println(<-ticker.C) } ticker.Stop() 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
类型的定义如下:
以下是 Time
类型的一些常见操作和用法:
获取当前时间:
1 currentTime := time.Now()
指定时间:
1 specificTime := time.Date(2023 , time.November, 14 , 12 , 30 , 0 , 0 , time.UTC)
时间格式化:
1 formattedTime := currentTime.Format("2006-01-02 15:04:05" )
时间比较:
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." ) }
时间计算:
1 futureTime := currentTime.Add(2 * time.Hour)
获取年、月、日等组件:
1 2 3 4 year := currentTime.Year() month := currentTime.Month() day := currentTime.Day()
时区操作:
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 mainimport ( "fmt" "time" ) func main () { 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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { timeString := "2023-11-14 15:30:00" layout := "2006-01-02 15:04:05" 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 mainimport ( "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 } 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 mainimport ( "fmt" "time" ) func main () { unixTimestamp := int64 (1605134515 ) nanoseconds := int64 (500000000 ) convertedTime := time.Unix(unixTimestamp, nanoseconds) fmt.Println("Converted time:" , convertedTime) 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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "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 mainimport ( "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
在 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 mainimport ( "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { current := time.Now() addOneHour := current.Add(1 * time.Hour) result := addOneHour.Compare(current) fmt.Println("result: " , result) }
输出结果:
6.23.46 Equal函数 time.Equal
函数用于比较两个时间对象是否相等。如果两个时间相等,则 Equal
返回 true
;否则返回 false
。
1 func (t Time) Equal(u Time) bool
以下是一个简单的示例,演示了如何使用 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 mainimport ( "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
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 mainimport ( "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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "bytes" "encoding/gob" "fmt" "time" ) func main () { currentTime := time.Now() var buffer bytes.Buffer encoder := gob.NewEncoder(&buffer) err := encoder.Encode(currentTime) if err != nil { fmt.Println("Error encoding time:" , err) return } 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 )
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 mainimport ( "bytes" "encoding/gob" "fmt" "time" ) func main () { currentTime := time.Now() encodedData, err := currentTime.GobEncode() if err != nil { fmt.Println("Error encoding time:" , err) return } fmt.Println("Encoded data:" , encodedData) 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
包中,Year
是 Time
类型的方法,用于返回时间中的年份。方法的签名如下:
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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() year := currentTime.Year() fmt.Println("Current Year:" , year) }
在这个例子中,Year
方法返回当前时间的年份,并将其打印出来。请注意,Year
方法返回的是一个 int
类型的值。
6.23.52 YearDay函数 在 Go 语言的 time
包中,YearDay
是 Time
类型的方法,用于返回一年中的第几天。方法的签名如下:
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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() weekday := currentTime.Weekday() fmt.Println("Today is:" , weekday) }
输出结果:
6.23.55 Day函数 Day 返回 t 指定的月份中的第几天。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "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) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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
包中,Nanosecond
是 Time
类型的一个方法,用于获取时间的纳秒部分。方法的签名如下:
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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() nanoseconds := currentTime.Nanosecond() fmt.Println("Nanoseconds:" , nanoseconds) }
在这个例子中,Nanosecond
方法被用于获取当前时间的纳秒部分,并将其打印出来。注意,这个值的范围是 0 到 999999999(纳秒),表示给定时间的纳秒偏移量。
6.23.60 ISOWeek函数 可以使用 ISOWeek
函数来获取时间属于一年中的 ISO 周数。
1 func ISOWeek (t Time) (year, week int )
以下是一个简单的示例,演示如何使用 ISOWeek
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() anotherLocation, err := time.LoadLocation("America/Los_Angeles" ) if err != nil { fmt.Println("Error loading location:" , err) return } 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.Time
的 In
方法来判断某个时间是否处于夏令时(Daylight Saving Time,DST)。
以下是一个示例,演示如何检查某个时间是否处于夏令时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() isdst := currentTime.IsDST() fmt.Println("是否为夏令时:" , isdst) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { thistime := time.Date(1 , 1 , 1 , 0 , 0 , 0 , 0 , time.UTC) iszero := thistime.IsZero() fmt.Println("是否是零时刻:" , iszero) }
输出结果:
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 mainimport ( "fmt" "time" ) func main () { anotherLocation, _ := time.LoadLocation("America/Los_Angeles" ) currentTime := time.Date(2023 , 11 , 14 , 0 , 0 , 0 , 0 , anotherLocation) 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 )
该方法返回一个字节切片和可能的错误。字节切片包含了二进制编码的时间数据,可以用于后续的存储或传输。
以下是一个简单的示例,演示如何使用 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 mainimport ( "encoding/base64" "fmt" "time" ) func main () { currentTime := time.Now() binaryData, err := currentTime.MarshalBinary() if err != nil { fmt.Println("Error encoding time:" , err) return } 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 )
该方法返回一个字节切片和可能的错误。字节切片包含了时间对象的 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 mainimport ( "encoding/json" "fmt" "time" ) func main () { currentTime := time.Now() 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 )
该方法返回一个字节切片和可能的错误。字节切片包含了时间对象的文本表示,可以用于文本编码。
以下是一个简单的示例,演示如何使用 MarshalText
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() 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 mainimport ( "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 mainimport ( "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 mainimport ( "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) }
输出结果:
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 mainimport ( "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" )) } 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 mainimport ( "fmt" "time" ) func main () { localTime := time.Now() fmt.Println("Local Time:" , localTime) 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 mainimport ( "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 } myInstance := MyStruct{} 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 mainimport ( "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 () { jsonData := []byte (`{"field1": 42, "field2": "Hello, JSON!"}` ) myInstance := MyStruct{} 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 mainimport ( "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" ) myInstance := MyStruct{} 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 mainimport ( "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, _ := time.Zone() fmt.Println("Current Location:" , location) fmt.Println("Zone Information:" , zone) }
在这个例子中,time.LoadLocation
函数加载一个特定的时区信息(在这里是纽约时区),然后 time.Zone
函数用于获取当前时间的时区信息。请注意,Zone
函数一般不直接使用,而是通过 LoadLocation
或 FixedZone
这样的函数来获取时区信息。
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 mainimport ( "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
),表示多长时间后执行回调函数 f
。f
是一个函数,表示定时器到期时要执行的回调操作。该函数返回一个 Timer
对象,你可以用来取消定时器。
以下是一个简单的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "time" ) func main () { 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 mainimport ( "fmt" "time" ) func main () { 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
包中,Reset
是 Timer
类型的一个方法,而不是一个单独的函数。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 mainimport ( "fmt" "time" ) func main () { 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
包中,Stop
是 Timer
类型的一个方法,而不是一个单独的函数。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 mainimport ( "fmt" "time" ) func main () { 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
是一个类型,表示星期几。这个类型定义如下:
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 mainimport ( "fmt" "time" ) func main () { currentTime := time.Now() weekday := currentTime.Weekday() fmt.Println("Today is:" , weekday.String()) }
输出结果:
6.24 内置函数 当你使用Golang(Go语言)进行编程时,Golang的设计者提供了一些方便的函数,这些函数无需导入特定的包,即可直接使用。这些被称为Golang的内置函数或内建函数。以下是一些常见的Golang内建函数:
6.24.1 append函数 1 2 3 4 5 6 7 8 9 10 package mainimport "fmt" func main () { slice := []int {1 , 2 , 3 } slice = append (slice, 4 , 5 ) fmt.Println(slice) }
输出结果:
6.24.2 cap函数 1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { slice := make ([]int , 3 , 5 ) fmt.Println(cap (slice)) }
输出结果:
6.24.3 Clear函数 go1.21.0版本新增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var array []int array = append (array, 2 ) array = append (array, 3 ) array = append (array, 4 ) array = append (array, 8 ) clear(array) fmt.Println(array) }
输出结果:
6.24.4 close函数 1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { c := make (chan int ) close (c) }
6.24.5 complex函数 1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { c := complex (1.0 , 2.0 ) fmt.Println(c) }
输出结果:
6.24.6 copy函数 1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { src := []int {1 , 2 , 3 } dst := make ([]int , len (src)) copy (dst, src) fmt.Println(dst) }
输出结果:
6.24.7 delete函数 1 2 3 4 5 6 7 8 9 10 package mainimport "fmt" func main () { m := map [string ]int {"a" : 1 , "b" : 2 } delete (m, "a" ) fmt.Println(m) }
输出结果:
6.25 defer和recover错误处理机制 在Golang中,defer
和 recover
是一对用于错误处理的机制,通常与 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) } }() panic ("Oops, something went wrong!" ) }
在上述例子中,recover
函数会捕获发生在 defer
中的 panic
,并打印一条错误消息。
通常,defer
和 recover
结合使用,以确保程序在发生异常时能够进行适当的清理工作,而不是立即退出。这样可以提高程序的健壮性,尽量避免未处理的 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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "errors" "fmt" ) func main () { err := errors.New("emit macho dwarf: elf header corrupted" ) 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 mainimport ( "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 mainimport ( "errors" "fmt" ) func main () { err1 := errors.New("error1" ) err2 := fmt.Errorf("error2: [%w]" , err1) fmt.Println(err2) fmt.Println(errors.Unwrap(err2)) }
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 mainimport "fmt" func main () { 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 ("Cannot divide by zero" ) } 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 mainimport ( "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
函数来获取:
数组的地址与数组的第一个元素的地址是一致的:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "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 mainimport "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 { }
注意事项:
coll
是你要遍历的数组、切片、字符串、映射或通道。
每次遍历得到的索引由 key
接收,每次遍历得到的索引位置上的值由 val
接收。
key
和 val
的名字可以根据需要随意命名,如 k
、v
、key
、value
等。
在这个循环中,key
和 val
是局部变量。
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 mainimport "fmt" func main () { var scores [5 ]int for i := 0 ; i < len (scores); i++ { fmt.Printf("请录入第%d个学生的成绩: " , i+1 ) fmt.Scanln(&scores[i]) } fmt.Println("方式1: 普通 for 循环" ) for i := 0 ; i < len (scores); i++ { fmt.Printf("第%d个学生的成绩为: %d\n" , i+1 , scores[i]) } fmt.Println("-----------------------------" ) fmt.Println("方式2: for-range 循环" ) for key, value := range scores { fmt.Printf("第%d个学生的成绩为: %d\n" , key+1 , value) } fmt.Println("-----------------------------" ) fmt.Println("方式3: for-range 循环,不使用 key" ) for _, value := range scores { fmt.Printf("学生的成绩为: %d\n" , value) } }
6.27.4 数组注意事项
长度是类型的一部分:
在 Go 语言中,数组的长度是数组类型的一部分,不同长度的数组被视为不同的类型。
数组是值类型,进行值传递:
在 Go 中,数组是值类型。默认情况下,对数组的操作是值拷贝,即在函数传递时会复制整个数组,而不是传递对原始数组的引用。
使用引用传递(指针方式)修改原数组:
如果需要在其他函数中修改原始数组,可以使用引用传递,通常通过传递数组的指针实现。这样,函数内对数组的修改会影响原始数组。
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 mainimport "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 mainimport "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 语言中,切片是一种对数组的动态窗口引用,它提供了灵活的方式来处理数组片段。
切片的数据结构:
切片是一个数据结构,包含三个字段:指向底层数组的指针 、切片的长度len 和切片的容量cap 。这三个字段一起描述了切片的属性和范围。
底层数组:
切片并不是自包含的数据结构,而是建立在底层数组之上的引用。底层数组是切片的实际数据存储位置。
切片长度和容量:
切片的长度表示当前切片中的元素数量,容量表示切片底层数组中的可访问元素数量。
动态扩展和收缩:
切片支持动态扩展和收缩。当切片的长度超过容量时,系统会创建一个新的底层数组,并将原有的元素复制到新数组中。这使得切片可以根据需要动态调整大小。
切片传递的是引用:
切片是引用类型,传递切片时实际上传递的是指向底层数组的指针。
对切片的修改会影响底层数组和其他引用该底层数组的切片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "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 mainimport "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 切片内存分析
6.28.3 使用make函数定义切片 1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { 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 mainimport "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 切片的注意事项
切片的定义和使用:切片在定义后不能直接使用,需要将其引用到一个数组,或者使用 make
函数为切片分配空间。
切片越界检查:切片在使用时要注意防止越界,即访问超过切片长度的元素。
切片的简写:切片的定义可以使用简写形式:
var slice = arr[0:end]
等同于 var slice = arr[:end]
var slice = arr[start:len(arr)]
等同于 var slice = arr[start:]
var slice = arr[0:len(arr)]
等同于 var slice = arr[:]
切片的连续切片:切片可以继续切片,形成对原切片的连续引用。
切片的元素追加:切片支持在尾部追加元素,使用 append
函数实现。
切片的复制:切片可以通过 copy
函数进行复制,将一个切片的内容复制到另一个切片。
6.29 映射 6.29.1 映射简介
映射(Map)概述:映射是 Go 语言中内置的一种类型,用于将键和值相关联。
通过键(key),我们可以获取对应的值(value)。
基本语法:
常见的 key 和 value 类型:
key
通常为 int 或 string 类型,而 value
通常为数字(整数、浮点数)、字符串、映射、结构体等。
但不可以使用切片、映射、函数作为映射的 key
或 value
类型。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { 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 mainimport "fmt" func main () { var map1 map [int ]string map1 = make (map [int ]string , 10 ) map1[1 ] = "hello" fmt.Println(map1) var map2 = make (map [int ]string ) map2[2 ] = "tttttt" fmt.Println(map2) 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 mainimport "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 mainimport "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语言面向对象编程说明:
Golang支持面向对象编程:
Golang支持面向对象编程,但与传统的面向对象编程有一些区别,因此并不是纯粹的面向对象语言。
没有类,使用结构体:
Golang没有类的概念,而是使用结构体(struct)来实现面向对象编程。结构体在Golang中与其他编程语言的类有同等的地位。
简洁的面向对象特性:
Golang的面向对象编程非常简洁,省略了传统OOP语言中的方法重载、构造函数和析构函数、隐藏的this指针等概念。
继承、封装和多态的实现方式:
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 mainimport "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 结构体内存分析
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 mainimport "fmt" type Teacher struct { Name string Age int School string } func main () { var t1 Teacher t1.Name = "小明" t1.Age = 18 t1.School = "清华大学" fmt.Println(t1) var t2 Teacher = Teacher{"李四" , 15 , "广东大学" } fmt.Println(t2) var t3 *Teacher = new (Teacher) (*t3).Name = "王五" (*t3).Age = 16 (*t3).School = "北京大学" fmt.Println(*t3) 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 mainimport "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 mainimport "fmt" type Student struct { Age int } type Stu Studentfunc 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 mainimport "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 mainimport "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() 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 mainimport "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() 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 mainimport "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 mainimport "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 mainimport "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 mainimport "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)是一种面向对象编程的概念,它将数据和对数据的操作封装在一起,以实现数据的保护和隐藏。在封装中,只有通过授权的操作方法才能访问和修改数据,从而隐藏了实现的细节,提高了程序的安全性和合理性。
封装的好处:
隐藏实现细节: 将数据和实现细节隐藏在内部,使外部模块只能通过指定的接口进行访问。
数据验证: 允许对数据进行验证,确保数据的安全和合理性。
Golang中的封装实现:
在Golang中,封装可以通过以下步骤实现:
结构体和字段的命名: 建议将结构体和字段的首字母小写,以限制其在其他包中的访问。
工厂函数: 提供一个首字母大写的工厂函数,用于创建结构体实例,类似于构造函数。
Set方法: 提供一个首字母大写的Set方法,用于对属性进行赋值,并包含数据验证的业务逻辑。
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 mypackagetype MyStruct struct { age int } func NewMyStruct () *MyStruct { return &MyStruct{} } func (s *MyStruct) SetAge(newAge int ) { if newAge >= 0 && newAge <= 120 { s.age = newAge } else { fmt.Println("Invalid age value" ) } } func (s *MyStruct) GetAge() int { return s.age }
在上面的示例中,结构体MyStruct
的字段age
被封装在内部,只能通过SetAge
和GetAge
方法进行访问。SetAge
方法包含了数据验证的逻辑,确保年龄在合理范围内。通过这种方式,实现了对数据的封装和保护。
1 2 3 4 5 6 7 8 9 10 package mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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.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 mainimport "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.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 mainimport "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.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 mainimport "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 mainimport "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 接口简介
接口定义与实现: 接口可以定义一组方法,但这些方法不需要实现具体的方法体,并且接口中不能包含任何变量。自定义类型在实现接口时,根据具体情况具体实现接口中定义的方法。
接口实现的规则: 实现接口时,必须实现接口中定义的所有方法,否则编译器会报错。
Golang中的接口实现方式: Golang中不需要显式使用implement
关键字来实现接口,而是基于方法的实现。这种方式让接口的实现更加灵活,没有强制的接口关键字。
多接口实现: 一个结构体只需实现了多个接口中定义的全部方法,即可被认为实现了这些接口。实现接口的耦合性较低,这种灵活性比一对一的接口实现更为松散。
接口的目的: 定义规范,为其他类型提供一组方法的契约。实现接口的具体类型可以根据需要来灵活地实现这些方法。
示例代码:
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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "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 mainimport "fmt" type Shape interface { Area() float64 } type Rectangle struct { Width float64 Height float64 } type Circle struct { Radius float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } 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 mainimport "fmt" type Shape interface { Area() float64 } type Rectangle struct { Width float64 Height float64 } type Circle struct { Radius float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } func main () { shapes := []Shape{ Rectangle{Width: 4 , Height: 5 }, Circle{Radius: 3 }, } 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 mainimport "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 ) printInt("hello" ) }
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 mainimport ( "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 mainimport ( "log" "os" ) func main () { f, err := os.CreateTemp("" , "example" ) if err != nil { log.Fatal(err) } defer os.Remove(f.Name()) 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 mainimport ( "log" "os" ) func main () { f, err := os.CreateTemp("" , "example.*.txt" ) if err != nil { log.Fatal(err) } defer os.Remove(f.Name()) 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 mainimport ( "fmt" "io" "os" ) func main () { 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 mainimport ( "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 mainimport ( "log" "os" ) func main () { 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() 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 mainimport ( "os" ) func main () { file, _ := os.Open("./anotherdir" ) file.Chdir() os.WriteFile("abc" , []byte ("tttttt" ), 0755 ) }
6.37.7 Chmod函数 chmod
函数可修改文件的模式。
1 2 3 4 5 6 7 8 9 10 package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "log" "os" ) func main () { dirPath := "./abc" 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 mainimport ( "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) 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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "log" "os" ) func main () { file, err := os.Open("example.txt" ) if err != nil { log.Fatal(err) } defer file.Close() 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) 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 mainimport ( "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 mainimport ( "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 mainimport ( "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 } 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 mainimport ( "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)模型,也是一种网络协议的抽象参考模型。
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