Go基础语言
什么是Go语言?
Go语言是一种开源编程语言,由Google开发。它具有以下特点:
- 简洁易学:Go语言的语法简洁、清晰,易于学习和理解,减少了代码的复杂性。
- 高性能:Go语言采用了垃圾回收机制,可以自动管理内存,提高了程序执行效率。同时,Go语言通过并行计算和协程(goroutine)的概念,支持高并发操作。
- 静态类型:Go语言是一种静态类型的编程语言,可以在编译时检查代码的类型安全性,减少了运行时错误。
- 跨平台:Go语言的编译器可以将Go代码编译为机器码,从而可以在不同的操作系统和硬件平台上运行。
- 丰富的标准库:Go语言拥有丰富的标准库,包含了各种功能模块,例如网络、文件操作、并发等,开发者可以直接使用这些库来加速开发。
- 开发效率高:Go语言具有良好的工具链,包括代码格式化工具、自动化构建工具等,可以提高开发效率。
1)搭建开发环境
1)安装Golang:下载并安装Go语言的最新版本。
2)选择一个适合的开发环境,例如Visual Studio Code``(VSCode)或者Goland,也可以使用云开发环境如Gitpod。
2)基础语法
2.1 Hello World
package main // 声明主程序包
import ( // 导入所需的包
"fmt"
)
func main() { // 声明主函数
fmt.Println("Hello World") // 输出Hello World
}
其中fmt是一个标准库包,全称为"format",它提供了格式化输入和输出的功能。
fmt包中的函数有:
- 打印输出:
Print、Printf、Println等。 - 格式化字符串:
Sprintf、Fprintf等。 - 读取输入:
Scan、Scanf、Scanln等。
2.2 变量
Go语言是一门强类型语言,每个变量都有自己的变量类型。常见的变量类型有字符串型、整数型、浮点型和布尔型,类似于C++。
var a = "initial" // 声明变量并赋初值
var b, c int = 1, 2 // 声明多个变量并赋值
var d = true // 声明变量并赋值
var e float64 // 声明变量,未赋值
f := float32(e) // 使用短变量声明方式赋值,可以自动推导变量类型
g := a + "foo" // 可以进行字符串的拼接操作
2.3 选择、循环语句
在Go语言中,我们可以使用选择语句(if-else和switch)和循环语句(for)来控制程序的流程。
if-else语句是用于在不同条件下执行不同代码块的选择结构。
for循环是用于重复执行一段代码块的循环结构。有多种形式的for循环可以选择,包括无限循环、条件循环和计数循环。
下面是示例:
if num := 9; num < 0 { // if-else语句
fmt.Println(num, "is negative") // 输出结果
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
for { // 无限循环
fmt.Println("loop") // 输出结果
break // 跳出循环
}
for j := 7; j < 9; j++ { // for循环
fmt.Println(j) // 输出结果
}
switch { // switch语句,后面可不加任何变量
case t.Hour() < 12: // 判断时间的小时部分是否小于12
fmt.Println("It's before noon") // 输出结果
default:
fmt.Println("It's after noon")
}
在这段示例代码中,展示了在Go语言中使用选择语句(if-else和switch)和循环语句(for)的用法。
首先是if-else语句,通过判断条件是否满足来执行不同的代码块。在示例中,使用if-else语句判断了一个变量num的值,并根据不同的情况打印输出不同的结果。
其次是for循环,用于重复执行一段代码块。示例中展示了两种常见的for循环形式。第一个是无限循环,可以使用for关键字后跟空的条件表达式来创建一个无限循环,在循环体内部使用break语句来跳出循环。第二个是条件循环,使用for关键字后跟条件表达式作为循环的终止条件,然后使用++运算符对循环变量进行递增操作。循环体内部输出了循环变量的值。
最后是switch语句,用于基于不同的情况执行不同的代码块。示例中使用了switch语句来判断当前时间的小时部分是否小于12,如果满足条件,就输出"It's before noon",否则输出"It's after noon"。
2.4 数组、切片、映射和范围遍历
Go语言中的数组、切片、映射和范围遍历类似于Python语言。
var a [5]int // 声明一个一维数组
a[4] = 100 // 给数组的第五个元素赋值为100
fmt.Println(a[4], len(a)) // 输出数组的第五个元素和数组的长度
var twoD [2][3]int // 声明一个二维数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j // 对数组的每个元素赋值为行索引加列索引的和
}
}
s := make([]string, 3) // 声明一个长度为3的切片
s[0] = "a" // 给切片的第一个元素赋值为 "a"
s[1] = "b" // 给切片的第二个元素赋值为 "b"
s[2] = "c" // 给切片的第三个元素赋值为 "c"
fmt.Println(s[2:5]) // 输出切片从索引2到索引4的元素(不包括索引5)
fmt.Println(s[:5]) // 输出切片从索引0到索引4的元素
fmt.Println(s[2:]) // 输出切片从索引2到末尾的所有元素
m := make(map[string]int) // 声明一个映射,键类型为字符串,值类型为整数
m["one"] = 1 // 向映射中添加键值对 "one": 1
m["two"] = 2 // 向映射中添加键值对 "two": 2
fmt.Println(m) // 输出整个映射
nums := []int{2, 3, 4} // 声明一个切片并初始化
sum := 0
for i, num := range nums { // 使用范围遍历切片,i为索引,num为值
sum += num
if num == 2 {
fmt.Println("index", i, "num", num) // 输出值为2的元素的索引和值
}
}
fmt.Println(sum) // 输出切片中所有元素的和
上述示例展示了Go语言中数组、切片、映射和范围遍历的用法。
- 数组是一组具有相同类型的元素的集合,其长度是固定的。可以通过索引访问数组中的元素。
- 切片是一个动态大小的容器,可以根据需要增加或减少大小。切片由指向底层数组的指针、长度和容量组成。
- 映射是无序的键值对集合,通过键来访问值。键必须是唯一的,而值可以重复。
- 范围遍历是一种方便的遍历切片、数组、字符串、映射等集合类型的方法。在每次迭代中,使用两个变量分别表示索引和值。
请注意,数组和切片的索引从0开始,长度等于元素的个数。切片可以使用切片表达式进行截取,以获取子切片。
2.5 函数
func exists(m map[string]string, k string) (v string, ok bool) {
// 通过键k在map m中查找对应的值v和是否存在的布尔值ok
v, ok = m[k]
// 返回值v和ok
return v, ok
}
这里的函数接受两个参数:一个是类型为map[string]string的变量m,另一个是类型为string的变量k。返回值有两个:一个是类型为string的变量v,用来存储键对应的值;另一个是类型为bool的变量ok,用来表示键是否存在。
在函数内部,使用m[k]来查找给定键k在map中对应的值。如果找到了值,将其赋给v并将ok设为true,表示键存在。如果未能找到值,则v将为空字符串且ok设为false,表示键不存在。
最后,通过return语句返回v和ok。
该函数可以用于判断某个键是否存在于给定的map中,并获取其对应的值。可以根据实际需求进行调用和使用。
2.6 指针
// add2ptr 函数用于将指定的整数指针加上2。
func add2ptr(n *int) {
// 将指针所指向的整数值加上2
*n += 2
}
这段代码定义了一个函数add2ptr,它接受一个*int类型的指针参数n。函数的作用是将n指针所指向的变量的值加上2。
在函数内部,通过在变量前面加上*来访问指针所指向的变量,然后对其进行修改。
2.7 结构体与结构方法
// user 结构体表示用户信息。
type user struct {
name string
password string
}
// resetPassword 方法用于重置用户的密码。
// - password: 新的密码字符串
func (u *user) resetPassword(password string) {
// 将用户的密码字段更新为新的密码
u.password = password
}
这段代码定义了一个名为user的结构体,它有两个字段:name和password,类型都是string。
接下来定义了一个结构方法resetPassword,它接收一个指向user类型的指针作为接收者,同时还接收一个password参数。
该结构方法的作用是将user类型的指针所指向的对象的password字段设置为传入的password值。
2.8 错误处理
在Go语言中,通常使用单独的返回值来传递错误信息。利用简单的 if-else 语句进行错误处理。
func findUser(users []user, name string) (v *user, err error) {
// 遍历用户列表
for _, u := range users {
// 如果用户名匹配,则返回该用户的指针和nil错误
if u.name == name {
return &u, nil
}
}
// 如果未找到匹配的用户,则返回nil指针和自定义的错误信息
return nil, errors.New("not found")
}
// 创建一个包含单个用户的用户列表,并查找指定用户名的用户
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
// 如果发生错误,打印错误信息并返回
fmt.Println(err)
return
}
// 打印找到的用户的用户名
fmt.Println(u.name)
在这段代码中,首先调用函数findUser来查找用户信息。函数接收一个[]user类型的切片参数users和一个string类型的参数name。函数返回两个值,第一个是指向user类型的指针变量v,第二个是error类型的变量err。
函数内部的逻辑是遍历users切片,如果找到与name匹配的用户,则返回指向该用户的指针和nil错误。如果没有找到匹配的用户,则返回nil指针和一个自定义的错误信息。
在调用函数时,使用:=语法同时接收函数返回的两个值。然后通过判断err是否为nil来检查是否发生了错误,并进行相应的处理。
2.9 字符串操作与格式化
Go语言的strings包提供了许多方便的字符串处理函数。字符串格式化可以使用类似C语言中的printf()函数的方式来格式化字符串。其中,%v可以打印任意类型的变量,%+v会打印更详细的结果,%#v会打印最详细的结果。
a := "hello"
// 判断字符串a是否包含子串"li"
fmt.Println(strings.Contains(a, "li"))
// 计算字符串a中子串"l"出现的次数
fmt.Println(strings.Count(a, "l"))
// 判断字符串a是否以"he"开头
fmt.Println(strings.HasPrefix(a, "he"))
// 判断字符串a是否以"llo"结尾
fmt.Println(strings.HasSuffix(a, "llo"))
// 在字符串a中查找子串"li"的索引位置,不存在则返回-1
fmt.Println(strings.Index(a, "li"))
// 使用连接符将字符串切片{"he", "llo"}连接成一个字符串
fmt.Println(strings.Join([]string{"he", "llo"}, "-"))
// 将字符串a重复2次
fmt.Println(strings.Repeat(a, 2))
// 替换字符串a中的子串"e"为"E",-1表示全部替换
fmt.Println(strings.Replace(a, "e", "E", -1))
// 将字符串"a-b-c"按照"-"进行分割,并返回字符串切片
fmt.Println(strings.Split("a-b-c", "-"))
// 将字符串a转换为小写
fmt.Println(strings.ToLower(a))
// 将字符串a转换为大写
fmt.Println(strings.ToUpper(a))
// 获取字符串a的长度
fmt.Println(len(a))
s := "string"
n := 10
p := &s
// 格式化输出,%v表示默认格式
fmt.Printf("s=%v\n", s)
// 格式化输出,%v表示默认格式
fmt.Printf("n=%v\n", n)
// 格式化输出指针的值
fmt.Printf("p=%v\n", p)
// 格式化输出指针的值,并包含类型信息
fmt.Printf("p=%+v\n", p)
// 格式化输出指针的值,并包含Go语法表示
fmt.Printf("p=%#v\n", p)
f := 3.141592653
// 打印浮点数f,不指定精度,默认保留小数点后6位
fmt.Println(f)
// 指定浮点数f的精度为2位
fmt.Printf("%.2f\n", f)
在这段代码中,通过调用 strings 包中的函数,对字符串 a 进行了一系列操作,如检查是否包含子串、统计子串出现的次数、判断是否有指定的前缀和后缀、查找子串在字符串中的位置、用指定连接符连接字符串片段、重复字符串、替换子串等。另外,还使用 %v、%+v、%#v 等格式化占位符打印了不同类型的变量,这些函数和格式化占位符在字符串处理和格式化输出方面非常有用,可以根据实际需求来使用它们。
2.10 JSON处理
在Go语言中,如果一个结构体的字段首字母大写,就可以使用 json.Marshal() 方法将其序列化为 JSON 字符串,同时也可以使用 json.Unmarshal() 方法将 JSON 字符串反序列化为对象。
// 定义 userInfo 结构体
type userInfo struct {
Name string `json:"name"` // 用户名
Age int `json:"age"` // 年龄
Hobby []string `json:"hobby"` // 爱好
}
// 创建 userInfo 对象 a
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
// 将 userInfo 对象转换为 JSON 字节切片
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // 打印 JSON 字节切片
fmt.Println(string(buf)) // 打印 JSON 字符串
// 使用 json.MarshalIndent 将 userInfo 对象转换为带缩进的 JSON 字符串
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 打印带缩进的 JSON 字符串
// 将 JSON 字符串解析成 userInfo 对象 b
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // 打印解析后的 userInfo 对象
这段代码演示了在Go语言中如何处理JSON数据。我们定义了一个userInfo结构体,其中包含三个字段:Name(用户名)、Age(年龄)和Hobby(爱好)。注意到这些字段的后面都有一个json标签,用于指定字段在JSON中的名称。创建了一个userInfo对象a,并给它的字段赋值。接下来,使用json.Marshal()将a对象序列化为JSON字节切片,并使用fmt.Println()打印结果。json.Marshal()函数将对象转换为JSON格式,使其可以在网络传输或存储中使用。
为了更好地展示JSON字符串,我们使用string()函数将JSON字节切片转换为字符串,并再次打印结果。
之后我们使用json.MarshalIndent()对a对象进行格式化的序列化,第二个参数为空字符串表示不添加额外的缩进,第三个参数为制表符\t,表示使用制表符缩进。然后,使用fmt.Println()打印带缩进的JSON字符串。我们使用json.Unmarshal()将JSON字符串反序列化为userInfo对象b。通过将JSON字符串作为输入,并提供一个指向b的指针,json.Unmarshal()将JSON数据解析并填充到b对象的相应字段中。然后,使用fmt.Printf()以更详细的方式打印b对象的内容。
这些函数和方法在处理JSON数据时非常有用,可以方便地进行序列化和反序列化操作。
2.11 时间处理与数字解析
在Go语言中,可以使用 time.Now() 获取当前时间,也可以使用 time.Date() 构造带有时区信息的时间对象。可以使用 Sub() 方法计算两个时间点之间的时间差,使用 Unix() 方法获取时间戳。
数字解析可以使用 strconv 包下的函数,将字符串转换为数字。
now := time.Now()
fmt.Println(now) // 打印当前时间
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t1 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 打印指定时间 t
fmt.Println(t.Year(), t.Month(), t, t.Day(), t.Hour(), t.Minute()) // 打印 t 的年月日时分
fmt.Println(t.Format("2006-01-02 15:04:05")) // 格式化打印 t
diff := t1.Sub(t) // 计算两个时间之间的差值
fmt.Println(diff) // 打印差值
fmt.Println(diff.Minutes(), diff.Seconds()) // 打印差值的分钟数和秒数
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36") // 解析时间字符串为时间对象
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // 判断解析后的时间对象是否等于 t
fmt.Println(now.Unix()) // 获取当前时间的 Unix 时间戳
f, _ := strconv.ParseFloat("1.234", 64) // 将字符串转换为浮点数
fmt.Println(f)
n, _ := strconv.ParseInt("111", 10, 64) // 将字符串转换为整数
fmt.Println(n)
n, _ = strconv.ParseInt("0x1000", 0, 64) // 支持十六进制字符串转换为整数
fmt.Println(n)
n2, _ := strconv.Atoi("123") // 将字符串转换为整数(常用于字符串表示的十进制数)
fmt.Println(n2)
这段代码演示了在Go语言中处理时间和数字解析的方法。通过调用time.Now()函数获取当前时间,并使用fmt.Println()打印输出结果。使用time.Date()函数构造了两个具有指定日期、时间和时区的时间对象t和t1。你可以传入年、月、日、时、分、秒、纳秒和时区信息来创建一个时间对象。使用fmt.Println()打印出t对象的各个字段值。使用t.Format()将时间对象t按照指定的格式("2006-01-02 15:04:05")进行格式化,并使用fmt.Println()打印结果。计算了t1和t之间的时间差,并将结果存储在变量diff中。使用fmt.Println()打印时间差值,以及将时间差转换为分钟数和秒数并打印输出。
使用time.Parse()函数将字符串"2022-03-27 01:25:36"按照指定的格式解析为时间对象t3,并与时间对象t进行比较。注意到解析时间字符串时,格式字符串中的年、月、日、时、分、秒必须和实际的字符串完全匹配,且在格式字符串中使用的占位符必须是固定的,如"2006"表示年份,"01"表示月份等。如果解析失败,会返回一个错误。使用now.Unix()获取当前时间的Unix时间戳,并使用fmt.Println()打印输出结果。
最后,使用strconv包下的函数将字符串"1.234"解析为浮点数并打印输出,将字符串"111"解析为十进制整数并打印输出,将字符串"0x1000"解析为十六进制整数并打印输出,以及使用strconv.Atoi()将字符串"123"解析为十进制整数并打印输出。
2.12 进程信息
在Go语言中,可以使用 os.Args 获取程序执行时指定的命令行参数。
fmt.Println(os.Args) // 打印命令行参数
fmt.Println(os.Getenv("PATH")) // 获取环境变量 PATH 的值并打印
fmt.Println(os.Setenv("AA", "BB")) // 设置环境变量 AA 的值为 BB,并打印结果(设置环境变量成功返回 nil)
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").Output() // 执行命令 grep,查找 /etc/hosts 文件中包含 "127.0.0.1" 的行
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 将执行结果转换为字符串并打印
这段代码使用 os.Args 获取程序执行时指定的命令行参数,并打印输出。
使用 os.Getenv() 获取指定环境变量的值,并打印输出。然后,使用 os.Setenv() 设置一个环境变量的值。
使用 exec.Command() 创建一个执行外部命令的命令对象,并调用 Output() 方法执行该命令并获取输出结果。最后,将输出结果转为字符串并打印输出。
这段代码中执行 grep 命令需要依赖于操作系统的支持,且可能会因为安全限制而无法正常执行。
总结
通过整理和梳理,完成了本文,作为Go语言的基础语法和常用特性实践总结。主要内容包括搭建开发环境、Hello World程序、变量声明和赋值、选择和循环语句、数组、切片、映射和范围遍历、函数、指针、结构体与结构方法、错误处理、字符串操作与格式化、JSON处理、时间处理与数字解析以及进程信息的获取等。代码示例中的一些参数和路径可能是虚拟的,请根据实际情况进行调整和使用。同时,在实际开发中,还需注意错误处理、异常情况的处理和安全性等问题。go语言作为一门新语言,集结了众多优秀语言的优势,如果已经熟悉Java和Python以及C/C++、Javascript其中一种或多种,对于理解掌握Go语言的语法来说可以很快上手,但不可忽视的是Go语言处理服务端的优势,如今众多大厂已经全面拥向Go语言作为后端语言,掌握Go语言也会越发重要。希望这篇文章可以帮助你再复习一遍Go语言基础知识并提升熟练度。我这里想补充一点,对于语言学习以及之后的项目开发,良好的测试覆盖率和清晰的文档是必不可少的。在实践过程中,我们可以编写各种类型的测试,包括单元测试、集成测试和端到端测试等,来确保代码的质量和正确性。而文档则可以帮助其他开发者了解项目的使用方法、接口说明等,提高协作效率。