Go 语言入门指南:基础语法和常用特性解析(下)|青训营

64 阅读12分钟

Go语言

之前(上)已经记录了go语言的一些基础语法,现在我把老师课上的后半部分也补充了。

11.结构体
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

举个例子,我们定义一个Person(人)结构体,代码如下:

type person struct {
	name string
	city string
	age  int8
}

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

var 结构体实例 结构体类型
//比如上面这个人的结构体
var p1 person

之后,我们通过.来访问结构体的字段(成员变量),例如p1.namep1.age

我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:

var p2 = new(person)//p2是一个结构体指针
结构体比较

如果结构体的所有成员变量都是可比较的,那么这个结构体就是可比较的。两个结构体可以通过==或!=运算符进行比较。==操作符按照顺序比较两个结构体的成员变量。

type Person struct {
    Name string
    Age int
}

func main() {
    p := Person{"jason", 25}
    q := Person{"jason", 25}
    
    fmt.Println(p == q)				// true
}
12.错误处理

实现 error 接口类型 (即实现 error 接口中的方法)来生成错误信息

方法一:在Error()方法中返回错误信息

自定义了一个fileError类型,实现了error接口:

package main

import (
	"fmt"
)

//结构体 fileError
type fileError struct {
	
}
//在结构体 fileError 上实现 Error() 方法,相当于实现了 error 接口
func (fe *fileError) Error() string {
	return "文件错误"
}

//经过以上两步已经实现了error这一接口数据类型!

func main() {
	conent, err := openFile()
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(string(conent))
	}
}

//只是模拟一个错误
func openFile() ([]byte, error) {  //返回一个error类型的值
	return nil, &fileError{}
}

像以上这样编码存在一个问题: 在实际的使用过程中,我们可能遇到很多错误,他们错误信息并不一样,不都是“文件错误”。 一种做法是每种错误都类似上面一样定义一个错误类型,然后在实现 Error() 方法时返回错误信息,但是这样太麻烦了。我们发现 Error() 返回的其实是个字符串,我们可以修改下,使得这个字符串可以让我们自己设置就可以了。 方法二:通过传参返回错误信息

type fileError struct {
    s string
}

func (fe *fileError) Error() string {
    return fe.s
}

func openFile() ([]byte, error) {
    return nil, &fileError{"文件错误,自定义"}
}

方法三:通过创建新的辅助函数返回错误信息

func New(text string) error {
    return &errorString{text}
}

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

而这种,就是Go语言内置的错误error,下面简单介绍一下,errors.New()是 “errors” 包中的一个内置方法。

我们导入"errors"包之后,就可以使用errors.New()来生成错误信息了。

我们先来看看errors.New在源码中的声明:

//参数是一个字符串,返回一个错误信息
func New(text string) error

实例:

package main

import "errors"

var ErrDivByZero = errors.New("division by zero")

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, ErrDivByZero
	}
	return x / y, nil
}
func main() {
	switch z, err := div(10, 0); err {
	case nil:
		println(z)
	case ErrDivByZero:
		panic(err) //在panic被抛出之后,如果程序里没有任何保护措施的话,程序就会打印出panic的详情,然后终止运行。
	}
}

输出结果:

panic: division by zero

goroutine 1 [running]:
main.main()
	D:/liteide/mysource/src/hello/main.go:18 +0x77

或者也可以最后的返回值中返回错误信息。例如计算开方的函数:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
    	//errors.New方法生成一个错误信息
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}
13.字符串操作

1)string底层是一个byte数组,因此string也可以进行切片处理;

2)string是不可变的,不能通过str[0]='l'来修改;但可以通过[]byte 或者 []rune来修改,即如果要修改字符串,只能先将str -> []byte 或者 []rune -> 修改 -> 再转换回string。

package main
import "fmt"
 
func main() {
	// string底层也是一个byte数组,因此string也可以进行切片处理
	var str string = "129.jpg"
	fmt.Println("str =", str)
	// 使用切片获取后三位
	var str_ string = str[4:7]
	fmt.Println("str_ =", str_)
 
	// string是不可变的,不能通过str[0]='l'来修改
	// 如果要修改字符串,只能先将str -> []byte 或者 []rune -> 修改 -> 再转换回string
	var byte_arr []byte = []byte(str)
	byte_arr[0] = '2'
	str = string(byte_arr)
	fmt.Println("str =", str)
	// byte数组每个元素,只能占一个字节,但一个汉字有三个字节
	// 故在字符串中,添加汉字,则需要将用[]rune代替[]byte,因为[]rune是按字符处理的故兼容汉字
	var rune_arr []rune = []rune(str)
	rune_arr[0] = '汉'
	str = string(rune_arr)
	fmt.Println("str =", str)
}

14.字符串格式化

格式化样式是一种字符串形式,格式化字符以%开头,加上一个表示格式化字符类型的字符串组成了格式化样式。例如, %s表示字符串格式,%d表示整数格式,下面是完整的图:

详细的格式化样式表总结如下:

格式化样式描述
%s输出字符串(string和[]byte)值
%d输出十进制整数值
%b输出整型的二进制表示形式
%#b输出整型的二进制完整表示形式
%o输出整型的八进制表示形式
%#o输出整型的八进制完整表示形式
%x输出整型的十六进制表示形式
%#x输出整型的十六进制完整表示形式
%X输出整型的十六进制表示形式(大写)
%#X输出整型的十六进制完整表示形式(大写)
%v输出值的本来形式
%+v输出值的本来形式,如果值为结构体,对结构体字段名和值展开
%#v输出Go语法格式的值
%t输出布尔值
%T输出值对应的Go语言类型
%%输出百分号(%)
%c输出Unicode码对应的字符
%U将Unicode码转换为对应的Unicode码点
%f输出浮点数
%e输出数值的科学计数法形式
%E与%e功能相同
%g输出紧凑的浮点数
%p输出指针
%q格式化字符串,在字符串的两端加上双引号
15.JSON处理

一个JSON对象是一个字符串到值的映射,写成一系列的name:value对形式,用花括号包含并以逗号分隔;也可以用于编码Go语言的map类型(key类型是字符串)和结构体。例如:

[{"key1":value1, "key2":value2, "key3":value3, "key4":[value4,value5]}, {"key6":value6, "key7":value7, "key8":value8, "key9":[value9,value10]}, ...]

json序列化

是将key-value结构的数据类型(如结构体、map、切片)序列化成json字符串。

package main
 
import (
	"encoding/json"
	"fmt"
)
 
// 用tag来指定序列化后的key
type Monster struct {
	Name  string `json:"monster_name"`  // 涉及“反射机制”
	Age   int    `json:"monster_age"`
	Phone string `json:"monster_phone"`
	Email string `json:"monster_email"`
}
 
func NewMonster(name string, age int, phone string, email string) *Monster {
	return &Monster{
		Name:  name,
		Age:   age,
		Phone: phone,
		Email: email,
	}
}
 
// 将 结构体 和 结构体切片 序列化
func SerialStructAndStructSlice() {
	var monster1 *Monster = NewMonster("tom", 14, "1234567", "2937139791@qq.com")
	var monster2 *Monster = NewMonster("jary", 16, "1234568", "2937139791@163.com")
	res, err := json.Marshal(*monster1) // Marshal(val interface{})
	if err != nil {
		fmt.Printf("Serialization failed, Error is %s\n", err)
	}
	fmt.Println(string(res))
 
	// var monsters []Monster = make([]Monster, 2)
	// monsters[0] = *monster1
	// monsters[1] = *monster2
	var monsters []Monster
	monsters = append(monsters, *monster1)
	monsters = append(monsters, *monster2)
	res, err = json.Marshal(monsters) // Marshal(val interface{})
	if err != nil {
		fmt.Printf("Serialization failed, Error is %s\n", err)
	}
	fmt.Println(string(res))
}
 
// 将 map 和 map切片 进行序列化
func SerialMap() {
	var map_1 map[string]interface{} = make(map[string]interface{}, 5)
	map_1["name"] = "tom"
	map_1["sex"] = "male"
	map_1["age"] = 35
	map_1["work"] = "worker"
	res, err := json.Marshal(map_1)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(res))
 
	var map_2 map[string]interface{} = make(map[string]interface{}, 5)
	map_2["name"] = "jary"
	map_2["sex"] = "female"
	map_2["age"] = 25
	map_2["work"] = "student"
 
	var map_ []map[string]interface{}
	map_ = append(map_, map_1)
	map_ = append(map_, map_2)
	res, err = json.Marshal(map_)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(res))
}
 
func main() {
	SerialStructAndStructSlice()
	SerialMap()
}
json反序列化
  1. 反序列化,指将json格式的数据反序列化成对应的数据类型(比如,结构体、map、切片等)的操作。
  2. 在反序列化时,需要保持反序列化后的数据类型序列化前的数据类型 保持一致。
  3. 如果json字符串是通过转义字符获取到的,则不需要再对其转义处理。
package main
 
import (
	"encoding/json"
	"fmt"
)
 
// 将json反序列化 成 map
func DeserializationMap() {
	// 在开发过程中,该json字符串一般是通过,网路传输/读取文件 得到的
	var str string = "[{\"age\":35,\"name\":\"tom\",\"sex\":\"male\",\"work\":\"worker\"}," + 
"{\"age\":25,\"name\":\"jary\",\"sex\":\"female\",\"work\":\"manager\"}]"
	var map_ map[string]interface{}
	err := json.Unmarshal([]byte(str), &map_)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(map_)
	for key, val := range map_ {
		fmt.Printf("[%s]=%v\t", key, val)
	}
	fmt.Println()
}
 
type Monster struct {
	Name string
	Age  int
	Sex  string
}
 
func DeserializationStruct() {
	// 在开发过程中,该json字符串一般是通过,网路传输/读取文件 得到的
	var str string = "{\"Name\":\"tom\",\"Age\":25,\"sex\":\"male\"}"
	var monster Monster
	err := json.Unmarshal([]byte(str), &monster)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(monster)
}
 
func main() {
	DeserializationMap()
	DeserializationStruct()
}
16.时间处理

可以通过time来处理日期和时间。我们需要引用标准库

import "time"

获取当前时间

now := time.Now()
fmt.Println(now)
// 当前时间戳,1970年1月1日到现在的秒数
fmt.Println("  秒", now.Unix())
fmt.Println("豪秒", now.UnixMilli())
fmt.Println("微秒", now.UnixMicro())
fmt.Println("纳秒", now.UnixNano())

日期时间格式化

特别注意:go的格式化字符串不是常见的 yy-MM-dd HH:mm:ss,而是2006-01-02 15:04:05 Go的成立日期。

fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println(now.Format("06-1-2 3:4:5"))
//会输出当前的电脑时间,如
//2023-08-01 17:02:37
//23-8-1 5:2:37

这里给出格式化表:

格式化字符说明:

字符说明
1月份
01月份,保证两位数字,1月会输出01
2日期
02日期,保证两位数字,2日会输出02
3小时,12小时制
03小时,保证两位数字,3时会输出03
15小时,24小时制,保证两位数字,3时会输出03
4分钟
04分钟,保证两位数字,4分会输出04
5
05秒,保证两位数字,5秒会输出05
06年,输出最后两位
2006年,输出4位
.000毫秒

字符串日期时间转time

通过time.Parse来把字符串转为 Time 时间对象

t, err := time.Parse("2006-01-02 15:04:05", "2023-04-14 07:03:04")
if err == nil {
    fmt.Print(t.Year())
} else {
    fmt.Print(err)
}
//出错了就会返回一个err,里面会有说明

获取年/月/周/日/时/分/秒

t, err := time.Parse("2006-01-02 15:04:05", "04-14 07:03:04")

fmt.Println("年", t.Year())
fmt.Println("月", t.Month())
fmt.Println("月", t.Month() == 4)
fmt.Println("日", t.Day())
fmt.Println("时", t.Hour())
fmt.Println("分", t.Minute())
fmt.Println("秒", t.Second())
fmt.Println("星期几", t.Weekday())
year, week := t.ISOWeek()
fmt.Println("某年第几周", year, week)
fmt.Println("当年第几天", t.YearDay())
17.数字解析

strconv 是 Golang 中一个非常常用的包,主要用于字符串和基本数据类型之间的相互转换。本文将详细介绍 strconv 包的常用函数及用法。

strconv.Atoi 和 strconv.Itoa

Atoi 函数用于将字符串转换为 int 类型,Itoa 函数则用于将 int 类型转换为字符串类型。简单使用示例如下:

示例:

package main
 
import (
    "fmt"
    "strconv"
)
 
func main() {
    str := "123"
    intValue, _ := strconv.Atoi(str)
    fmt.Printf("str to int: %d\n", intValue)
 
    intValue += 1
    str = strconv.Itoa(intValue)
    fmt.Printf("int to str: %s\n", str)
}
strconv.Parse 系列函数

strconv.Parse 系列函数用于将字符串解析为指定类型。其中常用的函数有 ParseInt、ParseBool 和 ParseFloat。简单使用示例如下:

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	// 解析整数
	intStr := "123"
	intValue, _ := strconv.ParseInt(intStr, 10, 64)
	fmt.Printf("Parsed int value: %d\n", intValue)
 
	// 解析布尔值
	boolStr := "true"
	boolValue, _ := strconv.ParseBool(boolStr)
	fmt.Printf("Parsed bool value: %t\n", boolValue)
 
	// 解析浮点数
	floatStr := "3.14"
	floatValue, _ := strconv.ParseFloat(floatStr, 64)
	fmt.Printf("Parsed float value: %f\n", floatValue)
}
strconv.Format 系列函数

strconv.Format 系列函数用于将基本数据类型转换为字符串类型。常用的函数有 FormatInt、FormatBool 和 FormatFloat。简单使用示例如下:

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	// 格式化整数
	intValue := 123
	intStr := strconv.FormatInt(int64(intValue), 10)
	fmt.Printf("Formatted int string: %s\n", intStr)
 
	// 格式化布尔值
	boolValue := true
	boolStr := strconv.FormatBool(boolValue)
	fmt.Printf("Formatted bool string: %s\n", boolStr)
 
	// 格式化浮点数
	floatValue := 3.14
	floatStr := strconv.FormatFloat(floatValue, 'f', -1, 64)
	fmt.Printf("Formatted float string: %s\n", floatStr)
}
strconv.Append 系列函数

strconv.Append 系列函数用于将基本数据类型追加到已存在的字节数组中。常用的函数有 AppendInt、AppendBool 和 AppendFloat。简单使用示例如下:

package main
 
import (
    "fmt"
    "strconv"
)
 
func main() {
    // 追加整数到字节数组
    num1 := 123
    byteSlice := []byte("Number: ")
    byteSlice = strconv.AppendInt(byteSlice, int64(num1), 10)
    fmt.Printf("Appended int: %s\n", byteSlice)
 
    // 追加布尔值到字节数组
    boolVal := true
    byteSlice = []byte("Bool: ")
    byteSlice = strconv.AppendBool(byteSlice, boolVal)
    fmt.Printf("Appended bool: %s\n", byteSlice)
 
    // 追加浮点数到字节数组
    floatVal := 3.14
    byteSlice = []byte("Float: ")
    byteSlice = strconv.AppendFloat(byteSlice, floatVal, 'f', -1, 64)
    fmt.Printf("Appended float: %s\n", byteSlice)
}
strconv.IsPrint 和 strconv.IsGraphic

strconv.IsPrint 函数用于判断给定的 Unicode 字符是否可打印,可打印字符是指那些可以在屏幕上显示的字符。strconv.IsGraphic 函数用于判断给定的 Unicode 字符是否是图形字符,图形字符是指可视化的字符。简单使用示

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	chars := []rune{'H', 'e', 'l', '\n', '♥', 127}
	for _, char := range chars {
		fmt.Printf("Character: %c, IsPrint: %v\n", char, strconv.IsPrint(char))
		fmt.Printf("Character: %c, IsGraphic: %v\n", char, strconv.IsGraphic(char))
	}
}
strconv.Quote 和 strconv.Unquote 系列函数

strconv.Quote 系列函数用于转义和引用字符串的功能,将字符串转换为可以直接表示的字符串字面值(literal),包括添加转义字符和引号。简单使用示例如下:

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	str := `路多辛的, "所思所想"!`
 
	quoted := strconv.Quote(str)
	fmt.Println("Quoted: ", quoted)
 
	unquoted, err := strconv.Unquote(quoted)
	if err != nil {
		fmt.Println("Unquote error: ", err)
	} else {
		fmt.Println("Unquoted: ", unquoted)
	}
}
strconv.CanBackquote

strconv.CanBackquote 函数用于检查字符串是否可以不变地表示为单行反引号字符串(即以 `` 开头和结尾的字符串)。简单使用示例如下:

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	str1 := "Hello, world!"
	str2 := "`Hello, world!`"
	str3 := "`Hello,\nworld!`"
 
	fmt.Println(strconv.CanBackquote(str1)) // 输出:false
	fmt.Println(strconv.CanBackquote(str2)) // 输出:true
	fmt.Println(strconv.CanBackquote(str3)) // 输出:false
}
18.进程信息

关于进程的一些简单操作大家看下面这个代码和注释即可。

package main 
 
import ( 
    "fmt" 
    "os" 
    "time" 
) 
 
func main() { 
    // 获得当前正在运行的进程id 
    fmt.Printf("os.Getegid(): %v\n", os.Getegid()) 
    // 父id 
    fmt.Printf("os.Getegid(): %v\n", os.Getegid()) 
 
    // 设置新进程的属性 
    attr := &os.ProcAttr{ 
        // files指定新进程继承的活动文件对象 
        // 前三个分别为,标准输入、标准输出、标准错误输出 
        Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, 
        // 新进程的环境变量 
        Env: os.Environ(), 
    } 
 
    // 开始一个新进程 
    p, err := os.StartProcess("c:\\windows\\system32\\notepad.exe", []string{"c:\\windows\\system32\\notepad.exe", "d:\\a.txt"}, attr) 
    if err != nil { 
        fmt.Println(err) 
    } 
    fmt.Println(p) 
    fmt.Println("进程ID:", p.Pid) 
 
    // 通过进程ID查找进程 
    p2, _ := os.FindProcess(p.Pid) 
    fmt.Println(p2) 
 
    // 等待10秒,执行函数 
    time.AfterFunc(time.Second*10, func() { 
        // 向p进程发出退出信号 
        p.Signal(os.Kill) 
    }) 
 
    // 等待进程p的退出,返回进程状态 
    ps, _ := p.Wait() 
    fmt.Println(ps.String()) 
}