这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
走进Go语言基础语言
前排提示:文章内容较多,如需上下文对照阅读,请善用CTRL+F和目录功能
前言
在我曾看过的一本书中,胡适曾提到:”怕什么真理无穷,进一寸有一寸的欢喜“。(《进一寸有一寸的欢喜:胡适谈读书》)我认为,学习计算机也是这样。计算机技术日新月异,但如果我们能持续学习、不断进步,就能够在编程/计算机领域取得更大成就。
对于这篇笔记,我通常喜欢在记录笔记时通过搜索引擎检索一些相关的资料,并对我的笔记进行补充,但难免有遗漏/错误之处,如果你在我的笔记中发现了错误之处,请你务必通过评论区或私信与我进行联系,我会及时进行指正。同时,在阅读这篇笔记的过程中,你有什么想法想和我进行交流,也可以发在评论区或我的私信,我看到后会及时进行回复,谢谢!
本堂课重点内容
- Go语言的背景介绍
- Go语言开发环境的搭建
- Go语言基础语法速览
- Go语言常用准库用法
介绍
什么是Go语言?
Go语言是一种编程语言,由Google开发,旨在提供一种简洁、高效的编程体验。它是一种静态类型、编译型语言,具有高性能、可伸缩性和简洁明了的语法。Go语言主要用于系统编程和云服务等领域,具有丰富的标准库和社区支持。它的语法类似于C语言,但更加简洁,并支持高并发编程。Go语言的并发模型基于 goroutine 和 channel,可以很方便的进行并发编程。
简而言之,Go语言是一种高效、简洁且易于学习的编程语言,适用于系统编程和云服务。
Go语言的特性
Go语言有以下特性:
-
高性能、高并发
Go语言有和C++、Java媲美的性能,并且内嵌了对高并发的支持
-
语法简单、学习曲线平缓
Go语言语法风格类似C语言,并且和C语言相比,其
在语法上进行大幅度简化,上手容易eg1:去掉了不需要的表达式括号,循环只有for循环一种表示方法等
eg2:HTTP服务器,具体代码解释可见下方码上掘金
示例一 -
丰富的标准库
很多情况下
不需要依赖第三方库就可以实现基础功能的实现与开发,降低了学习和使用成本 标准库有很高的稳定性和兼容性保障,能及时享受语言迭代带来的性能优化 -
完善的工具链
诞生之初就具有
丰富的工具链,比如:编译、代码格式化、错误检查、帮助文档、包管理、代码补充提示 有单元测试框架,能支持单元测试、性能测试、代码覆盖率、性能优化 -
静态链接
在Go语言中,所有的编译结构默认都是静态链接的,可以使用go build命令来编译源代码。编译后的二进制文件可以直接在机器上运行,不需要额外的运行时环境。 例如:
go build main.go这条命令会把main.go文件编译成可执行文件,文件名为main, 然后可以直接运行这个文件。
同时,在容器环境下运行,镜像体积可以控制得非常小,部署方便快捷
-
快速编译
Go语言拥有静态编译语言中最快的编译速度
在本地开发,几乎任何时候修改代码,都能在1秒钟左右增量编译完成
-
跨平台
Go语言能在常见的Windows、Linux、MacOS下运行,能够用来开发安卓、OS软件
同时,Go语言也能在其他设备上运行,比如树莓派、路由器
Go语言还支持交叉编译特性,在一个平台上编译出来的可执行程序可以在其他平台上运行。
Go语言在编译时会自动检测目标平台的架构,并生成对应的机器码。所以可以在一个平台上编译出来的可执行程序可以在其他平台上运行。这样可以避免在不同平台上重复编译的麻烦。
例如,在Windows平台上编译的可执行程序可以在Linux平台上运行,只需要在编译时指定目标平台的架构。
编译命令如下:
GOOS=linux GOARCH=amd64 go build main.go这条命令会把main.go文件编译成可执行文件,文件名为main, 然后可以在linux平台上运行这个文件。
需要注意的是,交叉编译并不支持所有的系统,特别是那些不常用的系统,可能需要手动设置环境变量来支持。(比如:Solaris、AIX、ARM、MIPS、PPC)
对于交叉编译不支持的系统,需要在该系统上进行本地编译才能运行
-
垃圾回收
和Java类似,使用Go语言进行编程的时候无需考虑内存的分配释放,可以专注于业务逻辑
Tips在即将到来的Go1.20版本中,可能会更新`手动感兴趣的同学可以看看这篇文章:打脸了兄弟们,Go1.20 arena 来了! - 掘金 (juejin.cn)
Go语言的应用
Go语言是一种新兴的编程语言,近年来已经被广泛应用在各种场景中,几乎所有的云原生组件都是用Go来实现的:
Docker: Docker是一个容器管理工具,它的核心部分是使用Go语言开发的。
Kubernetes: Kubernetes是一个容器编排工具,它的核心部分也是使用Go语言开发的。
Etcd: Etcd是一个高可用的分布式键值存储系统,它的核心部分是使用Go语言开发的。
Prometheus: Prometheus是一个监控系统,它的核心部分是使用Go语言开发的。
InfluxDB: InfluxDB是一个时序数据库,它的核心部分也是使用Go语言开发的。
Istio: Istio是一个微服务网格平台,它的核心部分是使用Go语言开发的。
Terraform: Terraform是一个资源管理工具,它的核心部分也是使用Go语言开发的。
GoLang自身 : GoLang 的编译器和核心库都是使用Go语言开发。
入门
安装Golang
相关链接:
Golang官网go.dev/
Go语言中文网:studygolang.com/dl
七牛云:goproxy.cn/
打开golang官网,下载golang的二进制文件进行安装即可
如果无法打开官网,也可以使用Go语言中文网提供的镜像下载安装
安装完成后,如果遇到第三方包无法安装,可以尝试使用七牛云提供的镜像
对于开发环境的搭建,可以参考:
练习 - 安装 Go - Training | Microsoft Learn
练习 - 安装 Visual Studio Code 和 Go 扩展 - Training | Microsoft Learn
Tips上文开发环境的搭建使用的软件是Visual Studio Code,如果不想使用,或者嫌装插件太麻烦,可以使用JetBrains公司开发的IDE(集成开发环境):GoLand,可省去不少步骤
GoLand是一个商业付费软件,你可以免费试用30天再决定是否购买。同时,如果你是在校学生,或者你维护着一个开源仓库,你可以向JetBrains公司申请免费试用,这里不再赘述。 相关链接:
学生免费申请:www.jetbrains.com/zh-cn/commu…
开源仓库:www.jetbrains.com/zh-cn/commu…
基础语法
打印Hello World
具体代码解释见下方码上掘金示例二
输出结果:
变量
Go语言支持多种变量类型,主要包括:
- 基本数据类型: int, float, bool, string, rune, byte
- 派生数据类型: 指针, 数组, 结构体, 切片, 接口
- 引用类型: 指针, 数组, 结构体, 切片, 接口
- 类型别名: 用type关键字定义
- 常量: 用const关键字定义
- 基本数据类型: int, float, bool, string, rune, byte
- 派生数据类型: 指针, 数组, 结构体, 切片, 接口
- 引用类型: 指针, 数组, 结构体, 切片, 接口
- 类型别名: 用type关键字定义
- 常量: 用const关键字定义
Go语言变量定义格式为: var 变量名 变量类型 = 值
例如: var a int = 10
也可以省略类型,通过初始值推断类型: var a = 10
还可以使用简化的变量声明语法: a:=10
变量的默认值为0,布尔类型默认值为false,字符串类型默认值为""。
Go语言支持的变量类型非常丰富,包括基本类型、派生类型和引用类型,能够满足不同场景下的需求。在实际开发中,我们通常会使用基本数据类型来定义变量,而派生数据类型和引用类型则用于处理更复杂的数据结构和程序流程。
另外,Go语言还支持类型别名和常量,分别用于定义类型别名和定义常量。常量是不可变的变量,可以用来存储程序运行期间不会改变的数据。
除此之外,Go语言还支持自动类型推导,即变量的类型由编译器根据变量的初始值自动推导。这样可以简化代码,减少类型的显式定义。
另外,Go语言还支持变量的多重赋值,这样可以同时对多个变量进行赋值。
Go语言变量类型的特性,如值类型和引用类型的区别,变量的默认值,变量的自动类型推导和多重赋值等,需要在实际编码中结合具体场景来理解和掌握。比如,值类型是不能直接指向内存地址,而引用类型可以。因此在使用变量时需要根据需要选择合适的类型。
对于自动类型推导,需要注意的是,它只能在变量声明和初始化时使用,不能在赋值语句中使用。
对于多重赋值,需要注意的是,它只能在同时赋值相同类型的变量时使用。
总之, Go语言变量类型提供了丰富的特性和灵活的使用方式, 开发者需要根据具体场景选择合适的变量类型并正确使用。
具体代码解释见下文码上掘金处示例三 小结:
- 变量和常量都必须在使用前先定义。
- 变量和常量都可以使用不同的方式进行初始化和赋值
- 变量和常量都可以使用fmt包中的Println函数进行输出。
- 常量的值不能被修改
- 变量在定义时如果没有赋值将会自动赋予对应类型的零值
if else
Go语言中的if else语句用于在特定条件下执行不同的代码。语法格式如下:
if 条件表达式 {
// 当条件表达式为真时执行
} else {
// 当条件表达式为假时执行
}
条件表达式可以是任何类型的表达式,其结果会被转换为布尔值,如果表达式为真,则执行if块中的代码,否则执行else块中的代码。
举个例子:
package main
import "fmt"
func main() {
a := 5
if a > 10 {
fmt.Println("a is greater than 10")
} else {
fmt.Println("a is less than or equal to 10")
}
}
这个程序会输出"a is less than or equal to 10",因为变量a的值是5,不大于10。
Go语言也支持嵌套if-else语句,以及else if语句的使用。
举个例子:
package main
import "fmt"
func main() {
a := 5
if a > 10 {
fmt.Println("a is greater than 10")
} else if a > 0 {
fmt.Println("a is greater than 0")
} else {
fmt.Println("a is less than or equal to 0")
}
}
这个程序会输出"a is greater than 0",因为变量a的值是5,大于0 。
在Go语言中,if语句还可以在条件表达式之前定义变量,这个变量仅在if语句块中可用。
举个例子:
package main
import "fmt"
func main() {
if a := 5; a > 10 {
fmt.Println("a is greater than 10")
} else {
fmt.Println("a is less than or equal to 10")
}
// fmt.Println(a) // a is not defined in this scope
}
这个程序会输出"a is less than or equal to 10",在if语句块外是无法访问a变量的。
具体代码解释见注释:
package main
import "fmt"
func main() {
// 这个语句的条件表达式是7%2==0,因为7除以2的余数为1,所以条件表达式为假,程序会输出"7 is odd"。
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
// 这个语句的条件表达式是8%4==0,因为8除以4的余数为0,所以条件表达式为真,程序会输出"8 is divisible by 4"。
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
// 这个语句中首先定义了一个变量num = 9,然后进行条件判断,第一个if条件表达式num < 0 为假,第二个else if条件表达式num < 10 为真,所以程序会输出"9 has 1 digit"。
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
循环
Go语言中提供了for循环来执行重复性任务。
最常用的for循环格式如下:
for initialization; condition; post {
// 被执行的代码
}
其中,initialization是初始化语句,通常用于定义循环变量;condition是循环条件表达式,当它为真时继续循环;post是循环后语句,通常用于更新循环变量。
for循环可以省略初始化语句和循环后语句,如果省略初始化语句,则循环变量必须在外部定义;如果省略循环后语句,则循环变量在循环结束后不会更新。
除此之外,Go还提供了其他的循环控制方式,如for-range循环用于遍历数组、切片和字典等,可以使用break语句和continue语句来控制循环流程。
例如:
package main
import "fmt"
func main() {
arr := [3]int{1, 2, 3}
for i, v := range arr {
fmt.Println(i, v)
}
}
这段代码使用for-range循环遍历了数组arr,输出了数组的索引和值。
具体代码解释见注释:
package main
import "fmt"
func main() {
i := 1
// 没有条件表达式,循环会无限循环,直到遇到break语句退出循环
for {
fmt.Println("loop")
break
}
// 这个循环的初始化语句是j := 7,条件表达式是j < 9,循环后语句是j++, 这个循环会输出7,8
for j := 7; j < 9; j++ {
fmt.Println(j)
}
// 这个循环的初始化语句是n := 0,条件表达式是n < 5,循环后语句是n++, 在循环体中有一个if语句,当n%2==0时继续下一次循环, 这个循环会输出1,3
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
// 这个循环只有一个条件表达式i <= 3, 没有初始化语句和循环后语句,这个循环会输出1,2,3.
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
switch
Go语言的switch语句是一种条件分支语句。它可以用来对一个变量进行多个条件的判断。switch语句的语法类似于C语言中的switch语句。
一个switch语句包含若干个case子句,每个子句都包含一个常量表达式和一条语句。当switch表达式的值与case子句中的常量表达式相等时,执行相应的语句。如果没有匹配的case子句,则执行default子句(如果有的话)。
例如:
package main
import "fmt"
func main() {
x := 2
switch x {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
default:
fmt.Println("unknown")
}
}
输出:two
Go语言中switch还支持在case中使用表达式,并且可以不使用break语句,会自动结束。
Tips在Go语言中,除非显式地使用了fallthrough语句,switch语句默认会查找与表达式相等的值
具体代码解释见注释:
package main
import (
"fmt"
"time"
)
func main() {
// 这个switch语句用来判断变量a的值。在这个例子中,a的值为2。因此,会执行case 2的语句,输出"two"。
// case 4, 5 中支持多值判断,如果a为4或者5都会输出"four or five"
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
// 这个switch语句没有表达式,而是使用了case语句中的表达式来进行判断。在这个例子中,t是一个time.Time类型的变量,它表示当前时间。t.Hour()返回当前时间的小时数。因此,如果当前时间的小时数小于12,则会执行第一个case语句中的语句,输出"It's before noon"。否则,会执行default语句中的语句,输出"It's after noon"。
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
数组
Go语言中的数组是固定长度的,数组长度是其类型的一部分。 数组长度定义在中括号[]内。 例如,在Go中定义一个长度为5的整数数组:
var myArray [5]intv
可以在定义时初始化数组,例如:
myArray := [5]int{1, 2, 3, 4, 5}
也可以使用简短的语法,Go会自动推断数组的长度:
myArray := [...]int{1, 2, 3, 4, 5}
Go语言中还支持多维数组。例如,可以定义一个 2x3 的整数数组:
var myMatrix [2][3]int
可以使用多个下标来访问多维数组中的元素,例如:
myMatrix[0][2] = 5
数组是值类型,如果需要在函数中修改数组的值,需要使用数组指针。例如:
func modifyArray(arr *[3]int) {
arr[0] = 10
}
另外Go语言还提供了内置的len()函数来获取数组长度,例如:
fmt.Println(len(myArray)) // Output: 5
具体代码解释见注释:
package main
import "fmt"
func main() {
// 定义了一个长度为 5 的 int 类型数组 "a"
var a [5]int
// 将第 4 个元素的值设为 100
a[4] = 100
// 使用 fmt.Println 打印了 "a" 数组的第 2 个元素和数组的长度
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
// 定义了一个包含 5 个元素的 int 类型数组 "b",并使用简短的语法将其初始化为 1, 2, 3, 4, 5
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
// 定义了一个 2x3 的 int 类型二维数组 "twoD",并使用两层循环将其初始化为两个下标相加的值
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
切片
Go语言中的切片是一种可变长度的数组。它由三个部分组成:指针、长度和容量。指针指向底层数组中的第一个元素,长度表示切片中的元素数量,容量表示底层数组中从第一个元素开始,切片可以使用的元素数量。切片可以使用内置函数make()创建,也可以使用数组或其它切片的子序列来创建。 例如:
package main
import "fmt"
func main() {
// 使用内置函数make()创建切片
s := make([]int, 3, 5)
fmt.Println(s)
// 输出:[0 0 0]
// 使用数组创建切片
a := [5]int{1, 2, 3, 4, 5}
s2 := a[1:4]
fmt.Println(s2)
// 输出:[2 3 4]
// 使用切片创建切片
s3 := s2[1:3]
fmt.Println(s3)
// 输出:[3 4]
}
当切片的长度超过容量时,Go语言会自动创建一个新的底层数组并将原数据拷贝过去。因此,如果需要频繁地进行扩容操作,建议使用内置函数append()来增加切片的元素。
切片还支持遍历操作,可以使用for-range循环来遍历切片中的元素。
示例:
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
// 遍历切片中的元素
for i, v := range s {
fmt.Println(i, v)
}
}
切片也可以作为函数的参数传递,在函数内部可以对切片进行修改操作。但是需要注意的是,如果在函数内部创建了一个新的底层数组,修改的将不会影响原切片。
示例:
package main
import "fmt"
func modify(s []int) {
s[0] = 100
}
func main() {
s := []int{1, 2, 3, 4, 5}
modify(s)
fmt.Println(s)
// 输出:[100 2 3 4 5]
}
具体代码解释见注释:
package main
import "fmt"
func main() {
// 使用了 Go 语言的内置函数 make() 创建了一个字符串类型的切片 s ,长度为 3
s := make([]string, 3)
// 使用了 s[0] = "a",s[1] = "b",s[2] = "c" 分别为切片赋值
s[0] = "a"
s[1] = "b"
s[2] = "c"
// 使用了 fmt.Println("get:", s[2]) 来获取并打印切片中第 2 个元素的值,即 "c"
fmt.Println("get:", s[2]) // c
// 使用 fmt.Println("len:", len(s)) 来获取并打印切片的长度,即 3
fmt.Println("len:", len(s)) // 3
// 使用了 append() 函数来在切片中添加元素
// 首先用 s = append(s, "d") 在切片末尾添加了一个 "d" 的元素
s = append(s, "d")
// 再用 s = append(s, "e", "f") 在切片末尾添加了 "e" 和 "f" 两个元素
s = append(s, "e", "f")
// 之后使用了 fmt.Println(s) 来打印切片中所有元素,结果为 [a b c d e f]
fmt.Println(s) // [a b c d e f]
// 接着使用了 c := make([]string, len(s)) 和 copy(c, s) 来创建一个新的切片 c,并将 s 中的所有元素复制到 c 中
c := make([]string, len(s))
copy(c, s)
// 之后使用了 fmt.Println(c) 来打印 c 中所有元素,结果为 [a b c d e f]
fmt.Println(c) // [a b c d e f]
// 接着使用了切片的切片功能,使用了 fmt.Println(s[2:5]),fmt.Println(s[:5]) 和 `fmt.Println(s[2:]) 来分别打印出 s 切片中第 2 个元素到第 5 个元素,第 1 个元素到第 5 个元素,和第 2 个元素到最后一个元素。
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
// 最后,在代码中创建了一个名为 good 的字符串切片并赋值为 ["g", "o", "o", "d"],然后使用 fmt.Println(good) 打印出该切片
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
map
语言中的Map是一种键值对存储结构,它使用哈希表实现。Map中的键和值可以是任何类型,但键必须是可比较的类型。Map是引用类型,使用make()函数创建。使用[]运算符读写Map中的值。
示例:
package main
import "fmt"
func main() {
// 创建一个空Map
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println(m) // 输出 map[a:1 b:2]
fmt.Println(m["a"]) // 输出1
}
使用map[key] = value赋值,使用delete(map, key)删除一个键值对,使用value, ok := map[key]读取值并判断键是否存在。
Map在迭代时顺序不确定,如果需要有序迭代可以使用sort包中的sort.Strings()或sort.Ints()对Map的key进行排序,再进行迭代。
package main
import "fmt"
func main() {
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
range
Go语言中的range关键字用于迭代数组、切片、字符串、Map和通道(channel)。
在循环中使用range关键字,会返回两个值:当前元素的下标和值。
示例:
package main
import "fmt"
func main() {
// 迭代数组
arr := [3]int{1, 2, 3}
for i, v := range arr {
fmt.Println(i, v)
}
// 输出
// 0 1
// 1 2
// 2 3
// 迭代切片
s := []int{4, 5, 6}
for i, v := range s {
fmt.Println(i, v)
}
// 输出
// 0 4
// 1 5
// 2 6
// 迭代字符串
str := "hello"
for i, v := range str {
fmt.Println(i, string(v))
}
// 输出
// 0 h
// 1 e
// 2 l
如果只需要迭代值,可以使用_来忽略下标。在迭代Map时,顺序不确定。如果需要有序迭代可以使用sort包中的sort.Strings()或sort.Ints()对Map的key进行排序,再进行迭代。
扩展 当迭代通道时,range关键字会不断从通道中读取值,直到通道关闭。
示例:
package main
import "fmt"
func main() {
// 创建一个通道
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
}()
// 迭代通道
for v := range ch {
fmt.Println(v)
}
// 输出
// 0
// 1
// 2
}
对于通道的读取和关闭操作是互相独立的,通道可以在没有关闭的情况下读取,但是读取关闭的通道会返回零值。
使用range迭代通道时,可以在循环中使用break语句或返回语句终止循环。
函数
Go语言中的函数是由一组语句组成的可重复调用的代码块。
函数定义格式:
func function_name( [parameter list] ) [return_types] {
函数体
}
示例:
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
result := add(1, 2)
fmt.Println(result) // 输出 3
}
Go语言中函数可以有多个返回值,返回值的类型可以在函数定义时列出,也可以在函数体中使用return语句返回。
示例:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b) // 输出 world hello
}
Go语言中函数可以作为参数传递给其他函数,也可以作为返回值。
示例:
package main
import "fmt"
func add(x, y int) int {
return x + y
}
func apply(op func(int, int) int, a, b int) int {
return op(a, b)
}
func main() {
result := apply(add, 1, 2)
fmt.Println(result) // 输出 3
}
Go语言中的函数还可以是闭包,闭包是一个可以访问其他变量的函数。
示例:
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
add := adder()
fmt.Println(add(1)) // 输出 1
fmt.Println(add(2)) // 输出 3
}
Go语言中的函数还可以支持可变数量的参数, 使用...表示。
示例:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
result := sum(1, 2, 3, 4)
fmt.Println(result) // 输出 10
}
在这个示例中,函数sum可以接受任意数量的int参数,并返回它们的和。可变数量的参数可以在调用时传递一个slice的形式,或者展开一个slice,例如sum(nums...)或者sum(slice...)。
另外,Go语言中的函数也可以支持命名返回值,这样在函数内部就可以直接使用返回值变量,而不用在每次返回时都显式地指定它们。
示例:
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
result := split(17)
fmt.Println(result) // 输出 7 10
}
在这个示例中,函数split在定义时声明了两个命名返回值x和y,在函数内部直接使用这两个变量,并在最后直接使用return语句返回。
指针
Go语言中的指针是一种指向内存地址的变量,它允许程序通过指针访问内存中的值。
声明指针变量时,在变量类型前面加上*号,如:var p *int。可以使用&符号取得变量的内存地址,如:p := &x。可以使用*符号访问指针变量所指向的内存地址的值,如:fmt.Println(*p)。
示例:
package main
import "fmt"
func main() {
x := 1
p := &x
fmt.Println(x) // 输出 1
fmt.Println(*p) // 输出 1
*p = 2
fmt.Println(x) // 输出 2
}
在这个示例中,变量x的值是1,我们用&符号取出x的地址赋值给p, p就是一个指向x地址的指针。然后我们用p来访问这个地址上的值,也就是x的值,用p=2来改变x的值。
指针也可以指向结构体、数组、切片等类型的变量。指针类型的变量可以进行相关的运算,如指针之间的比较和加减。
示例:
package main
import "fmt"
type Point struct {
x int
y int
}
func main() {
point := Point{1, 2}
p := &point
p.x = 3
fmt.Println(point) // 输出 {3, 2}
}
在这个示例中,我们创建了一个Point类型的变量point,然后用&取出这个变量的地址,赋值给p,这样p就成了一个指向Point类型变量的指针。我们可以使用p.x来访问point.x的值,或者p.x = 3来修改point.x的值。
Tips需要注意的是,Go语言中的指针是按值传递的,在函数中修改指针变量的值并不会影响到原始变量。如果要在函数中修改原始变量的值,需要传递指针的指针。
示例:
package main
import "fmt"
func add(x *int) {
*x += 1
}
func main() {
x := 1
add(&x)
fmt.Println(x) // 输出 2
}
在上面的例子中,我们定义了一个add函数,它接受一个指向int类型的指针变量x。在函数内部,我们使用 *x += 1来修改x所指向的内存地址上的值,也就是增加1。在main函数中,我们定义了一个x变量并赋值为1,然后使用 &x 传递给add函数,这样x的值就会在add函数中被修改为2。
结构体&结构体方法
Go语言中的结构体(struct)是一种自定义类型,它可以用来表示一组数据的集合。结构体是一种非常强大的类型,它可以用来表示各种各样的数据结构,如游戏中的角色、电商网站的商品信息等。
结构体的定义格式如下:
type struct_name struct {
field1 type1
field2 type2
...
}
示例:
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
p := Person{"Tom", 18}
fmt.Println(p.name) // 输出 Tom
fmt.Println(p.age) // 输出 18
}
在这个示例中,我们定义了一个名为Person的结构体,它有两个字段name和age。我们可以使用点语法来访问结构体的字段,例如p.name和p.age。
还可以使用结构体字面量的形式来初始化结构体变量,如:p := Person{name: "Tom", age: 18}。
结构体变量也可以通过指针来访问,如:p := &Person{name: "Tom", age: 18}。
结构体还可以定义方法,方法是一种特殊类型的函数,它可以直接访问结构体的字段。
示例:
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) sayHello() {
fmt.Printf("Hello, my name is %s and I am %d years old\n", p.name, p.age)
}
func main() {
p := Person{name: "Tom", age: 18}
p.sayHello()
}
在这个示例中,我们定义了一个名为sayHello的方法,它是Person结构体的方法。方法的声明格式是func (p Person) sayHello(),其中p是方法的接收者,它是一个Person类型的变量,可以直接访问结构体的字段。
结构体还可以定义构造函数来创建结构体变量。构造函数是一种特殊的函数,它被调用来初始化一个新的结构体变量。
示例:
package main
import "fmt"
type Person struct {
name string
age int
}
func newPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
func main() {
p := newPerson("Tom", 18)
fmt.Println(p)
}
在这个示例中,我们定义了一个名为newPerson的构造函数,它接受两个参数name和age,并返回一个指向Person类型的指针。在main函数中,我们使用newPerson函数来创建一个新的Person变量。
结构体还可以通过组合来继承和重用其他结构体的字段和方法。
示例:
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) sayHello() {
fmt.Printf("Hello, my name is %s and I am %d years old\n", p.name, p.age)
}
type Student struct {
Person
school string
}
func main() {
s := Student{Person{"Tom", 18}, "University"}
s.sayHello()
fmt.Println(s.school)
}
在这个示例中,我们定义了一个名为Student的结构体,它组合了Person结构体,继承了它的字段和方法。
错误处理
Go语言中的错误处理主要是通过返回错误值来实现的。在Go中,错误是一种具体的值,它可以通过返回值的方式来表示。
在Go中,错误是一种内置类型,它实现了error接口。error接口只有一个方法Error() string,返回错误信息。
示例:
package main
import (
"errors"
"fmt"
)
func div(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
在这个示例中,我们定义了一个名为div的函数,用于计算两个整数的商。如果除数为0,div函数会返回一个错误值,否则返回商和nil。在main函数中,我们调用div函数并处理错误。还可以使用Go的内置的errors.New()来创建一个error类型的值,这是一种常用的错误处理方式
Go还提供了另一种错误处理方式,即 panic-recover机制。 panic是一个内建函数,可以中断原有的控制流,而recover是一个内建函数,可以获取 panic 的值并继续执行。
示例:
package main
import (
"fmt"
)
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recover from panic:", err)
}
}()
panic("Something went wrong")
fmt.Println("This line will not be executed")
}
在这个示例中,我们使用了 defer 和 recover 两个内建函数来处理 panic。 在main函数中,我们使用了 panic("Something went wrong") 来引发一个异常,但是我们使用了 defer 函数来捕获这个异常并输出错误信息。
需要注意的是,使用 panic-recover 机制需要小心,过度使用它可能会导致程序难以维护。 通常在 Go 中,返回 error 会更加常见,只在遇到不可恢复的错误时使用 panic 。
关于这部分内容推荐文章进行补充学习:juejin.cn/post/713123…
字符串操作
Go语言中的字符串是一种不可变的数据类型,它可以用来表示一组字符序列。
Go语言中提供了很多内置的函数和类型来操作字符串。
- 字符串拼接: 可以使用+号进行字符串拼接
package main
import "fmt"
func main() {
s1 := "hello"
s2 := "world"
s3 := s1 + " " + s2
fmt.Println(s3) // "hello world"
}
- 字符串长度: 可以使用len函数来获取字符串的长度
package main
import "fmt"
func main() {
s := "hello"
fmt.Println(len(s)) // 5
}
- 字符串比较: 可以使用==和!=进行字符串比较
package main
import "fmt"
func main() {
s1 := "hello"
s2 := "world"
s3 := "hello"
fmt.Println(s1 == s2) // false
fmt.Println(s1 == s3) // true
}
- 字符串访问: 可以使用[]运算符来访问字符串中的某个字符
package main
import "fmt"
func main() {
s := "hello"
fmt.Println(s[0]) // 'h'
fmt.Println(s[4]) // 'o'
}
- 字符串切片: 可以使用s[start:end]来获取字符串的子串
package main
import "fmt"
func main() {
s := "hello world"
fmt.Println(s[0:5]) // "hello"
fmt.Println(s[6:11]) // "world"
}
- 字符串判断: 可以使用strings包中的函数如Contains,HasPrefix,HasSuffix来判断字符串是否包含某个子串或前缀/后缀.
- 字符串修改: 可以使用strings包中的函数如Replace,Trim,TrimLeft,TrimRight来修改字符串
- 字符串转换: 可以使用strconv包中的函数如Itoa,Atoi来转换字符串与整型变量。
字符串格式化
Go语言中提供了格式化字符串的内置函数,主要包括fmt包中的Printf和Sprintf函数。
- Printf函数: 将格式化后的字符串输出到标准输出(stdout)
package main
import "fmt"
func main() {
name := "Tom"
age := 18
fmt.Printf("My name is %s and I am %d years old\n", name, age)
// Output: My name is Tom and I am 18 years old
}
- Sprintf函数: 将格式化后的字符串返回
package main
import "fmt"
func main() {
name := "Tom"
age := 18
s := fmt.Sprintf("My name is %s and I am %d years old", name, age)
fmt.Println(s)
// Output: My name is Tom and I am 18 years old
}
这两个函数的使用方法基本相同,都需要指定一个格式化字符串和对应的参数。格式化字符串中的占位符使用%d, %s, %f等来表示,%d用于整型,%s用于字符串,%f用于浮点型。
除了这些常用的格式化字符外,还有很多其他的格式化字符可供使用,如%v用于任意类型,%b用于二进制,%x用于十六进制等,这里不一一展示。
JSON处理
Go语言中有许多第三方库可用来处理JSON格式的数据,其中最流行和官方推荐的是"encoding/json"包。
这个包提供了Marshal和Unmarshal函数来进行JSON序列化和反序列化。
示例:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
p := Person{Name: "Tom", Age: 18}
b, _ := json.Marshal(p)
fmt.Println(string(b))
// Output: {"name":"Tom","age":18}
var p2 Person
json.Unmarshal(b, &p2)
fmt.Println(p2)
// Output: {Tom 18}
}
在这个示例中,我们定义了一个结构体Person,并使用json.Marshal函数将其序列化为JSON格式的字符串。 然后使用json.Unmarshal函数将JSON格式的字符串反序列化为Person结构体。
我们还可以使用 json.NewEncoder 和 json.NewDecoder 来进行编码和解码,这在处理流式数据时会更加方便。
在这里需要注意的是,由于Go语言中的字段名默认是大写字母开头,所以在使用json包时需要使用tag来指定对应的JSON字段名,或者使用小写字母开头的字段名。
时间处理
Go语言中有一个"time"包来处理时间相关的操作。
这个包提供了很多函数和类型来处理时间,主要包括:
- time.Now: 获取当前时间
- time.Time: 时间类型
- time.Duration: 时间间隔
- time.Parse: 解析时间字符串
- time.Format: 格式化时间
示例:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
// Output: 2021-05-12 16:09:51.81735 +0800 CST
// 时间转字符串
fmt.Println(now.Format("2006-01-02 15:04:05"))
// Output: 2021-05-12 16:09:51
// 字符串转时间
t, _ := time.Parse("2006-01-02 15:04:05", "2021-05-12 16:09:51")
fmt.Println(t)
// Output: 2021-05-12 16:09:51 +0000 UTC
// 时间间隔
later := now.Add(time.Hour)
fmt.Println(later.Sub(now))
// Output: 1h0m0s
}
这些函数和类型只是Go中时间操作的一部分,还有很多其他的函数和方法可供使用。
需要注意的是,在处理时区时,Go中的时间类型默认是UTC时间,需要使用time.FixedZone("", offset)或time.Local来明确时区。
数字解析
Go语言中有一个"strconv"包来处理数字解析。
这个包提供了很多函数来将字符串转换为数字类型,主要包括:
- strconv.ParseBool: 解析布尔值
- strconv.ParseFloat: 解析浮点数
- strconv.ParseInt: 解析整数
- strconv.ParseUint: 解析无符号整数
示例:
package main
import (
"fmt"
"strconv"
)
func main() {
s := "true"
b, _ := strconv.ParseBool(s)
fmt.Println(b) // true
s = "3.14"
f, _ := strconv.ParseFloat(s, 64)
fmt.Println(f) // 3.14
s = "100"
i, _ := strconv.ParseInt(s, 10, 64)
fmt.Println(i) // 100
s = "200"
u, _ := strconv.ParseUint(s, 10, 64)
fmt.Println(u) // 200
}
需要注意的是,这些函数会返回两个值,第一个是解析后的数字类型,第二个是错误信息,在使用时需要判断是否有错误发生。
还可以使用strconv.Format函数来将数字类型转换为字符串,这样可以方便地进行数字与字符串之间的转换。
这些函数和类型只是Go中数字解析的一部分,还有很多其他的函数和方法可供使用。
strconv包提供了很多函数来帮助我们解析数字,这些函数都是非常方便和实用的,在实际项目中会经常用到。
另外 strconv 包还提供了一些其他的函数来处理数字,比如:
- strconv.Itoa: 将整数转为字符串
- strconv.Atoi: 将字符串转为整数
示例:
package main
import (
"fmt"
"strconv"
)
func main() {
i := 100
s := strconv.Itoa(i)
fmt.Println(s) // "100"
s = "200"
i, _ = strconv.Atoi(s)
fmt.Println(i) // 200
}
进程信息
Go语言中有一个"os"包来获取进程信息。
这个包提供了很多函数来获取进程相关信息,主要包括:
- os.Getpid: 获取当前进程的PID
- os.Getppid: 获取当前进程的父进程PID
- os.Getuid: 获取当前进程的用户ID
- os.Getgid: 获取当前进程的组ID
- os.Getgroups: 获取当前进程所属的所有组ID
示例:
package main
import (
"fmt"
"os"
)
func main() {
pid := os.Getpid()
fmt.Println("PID:", pid)
ppid := os.Getppid()
fmt.Println("PPID:", ppid)
uid := os.Getuid()
fmt.Println("UID:", uid)
gid := os.Getgid()
fmt.Println("GID:", gid)
groups, _ := os.Getgroups()
fmt.Println("Groups:", groups)
}
这些函数可以帮助我们获取当前进程的PID,父进程PID,用户ID,组ID,以及所属的所有组信息。
需要注意的是,这些函数只是获取进程信息的一部分,还有很多其他的函数和方法可供使用。
其中os包提供了os.StartProcess函数来启动一个新的进程,os.Exit函数来终止当前进程。
示例:
package main
import (
"fmt"
"os"
)
func main() {
// 启动新进程
cmd := "ls"
env := os.Environ()
procAttr := &os.ProcAttr{Env: env}
proc, _ := os.StartProcess(cmd, []string{cmd}, procAttr)
fmt.Println(proc)
// 退出进程
os.Exit(1)
}
在实际项目中,我们可以通过os包来获取进程相关的信息,以及控制和管理进程。
具体示例(码上掘金)
总结
一些话
本文介绍了Go语言的背景、特性、应用,介绍了Go环境的搭建,列举了Go的一些基础语法和一些简单示例,希望对你有所帮助。
本文内容较多,难免会有错误或者不足之处,如果你对本文有任何的意见和建议,欢迎在评论区和私信和我交流,我看到后会及时进行回复。
相关链接
Go包检索:pkg.go.dev/
维基百科页面:zh.m.wikipedia.org/wiki/Go
Go和错误处理:juejin.cn/post/713123…
Go手动内存管理:打脸了兄弟们,Go1.20 arena 来了! - 掘金 (juejin.cn)
最后
创作不易,如果你觉得本文对你有帮助,可以点赞、评论、收藏,你的支持是我最大的动力,下次更新会更快!