自定义错误
package main
import "fmt"
type IferError struct {
msg string
code int
}
// 调用者是谁
// Error(参数)
// 返回值
func (i *IferError) Error() string {
// fmt.Sprintf 返回一个 string
return fmt.Sprintf("错误信息:%s\n,代码:%d\n", i.msg, i.code)
}
// error 的使用,如果函数或方法中有预期错误,都需要抛出
func testErr(i int) (int, error) {
if i != 0 {
// 0, 实现 IferError
return 0, &IferError{msg: "错误数据", code: 500}
}
return 0, nil
}
func main() {
info, err := testErr(1)
if err != nil {
fmt.Println(err)
e, ok := err.(*IferError)
if ok {
fmt.Println(e.msg)
// 返回给前端
}
}
fmt.Println(info)
}
异常
基本操作
抛出 Panic
package main
func main() {
// 什么时候会发生恐慌 panic,我们不知道什么时候会报错
// 程序运行的时候会发生 panic
// 手动抛出 panic,我们在一些能够预知到危险的情况下,可以主动抛出
// 如果有 panic 发生,我们尽可能接收它,并处理
panic("程序 panic 了")
}
执行顺序
package main
import "fmt"
// defer 是延迟函数,先进后出,处理一些问题
// 出现了 panic 后,如果【前面】有 defer 语句,先执行所有的 defer 语句
func main() {
testPanic(1)
}
func testPanic(num int) {
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
if num == 1 {
panic("出现预定的异常----panic")
}
defer fmt.Println("4")
fmt.Println("5 ")
}
// 3、2、1、报错
c、3、2、1、b、a、报错
package main
import "fmt"
// defer 是延迟函数,先进后出,处理一些问题
// 出现了 panic 后,如果【前面】有 defer 语句,先执行所有的 defer 语句
func main() {
defer fmt.Println("a")
defer fmt.Println("b")
fmt.Println("c")
// 也不会继续再向下执行了
testPanic(1)
defer fmt.Println("d")
fmt.Println("e")
}
func testPanic(num int) {
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
if num == 1 {
panic("出现预定的异常----panic")
}
defer fmt.Println("4")
fmt.Println("5 ")
}
recover
package main
import "fmt"
// 1. panic 触发
// 2. 执行前面所有 defer 语句,先进后出
// 3. 直到遇到 recover 处理了这个 panic,函数结束
// 4. main,继续向下执行
func main() {
defer fmt.Println("a")
defer fmt.Println("b")
fmt.Println("c")
// 如果在函数内部已经处理 panic,那么程序会继续向下执行
testPanic(1)
defer fmt.Println("d")
fmt.Println("e")
}
func testPanic(num int) {
// 出去函数的时候处理这里面可能发生的panic
// recover func recover() any 返回panic传递的值
// panic func panic(v any)
defer func() {
if msg := recover(); msg != nil {
fmt.Println("recover 执行了,panic msg:", msg)
fmt.Println("---------程序已恢复----------")
}
}()
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
// 如果在函数中一旦触发了 panic,会终止后面要执行的代码
if num == 1 {
panic("出现预定的异常----panic")
}
defer fmt.Println("4")
fmt.Println("5")
}
包
Go 程序首先在 GOROOT/src 目录中寻找包目录,如果没有找到,则会去 GOPATH/src 目录中继续找,比如 fmt 包是位于 GOROOT/src 目录的,那么它将会从该目录导入。
导入包的时候,要从 GOPATH/src 去写,需要使用包名.函数名来使用。main 函数所在的包,必须是 main 包,表示程序的入口。一个目录下所有的 go 文件的 package 必须同名。
包分类:标准库、第三方、自定义。
GOPATH 模式下导包
先关闭 Goland 编辑器的 Go mod 模式,可以通过 go env 查看。
go env -w GOWORK=路径
我电脑的 GOPATH 是 C:\Users\dangp\go
C:\Users\dangp\go\src\xuego\hello\main.go
package main
import (
"xuego/hello/api"
"xuego/hello/db"
)
func main() {
api.RestAPI1()
api.RestAPI2()
db.MysqlHandler()
db.RedisHandler()
}
C:\Users\dangp\go\src\xuego\hello\api\http.go
package api
import "fmt"
func RestAPI1() {
fmt.Println("restAPI1")
}
导包
import (
// "crypto/rand" // 正常导入
// R "math/rand" // 可以给包起别名
// . "math/rand" // 简便模式:可以直接调用该包下的函数,不需要通过包名点的形式
_ "math/rand" // 匿名导入,只会执行这个包下的 init 方法
)
init 函数
init 函数,在 main 方法执行之前执行,通过 init 函数,通常用来初始化一些全局变量,建立一些第三方的连接(数据库连接)、注册、检查、修复程序状态。init 函数可以有多个。
- 如果导入了多个匿名包,按照 main 中导入包的顺序来进行执行
- 同一个包下的多个 go 文件,都有 init 的情况下,按照文件排放顺序来执行对应的 init 函数
- 单个 go 文件中的多个 init 是顺序执行的
- 等所有 go 文件中的 init 函数执行完毕后,才会到 main 包
Go Mod
go env
go env -w GO111MODULE=on
go mode init # Go 会尝试根据当前目录的位置推断模块路径,如果当前目录不在 `GOPATH` 中,并且没有提供明确的模块路径,Go 就无法确定模块路径,会报错
go mod init # 模块路径
# 如果你的模块路径是 `xuego/hello`,那么在导包时首先要写上 'xuego/hello',后面再拼接上其他路径
# 如果你的模块路径是 `xxx`,那么在导包时,首先要写上 `xxx`,后面再拼接上其他路径
设置包的代理
go env -w GOPROXY="https://goproxy.cn,direct"
如何更改模块路径
如果初始化时指定的模块路径不合适,可以随时修改。
- 编辑
go.mod文件,将module行改为新的模块路径。例如:
module github.com/username/xuego/hello
- 更新项目中的所有导入路径,确保它们与新的模块路径一致。例如:
import "github.com/username/xuego/hello/api"
- 运行以下命令更新依赖:
# 在 go.mod 所在的同级目录
go mod tidy
关于 go mod tidy
假如 main.go 中的代码如下:
package main
import (
"fmt"
"xxx/api"
"github.com/jinzhu/now"
)
func main() {
api.RestAPI1()
api.RestAPI2()
fmt.Println(now.BeginningOfMinute())
}
我可以在 xuego/hello 目录执行 go mod tidy 来拉取包中依赖的第三方包,注意执行命令的位置和 go.mod 在同一目录。
第三方的依赖都会放到 GOMODCACHE 对应的目录,即 GOPATH\pkg\mod 目录。
# 让包记录到自己的项目,不再是 GOMODCACHE 啦
go mod vendor
内置的 strings 包
package main
import (
"fmt"
"strings"
)
func main() {
// 1. 字符是不能修改的
str := "dva, xxx"
fmt.Println(str[0]) // 100
// 2. 判断某个字符是否包含了指定的内容
fmt.Println(strings.Contains(str, "x")) // true
// 3. 判断某个字符串是否包含了多个字符串中的某一个
fmt.Println(strings.ContainsAny(str, "xi")) // true
// 4. 统计这个字符在指定字符串中出现的数量 Count() 计数
fmt.Println(strings.Count(str, "x")) // 3
fileName := "20250301.mp3"
// 5. 判断用什么开头的
if strings.HasPrefix(fileName, "2025") {
fmt.Println("找到 2025 开头的文件:", fileName) // 找到 2025 开头的文件: 20250301.mp3
}
// 6. 判断用什么结尾的 HasSuffix()
if strings.HasSuffix(fileName, ".mp3") {
fmt.Println("找到 mp3 结尾的文件:", fileName) // 找到 mp3 结尾的文件: 20250301.mp3
}
// 7. 寻找这个字符串第一次出现的位置 Index()
fmt.Println(strings.Index(str, "v")) // 1,找不到会返回 -1
// 8. 寻找这个字符串最后一次出现的位置 LastIndex()
fmt.Println(strings.LastIndex(str, "a")) // 2
// 9. 拼接字符串, 数组或者切片拼接,前端给了我们多个参数
str2 := []string{"a", "b", "c", "d", "e"}
fmt.Println(strings.Join(str2, "~")) // a~b~c~d~e
// 10. 通过某个格式,拆分字符串 Split()
str3 := strings.Join(str2, "~") // [a~b~c~d~e]
fmt.Println(strings.Split(str3, "~")) // [a b c d e]
// 11. 大小写 ToUpper()
fmt.Println(strings.ToUpper(str)) // DVA, XXX
fmt.Println(strings.ToLower(str)) // dva, xxx
// 12. 替换
fmt.Println(strings.Replace(str, "x", "🤠", 1)) // dva, 🤠xx
// 13. 截取某个字符串
str5 := str[0:3]
fmt.Println(str5) // dva
}
strconv 类型转换方法
package main
import (
"fmt"
"strconv"
)
func main() {
s1 := "true"
// 1. 字符串转 bool
b1, err := strconv.ParseBool(s1)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%T,%t\n", b1, b1) // bool,true
// 2. 格式化布尔为字符串
s2 := strconv.FormatBool(b1)
fmt.Printf("%T,%s\n", s2, s2) // string,true
// 3. 字符串转 int
s3 := "100000"
// 参数:str、源进制(10)、大小
i1, _ := strconv.ParseInt(s3, 10, 64)
fmt.Printf("%T,%d\n", i1, i1) // int64,100000
// 4. 格式化 int 为字符串
s4 := strconv.FormatInt(i1, 10)
fmt.Printf("%T,%s\n", s4, s4) // string,100000
// 5. 字符串转数字(十进制)
atoi, _ := strconv.Atoi("-20")
fmt.Printf("%T,%d\n", atoi, atoi) // int,-20
// 6. 数字转字符串
itoa := strconv.Itoa(30)
fmt.Printf("%T,%s\n", itoa, itoa) // string,30
}
time
package main
import (
"fmt"
"time"
)
func main() {
time1()
}
func time1() {
// 返回值为 Time 结构体
now := time.Now()
year := now.Year()
month := now.Month()
day := now.Day()
hour := now.Hour()
minute := now.Minute()
second := now.Second()
// 整数补位: 02 如果不足两位,左侧用 0 补齐输出
// 2025-03-01 15:12:31
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
package main
import (
"fmt"
"time"
)
func main() {
time2()
}
func time2() {
now := time.Now()
// 时间格式化 2023-02-23 20:43:49
// 格式化模板:yyyy-MM-dd HH:mm:ss
// Go 语言诞生的时间作为格式化模板:2006 年 1 月 2 号下午 3 点 4 分
// Go 语言格式化时间的代码:2006-01-02 15:04:05 (记忆方式:2006 12 345)
// 固定的:"2006-01-02 15:04:05"
fmt.Println(now.Format("2006-01-02 15:04:05")) // 24 小时制
fmt.Println(now.Format("2006-01-02 03:04:05 PM")) // 12 小时制
fmt.Println(now.Format("2006/01/02 15:04")) // 2023/02/23 20:52
fmt.Println(now.Format("15:04 2006/01/02")) // 20:52 2023/02/23
fmt.Println(now.Format("2006/01/02")) // 2023/02/23
}
package main
import (
"fmt"
"time"
)
func main() {
time3()
}
// 将字符串格式化为 Time 对象 (获取到网页传递的时间字符串,需要转化为 Time 才能在代码中使用)
func time3() {
// 其他地方的时区格式:https://www.zeitverschiebung.net/cn/all-time-zones.html
// Asia/Shanghai" 注意大小写,如果不对,会报未知的时间错误
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
// 将字符串解析为时间 Time
timeStr := "2025-04-01 15:28:33"
timeObj, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)
fmt.Println(timeObj)
}
package main
import (
"fmt"
"time"
)
func main() {
time4()
}
// 时间戳:更多时候和随机数结合
func time4() {
// 格林威治时间自 1970 年 1 月 1 日(00:00:00 GMT) 至当前时间的总秒数
// 1970.1.1 00:00:00
now := time.Now()
timestamp1 := now.Unix() // 时间戳
timestamp2 := now.UnixNano() // 纳秒的时间数
fmt.Println(timestamp1) // 1740814216
fmt.Println(timestamp2) // 1740814216281495700
// 通过 Unix 转换 time 对象
timeObj := time.Unix(timestamp1, 0) // 返回 time 对象
year := timeObj.Year()
month := timeObj.Month()
day := timeObj.Day()
hour := timeObj.Hour()
minute := timeObj.Minute()
second := timeObj.Second()
// 2025-03-01 15:30:16
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}
随机数
package main
import (
"fmt"
"math/rand"
)
func main() {
// 1. 获取一个随机数
num1 := rand.Int()
fmt.Println("num1:", num1) // 7621103522683197901
// 2. [0, 100)
num2 := rand.Intn(100)
fmt.Println("num2:", num2) // 67
}
定时器
package main
import (
"fmt"
"time"
)
func main() {
// 每一秒都会触发
ticker := time.Tick(time.Second)
for i := range ticker {
fmt.Println(i)
}
}
时间比较
package main
import (
"fmt"
"time"
)
func main() {
// 加、减、比较(在 xxx 之前、在 xxx 之后、相等)
now := time.Now()
// 1. 加一个小时
later := now.Add(time.Hour)
//2025-03-01 16:43:32.3842393 +0800 CST m=+3600.000000001
fmt.Println(later)
// 2. later 和现在时间的差值
subTime := later.Sub(now)
fmt.Println(subTime)
// 3. 比较时间, 校验时间当地时间和网络时间是否一致
fmt.Println(now.Equal(later)) // fasle
fmt.Println(now.Before(later)) // true
fmt.Println(now.After(later)) // fasle
}
IO
文件信息
package main
import (
"fmt"
"os"
)
// 文件分为文本文件和二进制文件
func main() {
fileInfo, err := os.Stat("D:\2025\Golang\src\xuego\hello\go.mod")
if err != nil {
return
}
fmt.Println(fileInfo.Name()) // go.go
fmt.Println(fileInfo.IsDir()) // false
fmt.Println(fileInfo.ModTime()) // 2025-02-25 23:05:11.26645 +0800 CST
fmt.Println(fileInfo.Size()) // 22 字节数
fmt.Println(fileInfo.Mode()) // -rw-rw-rw-,r可读、w可写、x可执行
}
还有一种表示权限的标识:八进制,例如 chomd 7 7 7
目录的创建和删除
package main
import (
"fmt"
"os"
)
func main() {
// 1. 打开一个文件夹(存在我就打开,不存在就创建这个文件夹)
// 0777 => 表示可读可写可执行
err := os.Mkdir("D:\2025\Golang\src\xuego\hello\test", os.ModePerm)
if err != nil {
// 存在就无法创建了,Cannot create a file when that file already exists.
fmt.Println(err)
}
fmt.Println("文件夹创建完毕")
// 2. 创建多层级文件夹
err2 := os.MkdirAll("D:\2025\Golang\src\xuego\hello\a\b\c", os.ModePerm)
if err2 != nil {
fmt.Println(err2)
}
fmt.Println("层级文件夹创建完毕")
// 3. 删除空文件夹
// 通过 remove方 法只能删除单个空文件夹
err3 := os.Remove("D:\2025\Golang\src\xuego\hello\a\b\c")
if err3 != nil {
fmt.Println(err3)
//return
}
fmt.Println("file delete success!!")
// 4. 如果存在多层文件,removeAll,相对来说比较危险,删除这个目录下的所有东西, 强制删除
err4 := os.RemoveAll("D:\2025\Golang\src\xuego\hello\a")
if err4 != nil {
fmt.Println(err4)
return
}
fmt.Println("err4 delete success!!")
}
创建和删除文件
package main
import (
"fmt"
"os"
)
func main() {
// 1. 返回的 file 对象就是我们的文件
file1, err := os.Create("a.go") // 相对路径
if err != nil {
fmt.Println(err)
}
fmt.Println(file1)
// 2. 删除文件
os.Remove("D:\2025\Golang\src\xuego\hello\a.go")
}
IO 读
package main
import (
"fmt"
"os"
)
func main() {
// 1. 找到这个文件的对象
file1, err := os.Open("D:\2025\Golang\src\xuego\hello\a.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file1)
// 2. 打开文件
file2, err2 := os.OpenFile("D:\2025\Golang\src\xuego\hello\a.txt",
os.O_RDONLY|os.O_WRONLY, os.ModePerm)
if err2 != nil {
fmt.Println(err2)
}
fmt.Println(file2)
// 可以操作这个文件对象了
// ...
}
读取文件内容,为什么多读出了一个汉字?
package main
import (
"fmt"
"os"
)
// 读取文件数据
func main() {
// 我们习惯于在建立连接时候通过defer来关闭连接,保证程序不会出任何问题,或者忘记关闭
file, _ := os.Open("a.txt")
// 关闭连接
defer file.Close()
// 1. 创建一个容器/切片 (二进制文本文件 => 读取流到一个容器 => 再读取容器里的数据)
bs := make([]byte, 3, 1024)
// 2. 读取到缓冲区中
// 一个汉字占 3 个字节
n, err := file.Read(bs)
fmt.Println(string(bs)) // 你
file.Read(bs)
fmt.Println(string(bs)) // 好
// 为什么多读出了一个“好”
n, err = file.Read(bs)
fmt.Println(string(bs)) // 好
fmt.Println(n) // 0
fmt.Println(err) // EOF,读取到了文件末尾,就会返回 EOF
}
写入文件内容
package main
import (
"fmt"
"os"
)
func main() {
fileName := "a.txt"
// 权限:如果我们要向一个文件中追加内容,如果没有,就是从头开始写
file, _ := os.OpenFile(fileName, os.O_WRONLY|os.O_RDONLY|os.O_APPEND, os.ModePerm)
defer file.Close()
// 1. 方式一,通过 file.Write
/* bs := []byte{65, 66, 67, 68, 69} // A B C D E
n, err := file.Write(bs)
if err != nil {
fmt.Println(err)
}
fmt.Println(n) */
// 2. 方式二,通过 file.WriteString
n, err := file.WriteString("哈哈哈哈哈哈哈")
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
}
拷贝文件
package main
import (
"fmt"
"io"
"os"
)
func main() {
source := "D:\2025\Golang\src\xuego\hello\a.txt"
dest := "D:\2025\Golang\src\xuego\hello\b.txt"
// 打开源文件
srcFile, err := os.Open(source)
if err != nil {
fmt.Printf("打开源文件失败: %v\n", err)
return
}
defer srcFile.Close()
// 创建目标文件
destFile, err := os.Create(dest)
if err != nil {
fmt.Printf("创建目标文件失败: %v\n", err)
return
}
defer destFile.Close()
// 使用 io.Copy 进行拷贝
_, err = io.Copy(destFile, srcFile)
if err != nil {
fmt.Printf("文件拷贝失败: %v\n", err)
return
}
fmt.Println("文件拷贝成功!")
}
Seeker
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 读取文件
file, _ := os.OpenFile("D:\2025\Golang\src\xuego\hello\a.txt",
os.O_RDWR, os.ModePerm)
defer file.Close()
// 偏移量
// 相对开始位置,io.SeekStart
// 相对于文件末尾,io.SeekEnd
file.Seek(0, io.SeekStart)
buf := []byte{0}
file.Read(buf)
fmt.Println(string(buf)) // h
// 相对于当前位置
file.Seek(0, io.SeekCurrent)
file.Read(buf)
fmt.Println(string(buf)) // e
file.Seek(0, io.SeekCurrent)
file.Read(buf)
fmt.Println(string(buf)) // l
file.Seek(0, io.SeekCurrent)
file.Read(buf)
fmt.Println(string(buf)) // l
file.Seek(0, io.SeekCurrent)
file.Read(buf)
fmt.Println(string(buf)) // o
// 在结尾追加内容
// file.Seek(0, io.SeekEnd)
// file.WriteString("哈哈")
}
bufio
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("D:\2025\Golang\src\xuego\hello\a.txt")
if err != nil {
log.Println(err)
}
defer file.Close()
// 1. 创建一个 bufio 包下的 reader 对象
bufioReader := bufio.NewReader(file)
buf := make([]byte, 1024)
n, err := bufioReader.Read(buf)
fmt.Println("读取到了多少个字节:", n)
// 2. 读取键盘的输入
// 键盘的输入,实际上是流 os.Stdin
inputReader := bufio.NewReader(os.Stdin)
readString, _ := inputReader.ReadString('\n')
fmt.Println("读取键盘输入的信息:", readString)
}