本届青训营我写的第一篇学习笔记,也是第一篇实践笔记。
———————————————————————————————————————————
Go语言基础语法
1.hello world
package main //指定当前文件所属包名
import ( //导入包
"fmt" //fmt包提供格式化输入输出的函数
)
func main() { //入口函数
fmt.Printf("hello world") //格式化输出字符串
}
2.变量
var a = "define"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "string"
fmt.Println(a, b, c, d, e, f)
fmt.Println(g)
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
这段代码依次展示了:
定义 a 变量,并将字符串 "define" 赋值给它。根据值的类型,Go语言自动推导变量的类型为字符串。
定义两个整型变量 b 和 c,并分别将值 1 和 2 赋给它们。
定义一个布尔型变量 d,并将值 true 赋给它。根据值的类型,Go语言自动推导变量的类型为布尔型。
定义一个浮点型变量 e,但没有给它赋初值。根据Go语言的规则,未初始化的浮点型变量将被赋予零值,即 0.0。
使用短变量声明方式 := 定义一个名为 f 的 float32 类型变量,并将变量 e 的值转换为 float32 类型赋给它。
使用短变量声明方式定义一个名为 g 的变量,并将变量 a 的值与字符串 "string" 进行拼接赋给它。
使用 fmt.Println 函数将变量的值打印到标准输出。Println 函数接受多个参数,并以空格分隔它们进行打印。
定义三个常量 s、h 和 i。s 是一个字符串常量,h 是一个整型常量,i 是一个浮点型常量。在第18行中,i 的值是 3e20(科学计数法表示的数)除以 h 的结果。
使用 fmt.Println 函数打印常量 s、h、i 的值,以及 math.Sin 函数应用到 h 和 i 上的结果。math.Sin 是Go语言标准库中的一个函数,用于计算给定角度的正弦值。
3.if else
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
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")
}
展示了if else的不同用法:if else,if单独使用,else if的使用和条件的写法。
4.for循环
i := 1
for {
fmt.Println("loop")
break
}
这个for循环是一个无限循环。代码块中的语句打印输出 "loop",然后使用break语句终止循环。因此,循环只执行一次,输出 "loop"。
for j := 7; j < 9; j++ {
fmt.Println(j)
}
这个for循环使用初始化语句j := 7来初始化变量j,并设置循环条件为j < 9。循环体中的语句打印输出j的值,然后在每次迭代结束后,使用j++语句对j进行递增。循环将执行两次,打印输出数字7和8。
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
这个for循环使用初始化语句n := 0来初始化变量n,并设置循环条件为n < 5。循环体中的if语句检查n是否为偶数,如果是偶数,则使用continue语句跳过当前迭代,直接进行下一次迭代。如果n是奇数,则打印输出n的值。循环将执行3次,打印输出数字1和3。
for i <= 3 {
fmt.Println(i)
i = i + 1
}
这个for循环使用条件表达式i <= 3作为循环条件。循环体中的语句打印输出i的值,然后将i的值递增1。循环将执行4次,打印输出数字1、2、3。
5.switch
grade := "B"
switch grade {
case "A":
fmt.Println("优秀")
case "B":
fmt.Println("良好")
case "C":
fmt.Println("及格")
case "D":
fmt.Println("不及格")
default:
fmt.Println("未知等级")
}
在这个例子中,我们使用switch语句根据变量grade的值选择要执行的代码块。变量grade代表了学生的成绩等级。
switch grade表示我们要对grade进行判断。
然后,我们使用case关键字来定义每个可能的情况。如果grade的值与某个case的表达式相匹配,对应的代码块将会被执行。
- 如果
grade的值为"A",那么执行第一个case语句块,打印输出"优秀"。 - 如果
grade的值为"B",那么执行第二个case语句块,打印输出"良好"。 - 如果
grade的值为"C",那么执行第三个case语句块,打印输出"及格"。 - 如果
grade的值为"D",那么执行第四个case语句块,打印输出"不及格"。 - 如果
grade的值不匹配任何一个case的表达式,那么执行default语句块,打印输出"未知等级"。
在这个例子中,变量grade的值为"B",因此执行第二个case语句块,打印输出"良好"。
switch语句可以根据不同的条件进行选择,提供了更清晰和简洁的代码结构,避免了使用多个if-else语句的复杂性。
6.数组
// 创建一个包含5个整数的数组
var numbers [5]int
// 为数组赋值
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
// 访问数组元素并打印输出
fmt.Println(numbers[0]) // 输出: 10
fmt.Println(numbers[2]) // 输出: 30
// 修改数组元素的值
numbers[1] = 25
// 使用循环遍历数组并打印输出
for i := 0; i < len(numbers); i++ {
fmt.Println(numbers[i])
}
在这个例子中,我们创建了一个名为numbers的数组,它可以存储5个整数。
首先,我们使用索引操作符[]将值赋给数组元素。例如,numbers[0] = 10将数组的第一个元素赋值为10。
然后,我们使用索引操作符[]来访问数组元素。例如,fmt.Println(numbers[0])打印输出数组的第一个元素,即10。
我们还可以通过索引来修改数组元素的值。例如,numbers[1] = 25将数组的第二个元素修改为25。
最后,我们使用for循环和数组的长度来遍历数组,并使用索引操作符[]来访问和打印输出数组的每个元素。
7.切片
切片(Slice)是一种动态数组,它提供了对底层数组的部分或全部可见的窗口。切片相比于数组具有更灵活的长度和容量,并支持动态增长。
// 创建一个切片
numbers := []int{1, 2, 3, 4, 5}
// 打印输出切片的元素
fmt.Println(numbers) // 输出: [1 2 3 4 5]
// 访问切片的元素并打印输出
fmt.Println(numbers[0]) // 输出: 1
fmt.Println(numbers[2]) // 输出: 3
// 修改切片的元素
numbers[1] = 10
fmt.Println(numbers) // 输出: [1 10 3 4 5]
// 使用append函数向切片追加元素
numbers = append(numbers, 6, 7, 8)
fmt.Println(numbers) // 输出: [1 10 3 4 5 6 7 8]
// 创建一个长度为0的切片
var emptySlice []int
fmt.Println(emptySlice) // 输出: []
// 使用make函数创建切片
dynamicSlice := make([]int, 3, 5)
fmt.Println(dynamicSlice) // 输出: [0 0 0]
fmt.Println(len(dynamicSlice)) // 输出: 3
fmt.Println(cap(dynamicSlice)) // 输出: 5
// 通过切片表达式获取部分切片
partialSlice := numbers[1:4]
fmt.Println(partialSlice) // 输出: [10 3 4]
在这个例子中,我们首先创建了一个切片numbers,它包含了整数1到5。
我们可以像访问数组一样使用索引操作符[]来访问切片的元素。例如,fmt.Println(numbers[0])将打印输出切片的第一个元素,即1。
通过修改切片的元素,我们可以更改底层数组的值。例如,numbers[1] = 10将切片的第二个元素修改为10。
使用append函数可以向切片追加元素,实现动态增长。例如,numbers = append(numbers, 6, 7, 8)将6、7和8追加到切片的末尾。
我们还可以使用make函数创建切片,指定切片的长度和容量。例如,dynamicSlice := make([]int, 3, 5)创建了一个长度为3、容量为5的切片,初始值都为0。
切片表达式可以通过指定开始和结束索引来获取部分切片。例如,partialSlice := numbers[1:4]将返回一个包含numbers[1]、numbers[2]和numbers[3]的部分切片。
8.map
map是一种无序的键值对集合,也被称为哈希表或字典。map提供了一种快速查找和检索值的方式。
// 创建一个空的map
var colors map[string]string
fmt.Println(colors) // 输出: map[]
// 使用make函数创建一个map
ages := make(map[string]int)
fmt.Println(ages) // 输出: map[]
// 添加键值对到map中
colors["red"] = "#FF0000"
colors["green"] = "#00FF00"
colors["blue"] = "#0000FF"
fmt.Println(colors) // 输出: map[red:#FF0000 green:#00FF00 blue:#0000FF]
// 访问map中的值
fmt.Println(colors["red"]) // 输出: #FF0000
fmt.Println(colors["green"]) // 输出: #00FF00
// 修改map中的值
colors["blue"] = "#0000CC"
fmt.Println(colors) // 输出: map[red:#FF0000 green:#00FF00 blue:#0000CC]
// 删除map中的键值对
delete(colors, "green")
fmt.Println(colors) // 输出: map[red:#FF0000 blue:#0000CC]
// 使用range遍历map
for key, value := range colors {
fmt.Println(key, value)
}
在这个例子中,我们首先创建了一个空的map,使用var colors map[string]string声明了一个空的字符串类型键和字符串类型值的map。
我们也可以使用make函数创建一个map,例如ages := make(map[string]int)创建了一个键为字符串类型,值为整数类型的map。
通过在map变量后使用键来添加键值对。例如,colors["red"] = "#FF0000"将键"red"和值"#FF0000"添加到colors的map中。
使用索引操作符[]可以访问和修改map中的值。例如,colors["red"]将返回键"red"对应的值。
我们还可以使用delete函数从map中删除键值对。例如,delete(colors, "green")将从colors的map中删除键为"green"的键值对。
使用range关键字可以对map进行迭代。在循环中,range会按照无序的方式迭代map中的键值对,并将键和值分别赋值给迭代变量。
9.range
range关键字用于迭代各种数据结构,例如数组、切片、映射、通道等。它提供了一种简洁而方便的方式来遍历和访问这些数据结构中的元素。
// 使用range遍历数组
numbers := [5]int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Println(index, value)
}
// 使用range遍历切片
fruits := []string{"apple", "banana", "orange"}
for index, value := range fruits {
fmt.Println(index, value)
}
// 使用range遍历映射
colors := map[string]string{"red": "#FF0000", "green": "#00FF00", "blue": "#0000FF"}
for key, value := range colors {
fmt.Println(key, value)
}
// 使用range遍历通道
nums := make(chan int)
go func() {
nums <- 1
nums <- 2
nums <- 3
close(nums)
}()
for num := range nums {
fmt.Println(num)
}
在这个例子中,我们使用range关键字来遍历不同类型的数据结构。
首先,我们使用range遍历数组numbers。在每次迭代中,range返回当前元素的索引和值,我们将它们分别赋值给index和value变量,并打印输出。
接下来,我们使用range遍历切片fruits。同样,我们使用range返回当前元素的索引和值,并打印输出。
然后,我们使用range遍历映射colors。在每次迭代中,range返回当前键值对的键和值,我们将它们分别赋值给key和value变量,并打印输出。
最后,我们使用range遍历通道nums。我们在一个单独的goroutine中向通道发送了一些整数,并在发送完毕后关闭通道。在主goroutine中,我们使用range来迭代通道中的元素。由于通道已经被关闭,迭代会在所有元素都被接收后结束。
10.函数
// 定义一个简单的函数
func sayHello() {
fmt.Println("Hello!")
}
// 定义一个接受参数并返回值的函数
func addNumbers(a, b int) int {
return a + b
}
// 定义一个接受多个参数的函数
func calculateSum(numbers ...int) int {
sum := 0
for _, num := range numbers {
sum += num
}
return sum
}
// 定义一个接受多个返回值的函数
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
func main() {
// 调用函数
sayHello() // 输出: Hello!
result := addNumbers(5, 3)
fmt.Println(result) // 输出: 8
sum := calculateSum(1, 2, 3, 4, 5)
fmt.Println(sum) // 输出: 15
q, r := divide(10, 3)
fmt.Println(q, r) // 输出: 3 1
}
在这个例子中,我们定义了几个不同类型的函数来演示Go语言的函数语法。
首先,我们定义了一个名为sayHello的函数,它没有参数也没有返回值。在函数体内,我们使用fmt.Println语句打印输出"Hello!"。
接下来,我们定义了一个名为addNumbers的函数,它接受两个整数参数a和b,并返回它们的和。在函数体内,我们使用return语句返回计算结果。
我们还定义了一个名为calculateSum的函数,它接受可变数量的整数参数,并返回它们的总和。在函数体内,我们使用for循环和range关键字遍历参数列表,并将每个数值累加到sum变量中。
divide函数接受两个整数参数a和b,并返回它们的商和余数。在函数体内,我们使用除法运算符/计算商,使用取余运算符%计算余数,并使用多重返回值返回结果。
在main函数中,我们演示了如何调用这些函数并使用它们的返回值。我们调用sayHello函数来打印输出"Hello!"。我们将addNumbers的返回值赋给result变量,并打印输出结果。类似地,我们调用calculateSum函数来计算一系列数字的总和,并将结果打印输出。最后,我们调用divide函数来计算整数的商和余数,并将结果打印输出。
11.指针
指针是一种特殊的变量类型,它存储了一个变量的内存地址。通过指针,我们可以直接访问和修改该内存地址上存储的值。
// 定义一个整数变量和一个指向整数的指针变量
number := 42
var ptr *int
// 将指针变量指向整数变量的内存地址
ptr = &number
fmt.Println(number) // 输出: 42
fmt.Println(ptr) // 输出: 内存地址
// 通过指针修改变量的值
*ptr = 100
fmt.Println(number) // 输出: 100
在这个例子中,我们定义了一个整数变量number,并声明了一个指向整数的指针变量ptr,这通过类型*int来表示。
我们使用取地址操作符&将ptr指向number的内存地址。这样,ptr就指向了number变量。
我们可以使用fmt.Println函数打印输出number的值和ptr的值。当打印指针变量时,它将显示为内存地址。
通过解引用操作符*,我们可以访问指针所指向的内存地址上存储的值。在这个例子中,我们使用*ptr = 100将指针所指向的内存地址上的值修改为100。由于ptr指向number的地址,因此对*ptr的修改实际上也会修改number的值。
最后,我们再次打印输出number的值,可以看到它已经被修改为100。
12.结构体
结构体(Struct)是一种用于自定义复杂数据类型的构造体。结构体允许我们将不同类型的字段组合在一起,形成一个自定义的数据结构。
// 定义一个结构体
type Person struct {
Name string
Age int
Country string
}
func main() {
// 创建一个结构体实例
person1 := Person{
Name: "Alice",
Age: 25,
Country: "USA",
}
// 访问结构体字段
fmt.Println(person1.Name) // 输出: Alice
fmt.Println(person1.Age) // 输出: 25
fmt.Println(person1.Country) // 输出: USA
// 修改结构体字段的值
person1.Age = 30
fmt.Println(person1.Age) // 输出: 30
// 创建匿名结构体实例
person2 := struct {
Name string
Age int
}{
Name: "Bob",
Age: 28,
}
fmt.Println(person2.Name) // 输出: Bob
fmt.Println(person2.Age) // 输出: 28
}
在这个例子中,我们定义了一个名为Person的结构体,它有三个字段:Name、Age和Country,分别表示姓名、年龄和国家。
我们可以使用结构体字面量的方式创建结构体实例。在main函数中,我们创建了一个名为person1的结构体实例,并为其字段赋值。我们可以使用点号.来访问结构体的字段,例如person1.Name表示访问person1的Name字段。
我们还可以通过直接修改结构体的字段来更改其值。在这个例子中,我们将person1的年龄字段从25修改为30。
除了命名结构体外,我们还可以创建匿名结构体。在例子中,我们创建了一个匿名结构体实例person2,并为其字段赋值。匿名结构体没有类型名称,可以直接在使用时定义和初始化。
13.结构体方法
结构体方法(Struct Methods)是与结构体关联的函数,它们可以在结构体类型上定义和调用。结构体方法可以访问和操作结构体的字段,并执行特定的操作。
// 定义一个结构体
type Rectangle struct {
Width float64
Height float64
}
// 定义一个结构体方法,计算矩形的面积
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 定义一个结构体方法,计算矩形的周长
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func main() {
// 创建一个矩形结构体实例
rect := Rectangle{
Width: 10,
Height: 5,
}
// 调用结构体方法
area := rect.Area()
perimeter := rect.Perimeter()
fmt.Println("矩形的面积:", area) // 输出: 矩形的面积: 50
fmt.Println("矩形的周长:", perimeter) // 输出: 矩形的周长: 30
}
在这个例子中,我们定义了一个名为Rectangle的结构体,它有两个字段:Width和Height,表示矩形的宽度和高度。
然后,我们定义了两个结构体方法:Area和Perimeter。这两个方法都与Rectangle结构体关联,它们的接收者类型为Rectangle。接收者类型出现在方法名之前的括号中,表示该方法是与该类型的实例关联的。
Area方法用于计算矩形的面积,它通过接收者r访问矩形的宽度和高度字段,并返回计算结果。
Perimeter方法用于计算矩形的周长,它也通过接收者r访问矩形的宽度和高度字段,并返回计算结果。
在main函数中,我们创建了一个名为rect的矩形结构体实例,并为其字段赋值。然后,我们调用结构体方法Area和Perimeter,并将结果打印输出。
14.错误处理
// 自定义错误类型
type MyError struct {
Code int
Message string
}
// 实现 error 接口的 Error 方法
func (e MyError) Error() string {
return fmt.Sprintf("Error: %d - %s", e.Code, e.Message)
}
// 函数模拟一个可能出现错误的操作
func Divide(x, y int) (int, error) {
if y == 0 {
// 返回自定义错误类型
return 0, MyError{Code: 100, Message: "除数不能为零"}
}
if x%y != 0 {
// 返回使用 errors 包创建的错误
return 0, errors.New("无法整除")
}
// 返回 nil 表示无错误
return x / y, nil
}
func main() {
// 调用 Divide 函数
result, err := Divide(10, 2)
if err != nil {
fmt.Println("发生错误:", err)
} else {
fmt.Println("结果:", result)
}
// 调用 Divide 函数,触发自定义错误
result, err = Divide(10, 0)
if err != nil {
fmt.Println("发生错误:", err)
} else {
fmt.Println("结果:", result)
}
在这个例子中,我们定义了一个自定义的错误类型MyError,它具有Code和Message两个字段,并实现了error接口的Error方法。
然后,我们定义了一个Divide函数,模拟一个可能出现错误的操作。在函数内部,我们首先检查除数是否为零,如果是则返回一个自定义的MyError错误。如果除数不为零但无法整除,则返回使用errors.New函数创建的错误。
在main函数中,我们调用Divide函数两次进行计算并处理错误。对于成功的计算,我们打印出结果;对于发生错误的情况,我们打印出错误信息。
15.字符串操作
// 字符串拼接
str1 := "Hello"
str2 := "World"
result := str1 + " " + str2
fmt.Println(result) // 输出: Hello World
// 字符串长度
str := "Golang"
length := len(str)
fmt.Println(length) // 输出: 6
// 字符串切片
substr := str[1:3]
fmt.Println(substr) // 输出: ol
// 字符串分割
str = "apple,banana,orange"
slice := strings.Split(str, ",")
fmt.Println(slice) // 输出: [apple banana orange]
// 字符串连接
joinedStr := strings.Join(slice, "-")
fmt.Println(joinedStr) // 输出: apple-banana-orange
// 字符串包含判断
isContains := strings.Contains(str, "banana")
fmt.Println(isContains) // 输出: true
// 字符串替换
newStr := strings.Replace(str, "apple", "grape", -1)
fmt.Println(newStr) // 输出: grape,banana,orange
// 字符串前缀/后缀判断
isPrefix := strings.HasPrefix(str, "apple")
isSuffix := strings.HasSuffix(str, "orange")
fmt.Println(isPrefix, isSuffix) // 输出: true true
// 字符串查找
index := strings.Index(str, "banana")
fmt.Println(index) // 输出: 6
// 字符串大小写转换
str = "Golang"
lower := strings.ToLower(str)
upper := strings.ToUpper(str)
fmt.Println(lower, upper) // 输出: golang GOLANG
在这个例子中,我们使用了一些常见的字符串操作函数和方法:
+运算符用于字符串拼接。len()函数用于获取字符串的长度。- 切片表达式
str[start:end]用于获取字符串的子串。 strings.Split()函数用于将字符串分割成切片。strings.Join()函数用于将切片连接成字符串。strings.Contains()函数用于判断字符串是否包含指定子串。strings.Replace()函数用于替换字符串中的指定部分。strings.HasPrefix()和strings.HasSuffix()函数用于判断字符串是否以指定前缀或后缀开头。strings.Index()函数用于查找字符串中指定子串的索引位置。strings.ToLower()和strings.ToUpper()函数用于将字符串转换为小写或大写形式。
16.字符串格式化
name := "Alice"
age := 25
height := 1.65
// 使用占位符进行字符串格式化
fmt.Printf("Name: %s, Age: %d, Height: %.2f\n", name, age, height)
// 输出: Name: Alice, Age: 25, Height: 1.65
// 可以使用多个占位符
fmt.Printf("Name: %s, Age: %d, Height: %.2f\n", "Bob", 30, 1.80)
// 输出: Name: Bob, Age: 30, Height: 1.80
// 可以使用不同的占位符类型
fmt.Printf("Number: %d, Hex: %x, Float: %.2f\n", 42, 42, 3.14159)
// 输出: Number: 42, Hex: 2a, Float: 3.14
// 使用占位符宽度和对齐方式
fmt.Printf("Name: %-10s, Age: %03d\n", "Charlie", 5)
// 输出: Name: Charlie , Age: 005
// 使用%v占位符打印任意类型的值
fmt.Printf("Value: %v\n", true)
// 输出: Value: true
// 使用%T占位符打印值的类型
fmt.Printf("Type: %T\n", "Hello")
// 输出: Type: string
// 将格式化后的字符串保存到变量中
result := fmt.Sprintf("Name: %s, Age: %d", "David", 40)
fmt.Println(result)
// 输出: Name: David, Age: 40
在这个例子中,我们使用fmt.Printf()函数进行字符串格式化。我们使用了不同的占位符来表示不同类型的值:
%s表示字符串类型。%d表示整数类型。%.2f表示浮点数类型,保留两位小数。%x表示用十六进制表示整数类型。%v表示通用格式,可以打印任意类型的值。%T表示值的类型。
我们还可以使用宽度和对齐方式来格式化字符串,如使用%-10s表示左对齐并占用10个字符的字符串。
除了fmt.Printf()函数,我们还可以使用fmt.Sprintf()函数将格式化后的字符串保存到变量中。
17.JSON处理
// 定义一个结构体类型
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
// 创建一个Person对象
person := Person{
Name: "Alice",
Age: 25,
}
// 将Person对象编码为JSON格式
jsonData, err := json.Marshal(person)
if err != nil {
fmt.Println("JSON编码失败:", err)
return
}
fmt.Println(string(jsonData))
// 输出: {"name":"Alice","age":25}
// 将JSON数据解码为Person对象
var decodedPerson Person
err = json.Unmarshal(jsonData, &decodedPerson)
if err != nil {
fmt.Println("JSON解码失败:", err)
return
}
fmt.Println(decodedPerson)
// 输出: {Alice 25 }
// 创建包含嵌套结构的对象
company := struct {
Name string
Address string
Owner Person
}{
Name: "ABC Company",
Address: "123 Main St",
Owner: Person{
Name: "Bob",
Age: 30,
},
}
// 编码为JSON格式
jsonData, err = json.MarshalIndent(company, "", " ")
if err != nil {
fmt.Println("JSON编码失败:", err)
return
}
fmt.Println(string(jsonData))
// 输出:
// {
// "Name": "ABC Company",
// "Address": "123 Main St",
// "Owner": {
// "name": "Bob",
// "age": 30
// }
// }
}
在这个例子中,我们首先定义了一个Person结构体类型,它有三个字段Name、Age和Email,并使用json标签指定了对应的JSON字段名称。
在main函数中,我们创建了一个Person对象person,并使用json.Marshal()函数将其编码为JSON格式的数据。编码后的数据以字节数组形式返回,我们可以使用string()函数将其转换为字符串并打印出来。
接着,我们使用json.Unmarshal()函数将上一步编码的JSON数据解码为Person对象decodedPerson。解码过程中,需要提供目标对象的指针作为参数。
除了简单的结构体类型,我们还演示了一个包含嵌套结构的对象company的编码和解码。在编码过程中,我们使用json.MarshalIndent()函数指定了缩进和分隔符,以便生成格式化的JSON数据。
18.时间处理
func main() {
// 获取当前时间
now := time.Now()
fmt.Println("当前时间:", now)
// 格式化时间为字符串
timeStr := now.Format("2006-01-02 15:04:05")
fmt.Println("格式化后的时间:", timeStr)
// 解析字符串为时间
parsedTime, err := time.Parse("2006-01-02 15:04:05", "2021-09-01 12:30:00")
if err != nil {
fmt.Println("解析时间失败:", err)
return
}
fmt.Println("解析后的时间:", parsedTime)
// 时间比较
isBefore := now.Before(parsedTime)
isAfter := now.After(parsedTime)
fmt.Println("当前时间早于解析时间:", isBefore)
fmt.Println("当前时间晚于解析时间:", isAfter)
// 时间加减
oneHourLater := now.Add(time.Hour)
threeDaysEarlier := now.Add(-3 * 24 * time.Hour)
fmt.Println("一小时后的时间:", oneHourLater)
fmt.Println("三天前的时间:", threeDaysEarlier)
// 时间差计算
duration := now.Sub(parsedTime)
fmt.Println("时间差:", duration)
// 定时器
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("定时器触发")
// 格式化输出时间组件
year, month, day := now.Date()
hour, minute, second := now.Clock()
fmt.Printf("年:%d,月:%d,日:%d,时:%d,分:%d,秒:%d\n", year, month, day, hour, minute, second)
在这个例子中,我们首先使用time.Now()函数获取当前时间。然后,我们使用Format()方法将时间格式化为字符串,并使用指定的时间格式模板进行格式化。
接下来,我们使用time.Parse()函数将字符串解析为时间。在解析时,需要提供时间格式模板和要解析的字符串作为参数。如果解析成功,将返回对应的时间对象。
我们还演示了时间比较的操作,使用Before()和After()方法判断一个时间是否早于或晚于另一个时间。
时间的加减操作可以使用Add()方法,接受一个time.Duration类型的参数,表示要添加或减去的时间段。
我们还展示了时间差的计算,使用Sub()方法计算两个时间之间的时间差,并返回一个time.Duration类型的值。
在定时器部分,我们使用time.NewTimer()函数创建一个定时器,然后使用timer.C通道来等待定时器触发。
最后,我们使用Date()和Clock()方法分别获取时间的年、月、日和时、分、秒等组件,并格式化输出。
19.数字处理
// 数字转换为字符串
num := 42
str := strconv.Itoa(num)
fmt.Println("数字转换为字符串:", str)
// 字符串转换为数字
str = "3.14"
f, err := strconv.ParseFloat(str, 64)
if err != nil {
fmt.Println("字符串转换为浮点数失败:", err)
return
}
fmt.Println("字符串转换为浮点数:", f)
// 数字取绝对值
num = -10
absNum := math.Abs(float64(num))
fmt.Println("绝对值:", absNum)
// 数字取最大值和最小值
num1 := 10
num2 := 20
maxNum := math.Max(float64(num1), float64(num2))
minNum := math.Min(float64(num1), float64(num2))
fmt.Println("最大值:", maxNum)
fmt.Println("最小值:", minNum)
// 数字取整
f = 3.14
roundedNum := math.Round(f)
fmt.Println("四舍五入取整:", roundedNum)
// 数字格式化输出
f = 12345.6789
formattedNum := strconv.FormatFloat(f, 'f', 2, 64)
fmt.Println("格式化输出:", formattedNum)
在这个例子中,我们首先使用strconv.Itoa()函数将整数转换为字符串。该函数接受一个整数作为参数,并返回对应的字符串。
接下来,我们使用strconv.ParseFloat()函数将字符串转换为浮点数。该函数接受要解析的字符串和位数参数,并返回对应的浮点数。如果解析失败,将返回一个错误。
我们还演示了一些数学函数的使用,如math.Abs()函数用于获取数字的绝对值,math.Max()和math.Min()函数用于获取一组数字中的最大值和最小值,math.Round()函数用于将浮点数进行四舍五入取整。
最后,我们使用strconv.FormatFloat()函数将浮点数格式化为字符串。该函数接受要格式化的浮点数、格式选项和位数参数,并返回对应的字符串。
20。进程信息
// 获取当前进程的PID
pid := os.Getpid()
fmt.Println("当前进程的PID:", pid)
// 获取当前进程的父进程的PID
ppid := os.Getppid()
fmt.Println("当前进程的父进程的PID:", ppid)
// 创建一个新的进程
cmd := exec.Command("ls", "-l")
err := cmd.Start()
if err != nil {
fmt.Println("创建进程失败:", err)
return
}
// 等待进程执行结束
err = cmd.Wait()
if err != nil {
fmt.Println("进程执行出错:", err)
return
}
// 获取进程的工作目录
wd, err := os.Getwd()
if err != nil {
fmt.Println("获取工作目录失败:", err)
return
}
fmt.Println("进程的工作目录:", wd)
// 设置进程的工作目录
err = os.Chdir("/tmp")
if err != nil {
fmt.Println("设置工作目录失败:", err)
return
}
wd, err = os.Getwd()
if err != nil {
fmt.Println("获取工作目录失败:", err)
return
}
fmt.Println("进程的工作目录:", wd)
在这个例子中,我们首先使用os.Getpid()函数获取当前进程的PID,并使用os.Getppid()函数获取当前进程的父进程的PID。
接下来,我们使用os/exec包创建一个新的进程,并使用Command()函数指定要执行的命令和参数。然后,我们使用Start()方法启动进程的执行。
使用Wait()方法可以等待进程的执行结束,并获取进程的退出状态。如果返回的错误不为空,则表示进程执行出错。
我们还演示了如何使用os.Getwd()函数获取进程的工作目录,并使用os.Chdir()函数设置进程的工作目录。