【go学习笔记四】接口、模拟OOP、并发编程、标准库
golang 接口
接口是一种新的类型定义,把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
要实现接口,需要实现接口的所有方法。
语法格式
// USB接口
type USB interface {
read()
write()
}
type Computer struct {
name string
}
func (c Computer) read() {
fmt.Printf("Computer %v reading\n", c.name)
}
func (c Computer) write() {
fmt.Printf("Computer %v writing\n", c.name)
}
func main() {
var c USB = Computer{
name: "yadi",
}
c.read()
c.write()
fmt.Printf("c: %#v\n", c) // c: main.Computer{name:"yadi"}
}
值类型接收者和指针类型接收者
传拷贝和传指针的区别,拷贝不影响原值,指针影响原值。
接口和类型的关系
一个类型可以实现多个接口
type PlayerMusic interface { playMusic() } type PlayVideo interface { playVideo() } type Mobile struct { } func (m Mobile) playMusic() { fmt.Println("play music") } func (m Mobile) playVideo() { fmt.Println("play video") } func main() { m := Mobile{} m.playMusic() m.playVideo() }
多个类型可以实现同一个接口(多态)
type Pet interface { eat() } type Dog struct { } type Cat struct { } func (d Dog) eat() { fmt.Println("dog eat") } func (c Cat) eat() { fmt.Println("cat eat") } func main() { var p Pet p = Dog{} p.eat() // dog eat p = Cat{} p.eat() // cat eat }
接口嵌套
type Flyer interface {
fly()
}
type Swimmer interface {
swim()
}
// 接口的组合
type FlyFish interface {
Flyer
Swimmer
}
type FFish struct {
}
func (f FFish) fly() {
fmt.Println("fish fly")
}
func (f FFish) swim() {
fmt.Println("fish swim")
}
func main() {
var ff FlyFish = FFish{}
ff.fly() // fish fly
ff.swim() // fish swim
}
OCP设计原则
Open-closed Principle。对扩展开放,对修改关闭。
如Pet接口,Dog和Cat结构体实现了Pet接口,要增加Pig结构体,也可直接继承Pet接口,不造成对源代码的修改。
golang模拟OOP属性和方法
可以通过结构体struct和函数绑定来实现OOP的属性和方法等特性。OOP:面向对象编程。
继承
通过结构体嵌套实现继承。
type Animal struct {
}
type Dog struct {
a Animal
}
构造函数
使用函数模拟构造函数的功能。
type Person struct {
name string
age int
}
func NewPerson(name string, age int) (*Person, error) {
if name == "" || age < 0 {
return nil, fmt.Errorf("args wrong")
}
return &Person{name: name, age: age}, nil
}
func main() {
person, err := NewPerson("", 15)
fmt.Printf("err: %#v\n", err) // err: &errors.errorString{s:"args wrong"}
fmt.Printf("person: %#v\n", person) // person: (*main.Person)(nil)
fmt.Println("----------------")
person, err = NewPerson("tom", 15)
fmt.Printf("err: %#v\n", err) // err: <nil>
fmt.Printf("person: %#v\n", person) // person: &main.Person{name:"tom", age:15}
}
包管理工具go module
创建一个包,一般是创建一个文件夹,在文件夹里的go文件中,使用package关键字声明包名称。
go module是golang 1.11新加的特性,用来管理模块中的包依赖关系。
go在依赖管理时,会创建两个文件,go.mod
和go.sum
go.mod提供了依赖版本的全部信息。
go.mod如:
module go_pro
go 1.18
require github.com/go-sql-driver/mysql v1.6.0 // indirect
go.sum如:
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
格式就是:
依赖的路径 版本号 hash算法
这两个文件暂时先了解这么多。
使用方法
初始化模块:
go mod init <项目模块名称>
依赖关系处理,根据go.mod文件:
go mod tidy
需要依赖就下载,没有用到就去掉将依赖包复制到项目下的vendor目录:
go mod vendor
显示依赖关系
go list -m all
显示详细依赖关系
go list -m -json all
下载依赖
go mod download [path@version]
如安装gin
:
Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。
> go get -u github.com/gin-gonic/gin
之后,go.mod
会自动加入需要的依赖:
module go_pro
go 1.18
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.7.7 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
demo:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
结果:
golang并发编程
协程
go task()
demo
import (
"fmt"
"time"
)
func show(msg string) {
for i := 0; i < 5; i++ {
fmt.Printf("msg: %v\n", msg)
time.Sleep(time.Millisecond * 100)
}
}
普通函数运行:
func main() {
show("Go")
show("Hi")
// msg: Go
// msg: Go
// msg: Go
// msg: Hi
// msg: Hi
// msg: Hi
}
其中一个以协程方式执行:
func main() {
go show("Go") // go 启动了一个协程来执行
show("Hi")
// msg: Hi
// msg: Go
// msg: Hi
// msg: Go
// msg: Go
// msg: Hi
}
两个都以协程方式执行:
func main() {
go show("Go") // go 启动了一个协程来执行
go show("Hi")
// 什么都没输出,因为还没开始打印main函数已经退出了
}
可以在main最后等待一段时间:
func main() {
go show("Go")
go show("Hi")
time.Sleep(time.Second * 2)
// msg: Hi
// msg: Go
// msg: Go
// msg: Hi
// msg: Hi
// msg: Go
}
channel
通道,在goroutine之间共享数据。
需要在声明通道时指定数据类型。在任何给定时间只有一个协程可以访问数据项。
两种通道:
- 无缓冲,用于协程之间的同步通信,保证发送和接收瞬间执行两个协程的交换。
- 缓冲,异步通信。
unBufferd := make(chan int) // 整型无缓冲通道
buffered := make(chan int, 10) //整型有缓冲通道
发送值和接收值:
unBufferd <- 4
data := <-unBufferd
// 当然,不能再一个协程里写,无缓冲通道会死锁
// fatal error: all goroutines are asleep - deadlock!
fmt.Printf("data: %v\n", data)
有缓冲就没问题了:
buffered <- 4
data := <-buffered
fmt.Printf("data: %v\n", data) // data: 4
完整的小例子测试:
var values = make(chan int)
func send() {
rand.Seed(time.Now().UnixNano())
value := rand.Intn(10)
fmt.Printf("send: %v\n", value)
time.Sleep(time.Second * 5)
values <- value
}
func main() {
defer close(values) // 主函数退出的函数把通道关了
go send() // 启动一个协程
fmt.Println("waiting...")
value := <-values
fmt.Printf("value: %v\n", value)
fmt.Println("end...")
}
// waiting...
// send: 7 阻塞几秒
// value: 7
// end...
waitGroup同步
sync.WaitGroup内部维护一个计数,初始值为0。
保证在并发环境中完成指定数量的任务。
没有同步情况:
import (
"fmt"
"time"
)
func show(msg string) {
for i := 0; i < 5; i++ {
fmt.Printf("msg: %v\n", msg)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
go show("Go")
go show("Hi")
// 什么都没输出,因为还没开始打印main函数已经退出了
}
同步情况:
var wg sync.WaitGroup
func show(msg string) {
defer wg.Done()
for i := 0; i < 3; i++ {
fmt.Printf("msg: %v\n", msg)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
wg.Add(1)
go show("Go")
wg.Add(1)
go show("Hi")
wg.Wait()
fmt.Println("end...")
}
// msg: Hi
// msg: Go
// msg: Go
// msg: Hi
// msg: Hi
// msg: Go
// end...
runtime包
定义了一些协程管理相关的API
runtime.Gosched()
让出CPU时间片,重新等待安排任务
例子:
func show(msg string) {
for i := 0; i < 2; i++ {
fmt.Printf("msg: %v\n", msg)
}
}
func main() {
go show("Go")
for i := 0; i < 2; i++ {
runtime.Gosched() // 让出时间片
fmt.Println("golang....")
}
fmt.Println("end...")
}
runtime.Goexit()
退出当前协程。
runtime.GOMAXPROCS
设置能最多运行的CPU核心数。
fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())
runtime.GOMAXPROCS(2)
Mutex互斥锁
测试:
var value int = 0
func add() {
// 当次数小的时候,出现问题的概率小,当大的时候,如十万,百万,最后的结果就出现了问题
for i := 0; i < 100000; i++ {
value += 1
}
}
func main() {
go add()
go add()
go add()
time.Sleep(1 * time.Second)
fmt.Printf("value: %v\n", value)
// value: 127431
// value: 117896
}
加锁之后:
var value int = 0
var value_lock sync.Mutex
func add() {
for i := 0; i < 100000; i++ {
value_lock.Lock()
value += 1
value_lock.Unlock()
}
}
func main() {
go add()
go add()
go add()
time.Sleep(1 * time.Second)
fmt.Printf("value: %v\n", value)
// value: 300000
}
channel遍历
通道关闭读到默认值,通道未关闭,读的多了,死锁情况如下:
import "fmt"
var c = make(chan int)
func main() {
go func() {
for i := 0; i < 2; i++ {
c <- i
}
close(c)
}()
r := <-c
fmt.Printf("r: %v\n", r)
r = <-c
fmt.Printf("r: %v\n", r)
r = <-c // channel已经关闭了,读到0;未关闭,死锁
fmt.Printf("r: %v\n", r)
}
fori循环遍历:
for i := 0; i < 2; i++ {
r := <-c
fmt.Printf("r: %v\n", r)
}
for range:
for v := range c {
fmt.Printf("v: %v\n", v)
}
循环判断状态:
for {
v, ok := <-c
if ok {
fmt.Printf("v: %v\n", v)
} else {
break
}
}
select switch
select 会监听case语句中channel的读写操作,当case中的channel读写操作为非阻塞状态,会触发相应的动作。
select中case语句必须是一个channel操作
select中的default子句总是可运行的
如果多个case可写,随机公平地选一个执行,其他不会执行。
如果没有可运行的case语句,且有default,就执行default。
如果没有可执行的case语句,且没有default,就阻塞。
demo
var chanInt = make(chan int, 0)
var chanStr = make(chan string)
func main() {
go func() {
chanInt <- 1
chanStr <- "Hi"
defer close(chanInt)
defer close(chanStr)
}()
for {
select {
case r := <-chanInt:
fmt.Printf("chanInt: %v\n", r)
case r := <-chanStr:
fmt.Printf("chanStr: %v\n", r)
default:
fmt.Println("waiting...")
}
time.Sleep(time.Second * 1)
}
// chanInt: 1
// chanStr: Hi
// chanStr:
// chanStr:
// chanInt: 0
// chanStr:
// chanStr:
// chanInt: 0
// chanInt: 0
}
Timer
内部也是通过管道实现的。
demo1:
func main() {
timer := time.NewTimer(time.Second * 2)
fmt.Printf("time.Now(): %v\n", time.Now())
t1 := <-timer.C // C是channel, 阻塞,直至时间到了
fmt.Printf("t1: %v\n", t1)
// time.Now(): 2022-04-18 13:10:11.9222806 +0800 CST m=+0.003442901
// t1: 2022-04-18 13:10:13.9367907 +0800 CST m=+2.017953001
}
demo2:
func main() {
fmt.Printf("time.Now(): %v\n", time.Now())
<-time.After(time.Second * 2)
fmt.Printf("time.Now(): %v\n", time.Now())
// time.Now(): 2022-04-18 13:14:36.3144857 +0800 CST m=+0.003912601
// time.Now(): 2022-04-18 13:14:38.329183 +0800 CST m=+2.018609901
}
time.After和time.Sleep区别:
func main() {
fmt.Printf("time.Now()1: %v\n", time.Now())
t := time.After(time.Second * 2)
fmt.Printf("time.Now()2: %v\n", time.Now())
time.Sleep(time.Second * 5)
fmt.Printf("(<-t)3: %v\n", (<-t))
// time.Now()1: 2022-04-18 13:18:47.8695158 +0800 CST m=+0.003094101
// time.Now()2: 2022-04-18 13:18:47.8834527 +0800 CST m=+0.017031001
// (<-t)3: 2022-04-18 13:18:49.8910131 +0800 CST m=+2.024591401
}
Sleep就阻塞在那,After非阻塞,等到从通道里取值的时候才:没到时间就阻塞,否则就立刻返回。
停止计时器:
func main() {
timer := time.NewTimer(time.Second * 1)
go func() {
<-timer.C
fmt.Println("time up...")
}()
timer.Stop() // 取消计时器
fmt.Println("time stop...")
time.Sleep(time.Second * 3)
}
time Reset
重新设置时间。
Ticker
Timer只执行一次,Ticker周期执行。
简单使用:
func main() {
ticker := time.NewTicker(time.Second)
counter := 0
for _ = range ticker.C {
counter++
fmt.Println("Ticker...") // 1s周期运行
if counter == 5 {
ticker.Stop()
break
}
}
}
和通道一起使用:
func main() {
chanInt := make(chan int)
ticker := time.NewTicker(time.Second)
go func() {
for _ = range ticker.C {
select {
case chanInt <- 1:
case chanInt <- 2:
case chanInt <- 3:
}
}
}()
sum := 0
for v := range chanInt {
fmt.Printf("v: %v\n", v)
sum += v
if sum >= 10 {
break
}
}
}
// v: 2
// v: 3
// v: 1
// v: 3
// v: 1
原子变量
对一个简单的变量实现同步,一种方法是加锁,另一种方式就是使用原子变量。
原子变量计数器:
var wg sync.WaitGroup
var v int32 = 0
// CAS操作
func add() {
defer wg.Done()
atomic.AddInt32(&v, 1)
}
func main() {
for i := 0; i < 100000; i++ {
wg.Add(1)
go add()
}
wg.Wait()
fmt.Printf("v: %v\n", v)
}
其他操作(也适用于其他类型):
atomic.LoadInt32(&i)
atomic.StoreInt32(&i,100)
atomic.CompareAndSwapInt32(&i,100,101)
标准库
os模块
文件目录相关
创建文件:
func createFile() {
// 没有就创建文件,有就覆盖
f, err := os.Create("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
// 打印文件名称
fmt.Printf("f.Name(): %v\n", f.Name())
}
}
创建目录:
func makeDir() {
err := os.Mkdir("a", os.ModePerm)
// 创建多级目录
err2 := os.MkdirAll("a/b/c", os.ModePerm)
if err != nil {
fmt.Printf("err: %v\n", err)
}
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
}
删除文件或目录:
func remove() {
err := os.Remove("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
// 删除目录
err2 := os.RemoveAll("a")
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
}
查看和切换当前目录:
func wd() {
dir, _ := os.Getwd()
fmt.Printf("dir: %v\n", dir) // dir: C:\Users\PC\Desktop\go_pro
os.Chdir("d:\\")
dir, _ = os.Getwd()
fmt.Printf("dir: %v\n", dir) // dir: d:\
}
临时目录:
os.TempDir()
重命名文件:
os.Rename
简单读写文件:
func readFile() {
b, _ := os.ReadFile("a.txt")
fmt.Printf("b: %v\n", string(b[:]))
}
func writeFile() {
_ = os.WriteFile("a.txt", []byte("Hello world"), os.ModePerm)
}
File文件读操作
读文件
func openClose() {
// 只读打开
f, err := os.Open("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
var b []byte = make([]byte, 20)
f.Read(b)
fmt.Printf("b: %v\n", string(b[:])) // b: Hello world
f.Close()
}
OpenFile可以进行更细致的一些操作:
func openClose() {
f, err := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("f.Name(): %v\n", f.Name())
f.Close()
}
}
持续读取,直至文件尾:
func openClose() {
buf := make([]byte, 3)
f, _ := os.Open("a.txt")
for {
n, err := f.Read(buf)
if err == io.EOF {
break
}
if n < 3 {
buf[2] = 0
}
fmt.Printf("string(buf): %v\n", string(buf))
}
}
// string(buf): Hel
// string(buf): lo
// string(buf): wor
// string(buf): ld
读取子目录:
简单读取:
func openClose() {
de, _ := os.ReadDir("a")
for _, v := range de {
fmt.Printf("v.IsDir(): %v\n", v.IsDir())
fmt.Printf("v.Name(): %v\n", v.Name())
}
// v.IsDir(): true
// v.Name(): b
// v.IsDir(): true
// v.Name(): d
}
文件树的形式:
func openClose(str string, prefix string) {
de, err := os.ReadDir(str)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
for _, v := range de {
if !v.IsDir() {
fmt.Printf("%v%v%v\n", prefix[:maxInt(0, len(prefix)-4)], "|-- ", v.Name())
} else {
fmt.Printf("%v%v\n", prefix, v.Name())
openClose(str+"/"+v.Name(), prefix+" ")
}
}
}
File文件写操作
func write() {
f, err := os.OpenFile("a.txt", os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
f.Write([]byte("Hello world!"))
f.WriteString("\nanother Line")
f.Close()
}
进程相关操作
func main() {
// 获取当前进程的进程id
fmt.Printf("os.Getpid(): %v\n", os.Getpid())
// 获取父进程id
fmt.Printf("os.Getppid(): %v\n", os.Getppid())
// 设置新进程属性
attr := &os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
Env: os.Environ(),
}
// 开启新进程
p, err := os.StartProcess("C:\\Program Files (x86)\\Notepad++\\notepad++.exe", []string{"C:\\Program Files (x86)\\Notepad++\\notepad++.exe"}, attr)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Printf("p: %v\n", p)
fmt.Printf("p.Pid: %v\n", p.Pid)
// 根据进程id找进程
p2, _ := os.FindProcess(p.Pid)
fmt.Printf("p2: %v\n", p2)
// 设置10s后执行一个函数
time.AfterFunc(time.Second*10, func() {
p.Signal(os.Kill)
})
ps, _ := p.Wait()
fmt.Printf("ps: %v\n", ps)
}
os包和环境相关的方法
获取所有环境变量:os.Environ()
获取某个环境变量:os.Getenv()
查找某个环境变量:os.LookupEnv()
io模块
- io:提供基本的接口
- io/ioutil封装一些使用的io函数
- fmt,io格式化
- bufio 实现带缓冲的io
基本接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
demo1:
func main() {
r := strings.NewReader("hello world")
buf := make([]byte, 10)
r.Read(buf)
fmt.Printf("string(buf): %v\n", string(buf)) // string(buf): hello worl
}
demo2:
copy到输出控制台。
r := strings.NewReader("hello world")
if _, err := io.Copy(os.Stdout, r); err != nil {
log.Fatal(err)
}
// hello world
带缓冲区的复制:
r := strings.NewReader("hello world")
buf := make([]byte, 20)
// 带缓冲区复制,效率更高
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
log.Fatal(err)
}
// hello world
多个reader:
r1 := strings.NewReader("first reader ")
r2 := strings.NewReader("second reader ")
r3 := strings.NewReader("third reader\n")
r := io.MultiReader(r1, r2, r3)
buf := make([]byte, 20)
// 带缓冲区复制,效率更高
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
log.Fatal(err)
}
// first reader second reader third reader
管道:
io.pipe()
ioutil包
读字符串reader:
r := strings.NewReader("hi hihihi hi")
b, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Printf("string(b): %v\n", string(b)) // string(b): hi hihihi hi
读文件reader:
f, _ := os.Open("a.txt")
b, err := ioutil.ReadAll(f)
ioutil.ReadDir()
func main() {
fi, err := ioutil.ReadDir(".")
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
for _, v := range fi {
fmt.Printf("%v\n", v.Name())
}
// a
// a.txt
// go.mod
// go.sum
// main.go
// user
}
ioutil.ReadFile()
b, err := ioutil.ReadFile("a.txt")
writeFile()
// 覆盖原内容
ioutil.WriteFile("a.txt", []byte("ioutil writefile"), 0755)
临时文件:
content := []byte("temp file content")
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name())
fmt.Printf("tmpfile.Name(): %v\n", tmpfile.Name())
// tmpfile.Name(): C:\Users\PC\AppData\Local\Temp\example1840979046
if _, err := tmpfile.Write(content); err != nil {
log.Fatal(err)
}
tmpfile.Close()
bufio
多了一个缓冲:
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
lastByte int // last byte read for UnreadByte; -1 means invalid
lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}
例子:
func main() {
r := strings.NewReader("ddddddddddfasdf")
r2 := bufio.NewReader(r)
s, _ := r2.ReadString('\n')
fmt.Printf("s: %v\n", s) // s: ddddddddddfasdf
}
rune 英[ruːn]
非英文字符读取:
func main() {
r := strings.NewReader("我的博客")
r2 := bufio.NewReader(r)
r3, size, _ := r2.ReadRune()
fmt.Printf("%c %v\n", r3, size)
r3, size, _ = r2.ReadRune()
fmt.Printf("%c %v\n", r3, size)
r3, size, _ = r2.ReadRune()
fmt.Printf("%c %v\n", r3, size)
r3, size, _ = r2.ReadRune()
fmt.Printf("%c %v\n", r3, size)
r3, size, _ = r2.ReadRune()
fmt.Printf("%c %v\n", r3, size)
// 我 3
// 的 3
// 博 3
// 客 3
// 0
}
Scanner
r := strings.NewReader("ABC DEF GHI JKL")
s := bufio.NewScanner(r)
s.Split(bufio.ScanWords)
for s.Scan() {
fmt.Printf("%v\n", s.Text())
}
// ABC
// DEF
// GHI
// JKL
log
panic
:还是会执行defer
func main() {
defer fmt.Println("panic之后再执行")
log.Panic("发生了panic异常")
fmt.Println("end...")
}
// 2022/04/18 21:12:22 发生了panic异常
// panic之后再执行
// panic: 发生了panic异常
// goroutine 1 [running]:
// log.Panic({0xc000107f30?, 0x60?, 0xff7be0?})
// D:/go1.18/src/log/log.go:385 +0x65
// main.main()
// c:/Users/PC/Desktop/go_pro/main.go:10 +0x94
// exit status 2
配置
func main() {
log.Print(log.Flags())
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Print(log.Flags())
// 2022/04/18 21:17:18 3
// 2022/04/18 21:17:18 main.go:10: 19
}
整合
搞成一个初始化函数:
func init() {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetPrefix("mylog: ")
f, _ := os.OpenFile("a.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
log.SetOutput(f)
}
func main() {
log.Print("dns")
log.Print("haha")
log.Panic("123")
}
自定义logger
var diLogger *log.Logger
func init() {
f, _ := os.OpenFile("a.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
diLogger = log.New(f, "Mylog: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
diLogger.Print("dns")
log.Print("haha") // 二者的输出互不影响
diLogger.Panic("123")
}
builtin
内建,不需要导入,里面的变量和函数可以直接使用。
一些函数
s := []int{}
// 添加元素
s = append(s, 100)
fmt.Printf("s: %v\n", s) // s: [100]
// 获取字符串、切片、数组长度
len := len(s)
fmt.Printf("len: %v\n", len)
print("tom", " ", 20, "\n")
// 抛出一个panic异常, 程序退出,defer还会照常执行
panic("something wrong...")
new
和make
区别:
make
只能用来分配和初始化类型为slice
,map
,chan
的数据,new
可以分配任意类型的数据。new
返回的是指针,make
返回本身new
分配的空间会被清零,make
分配后会进行初始化