Go语言
之前(上)已经记录了go语言的一些基础语法,现在我把老师课上的后半部分也补充了。
11.结构体
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- 字段名:表示结构体字段名。结构体中的字段名必须唯一。
- 字段类型:表示结构体字段的具体类型。
举个例子,我们定义一个Person(人)结构体,代码如下:
type person struct {
name string
city string
age int8
}
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
var 结构体实例 结构体类型
//比如上面这个人的结构体
var p1 person
之后,我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.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反序列化
- 反序列化,指将
json格式的数据反序列化成对应的数据类型(比如,结构体、map、切片等)的操作。 - 在反序列化时,需要保持反序列化后的数据类型 和 序列化前的数据类型 保持一致。
- 如果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())
}