单页面营销型网站制作,市场营销实务,世界上让导航崩溃的城市,wordpress好学1. 系统命令调用 所谓的命令调用#xff0c;就是通过os#xff0c;找到系统中编译好的可执行文件#xff0c;然后加载到内存中#xff0c;变成进程。 1.1 exec.LookPath#xff08;寻找命令#xff09; 作用#xff1a; exec.LookPath 函数用于在系统的环境变量中搜索可…1. 系统命令调用 所谓的命令调用就是通过os找到系统中编译好的可执行文件然后加载到内存中变成进程。 1.1 exec.LookPath寻找命令 作用 exec.LookPath 函数用于在系统的环境变量中搜索可执行文件的路径。这个函数属于 os/exec 包通常用于查找并执行系统命令。 语法 func exec.LookPath(file string) (string, error) 参数 file string要查找的可执行文件的名称。 返回值 string该返回值是可执行文件的完整路径如果找到了的话。 error该返回值是一个错误对象如果搜索过程中出现错误则返回非 nil 值。 package mainimport (fmtos/exec
)func main() {// 通过LookPath在系统PATH中找命令s, err : exec.LookPath(git)if err ! nil {panic(err)} else {fmt.Println(命令在path中找到)}
}
调试结果
命令在path中找到 1.2 exec.Command构造要执行的命令 作用 exec.Command 函数用于创建一个要执行的命令的 *exec.Cmd 结构体但并不执行。 语法 func exec.Command(name string, arg ...string) *exec.Cmd 参数 name string要执行的可执行文件的名称。如果该文件不在系统的 PATH 环境变量中你需要提供完整的路径。 arg ...string一个字符串切片表示传递给命令的参数。第一个参数通常是子命令或命令的主要用途后续参数是该命令的其他选项或参数。 返回值 *exec.Cmd返回一个 *exec.Cmd 结构体它表示要执行的命令。 package mainimport (fmtos/exec
)func main() {// 通过LookPath在系统PATH中找命令s, err : exec.LookPath(git)if err ! nil {panic(err)}// 构造要执行的命令// c : exec.Command(s, -v) // git -v// 或者还可以这样c : exec.Command(s, []string{-v}...)fmt.Println(c) // 打印执行的命令fmt.Println(c.Args) // 打印命令的参数列表
}
调试结果
D:\软件安装\Git\Git\cmd\git.exe -v
[D:\软件安装\Git\Git\cmd\git.exe -v]
1.3 Output执行命令并输出结果 作用 Output 方法用于执行命令并捕获其标准输出。这个方法会启动命令等待命令完成并返回命令的输出。 语法 func (*exec.Cmd).Output() ([]byte, error) 返回值 []byte输出结果。 error如果执行过程中遇到错误会返回错误信息。 package mainimport (fmtos/exec
)func main() {// 通过LookPath在系统PATH中找命令s, err : exec.LookPath(git)if err ! nil {panic(err)}// 构造要执行的命令c : exec.Command(s, -v) // git -v// 或者还可以这样// c : exec.Command(s, []string{-v}...)fmt.Println(c) // 打印执行的命令fmt.Println(c.Args) // 打印命令的参数列表// 执行命令并输出结果b, err2 : c.Output()if err2 ! nil {panic(err2)}fmt.Println(b)fmt.Printf(b%v\nstring(b)%v, b, string(b))
}
调试结果
D:\软件安装\Git\Git\cmd\git.exe -v
[D:\软件安装\Git\Git\cmd\git.exe -v]
[103 105 116 32 118 101 114 115 105 111 110 32 50 46 52 52 46 48 46 119 105 110 100 111 119 115 46 49 10]
b[103 105 116 32 118 101 114 115 105 111 110 32 50 46 52 52 46 48 46 119 105 110 100 111 119 115 46 49 10]
string(b)git version 2.44.0.windows.1
2. 日志
2.1 log包
Go标准库中有log包提供了简单的日志功能。
日志输出需要使用日志记录器Logger。 2.1.1 log.Print 作用 输出日志信息。 语法 func log.Print(v ...any) 参数 v ...any表示接受任意数量的参数并且每个参数可以是任何类型多参数用逗号分隔。 package mainimport (fmtlog
)func main() {log.Print(xxxxx)fmt.Println(xxxx)
} 这里在ide看的比较明显log.Print默认打印出来的是含年月日时分秒的且是红色字体这是因为它用的是stderr标准错误输出。
而fmt.Print是标准输出stdout所以是蓝色字体。
但是颜色仅仅在ide有显示编译后执行或linux系统中是无颜色的。
2.1.2 log.Fatal 作用 用于输出日志信息并且在输出日志信息后会调用 os.Exit(1) 来终止程序的运行。 语法 func log.Fatal(v ...any) 参数 v ...any任意数量任意类型的参数多个参数用逗号分隔。 2.1.3 log.Panic 作用 用于输出日志信息并在输出后触发一个 panic这会导致程序立即停止运行并开始 panic 恢复机制。 语法 func log.Panic(v ...any) 参数同上。 package mainimport (log
)func main() {log.Panic(log.Panic test!!!)
}
2.2 log.Print系列源码讲解
2.2.1 日志记录器Logger var std New(os.Stderr, , LstdFlags)注意这个std虽然是个全局变量但小写包外不可见的。
上面我们用的log.Print系列都是调用的这个std对象但该std既不是标准输出也不是标准错误输出而是一个标准logger。
因为日志的输出是需要使用日志记录器Logger的通过Logger才能输出日志并不是直接print就行了。
Logger日志记录器也叫缺省日志记录器。
var std New(os.Stderr, , LstdFlags)含义
1New这是 log 包中的一个函数用于创建一个新的 log.Logger 对象logger实际是一个结构体。
2os.Stderr这是标准库 os 包中的一个变量表示标准错误输出stderr。日志信息会被输出到这个标准错误流。
3这是 New 函数的第二个参数表示日志消息前缀。这里传入空字符串意味着日志消息前不会有额外的前缀。
4LstdFlags这是 log 包中定义的一组日志标志位用于控制日志输出的格式。LstdFlags 通常包括时间戳、日志级别等信息。
这里注意LstdFlags点击它看下源码
const (// 将日志的日期设置为当地时间格式为 2009/01/23。这是通过左移操作符和 iota 关键字实现Ldate 1 iota // the date in the local time zone: 2009/01/23// 将日志的时间设置为当地时间格式为 01:23:23。Ltime // the time in the local time zone: 01:23:23// 在时间中包含微秒格式为 01:23:23.123123,这个选项假设 Ltime 已经被设置。Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.// 在日志中包含完整的文件名和行号例如 /a/b/c/d.go:23。Llongfile // full file name and line number: /a/b/c/d.go:23// 在日志中包含文件名的最后一个元素和行号例如 d.go:23。如果设置了这个选项它会覆盖 Llongfile。Lshortfile // final file name element and line number: d.go:23. overrides Llongfile// 如果设置了 Ldate 或 Ltime使用协调世界时UTC而不是本地时区。LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone// 将日志前缀从行的开始移动到消息之前。Lmsgprefix // move the prefix from the beginning of the line to before the message// 是一个组合标志设置了日志的初始值这里组合了 Ldate 和 Ltime。LstdFlags Ldate | Ltime // initial values for the standard logger
)
上表列出的方法底层都使用std.Output输出日志内容。而std本质上是使用了标准错误输出、无前缀、 LstdFlags标准标记的记录器Logger实例。
2.3 std(标准输出)使用
package mainimport (log
)func main() {log.Print(log.Print)log.Printf(log.Printf)log.Println(log.Println)// 等价于log.Print(log.Fatal);os.Exit(1)log.Fatal(log.Fatal)// 等价于log.Println(xxx); panic()panic的退出状态码为2log.Panicln(log.Panicln)
}
调试结果
2024/09/29 11:44:14 log.Print
2024/09/29 11:44:14 log.Printf
2024/09/29 11:44:14 log.Println
2024/09/29 11:44:14 log.Fatal
2.4 自定义Logger
2.4.1 标准输出
package mainimport (logos
)func main() {// stdout: 标准输出logger : log.New(os.Stdout, , log.LstdFlags)logger.Println(这是自定义的标准日志输出)
}
调试结果
2024/09/29 13:56:22 这是自定义的标准日志输出
2.4.2 标准错误输出
package mainimport (logos
)func main() {// stderr: 标准错误输出// log.LstdFlags: 年月日时分秒// log.Lshortfile在日志中包含完整的文件名和行号logger1 : log.New(os.Stderr, 日志前缀, log.LstdFlags|log.Lshortfile)// Fatalln: Printlnos.Exit(1)logger1.Fatalln(自定义的标准错误输出。)
}
调试结果
日志前缀2024/09/29 14:03:21 main.go:15: 自定义的标准错误输出。
Process 3312 has exited with status 1
这里注意两个地方
1日志前缀2024/09/29 14:03:21 main.go:15: 自定义的标准错误输出。
这里明确的显示了错误的地方在main.go:15。
2status 1
退出状态码为1。 然后还可以配置flag调整日志前缀的位置。
package mainimport (logos
)func main() {// stderr: 标准错误输出// log.LstdFlags: 年月日时分秒// log.Lshortfile在日志中包含完整的文件名和行号logger1 : log.New(os.Stderr, 日志前缀, log.LstdFlags|log.Lshortfile)// Fatalln: Printlnos.Exit(1)logger1.Println(自定义的标准错误输出。)logger2 : log.New(os.Stderr, 日志前缀, log.LstdFlags|log.Lshortfile|log.Lmsgprefix)logger2.Fatalln(自定义的标准错误输出。)
}
调试结果
日志前缀2024/09/29 14:23:42 main.go:15: 自定义的标准错误输出。
2024/09/29 14:23:42 main.go:18: 日志前缀自定义的标准错误输出。
2.5 日志持久化
package mainimport (logos
)func main() {logfile : D:/个人/学习/Go/文件与目录操作/test.logf, err : os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.ModePerm)if err ! nil {log.Panic(err)}defer f.Close()l : log.New(f, , log.LstdFlags|log.Lshortfile)l.Println(日志持久化文件测试!)
}3. zerolog
官网https://zerolog.io/ 上面介绍的log模块太简陋了实际使用并不方便。 常用的三方日志模块如下 1logrus有日志级别、Hook机制、日志格式输出很好用。 2zap是Uber的开源高性能日志库。 3zerolog更注重开发体验高性能、有日志级别、链式APIjson格式日志记录号称0内存分配。 3.1 下载
go get -u github.com/rs/zerolog/log
3.2 简单日志示例
对于简单的日志记录导入全局记录器包github.com/rs/zerolog/log
package main// 注意这个包的自动引入的话需要先zerolog等import github.com/rs/zerolog出来后再自动补充后面的/log
import github.com/rs/zerolog/logfunc main() {log.Print(zerolog/log Print test)log.Fatal().Msg(zerolog/log Fatal test)// 这个也是 os.Exit(1)
}
调试结果
{level:debug,time:2024-09-29T17:28:0908:00,message:zerolog/log Print test}
{level:fatal,time:2024-09-29T17:28:0908:00,message:zerolog/log Fatal test}
3.3 日志级别 zerolog提供以下级别从高到底 panic (zerolog.PanicLevel, 5)fatal (zerolog.FatalLevel, 4)error (zerolog.ErrorLevel, 3)warn (zerolog.WarnLevel, 2)info (zerolog.InfoLevel, 1)debug (zerolog.DebugLevel, 0)trace (zerolog.TraceLevel, -1) 且级别还分为 gLevel全局级别。 zerolog.SetGlobalLevel(级别数字或常量) 来设置全局级别。 zerolog.GlobalLevel() 获取当前全局级别。每个Logger的级别日志记录器级别。消息的级别Msg。 package mainimport (github.com/rs/zerolog/log
)func main() {// debug级别的消息相当于log.Debug().Msg()log.Print(zerolog/log Print test)// fatal级别的消息结合了os.Exit(1)log.Fatal().Msg(zerolog/log Fatal test)// Panic级别的消息log.Panic().Msg(zerolog/log Panic test)
}3.3.1 尝试先理解“级别”
首先是我们使用的Zeerolog包中的log.xxx这种它其实是一个缺省默认的的logger比如log.Print点这个Print看下源码
func Print(v ...interface{}) {Logger.Debug().CallerSkipFrame(1).Msg(fmt.Sprint(v...))
}
然后再点击Logger跳转到下一层源码
var Logger zerolog.New(os.Stderr).With().Timestamp().Logger()
可以看到这里定义了一个全局的且包外可见的Logger我们之前使用的zerrolog包中的log.xxx实际调用的都是这个缺省Logger。
我们可以尝试着调用一下这个Logger
package mainimport (fmtgithub.com/rs/zerolog/log
)func main() {fmt.Println(log.Logger)
}
调试结果
{{0xc00000a020} -1 nil [123] [{}] false nil}可以看到log.Logger缺省logger的默认级别为 -1 trace。
那可以尝试自定义一下这个默认的Logger日志记录器级别
package mainimport (fmtgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {fmt.Println(log.Logger)// 自定义Logger级别记录器级别log1 : log.Logger.Level(zerolog.InfoLevel)fmt.Println(log1)}
调试结果
{{0xc00000a020} -1 nil [123] [{}] false nil}
{{0xc00000a020} 1 nil [123] [{}] false nil}
可以看到上面自定义的级别已经从原来的-1变成1了。 接下来试试-1和1在一起打印。
package mainimport (fmtgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {fmt.Println(log.Logger)// 自定义Logger级别记录器级别log1 : log.Logger.Level(zerolog.InfoLevel)fmt.Println(log1)log.Debug().Msg(log.Debug相当于是log.Print) // 缺省logger// 这一条不会打印// 该消息是debug级别通过log1这个日志记录器输出log1.Debug().Msg(自定义日志级别的logger)
}
调试结果
{{0xc00000a020} -1 nil [123] [{}] false nil}
{{0xc00000a020} 1 nil [123] [{}] false nil}
{level:debug,time:2024-09-30T15:10:1508:00,message:log.Debug相当于是log.Print}为什么log1.Debug().Msg(自定义日志级别的logger)没有打印 这里就涉及到另一个级别了“消息级别”。
级别分为“记录器级别”和“消息级别Logger”我们使用log.xxx输出的都是“消息级别”那为啥上面的log1没有打印出来呢
输出成功的前提消息级别必须 日志记录器级别。
我们在log1中自定义的级别是记录器级别记录器级别为info但log1的消息级别为debug所以输出不了。
所以这里尝设置log1的消息级别为Info这样消息级别和记录器级别就相等了。
package mainimport (fmtgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {fmt.Println(log.Logger)// 自定义记录器日志级别log1 : log.Logger.Level(zerolog.InfoLevel)fmt.Println(log1)// 缺省logger的消息级别为debuglog.Debug().Msg(log.Debug相当于是log.Print)// 这一条不会打印因为log1的记录器日志级别为Infolog1.Debug().Msg(自定义日志级别的logger)log1.Info().Msg(调整log1的消息级别为Info)
}
调试结果
{{0xc00000a020} -1 nil [123] [{}] false nil}
{{0xc00000a020} 1 nil [123] [{}] false nil}
{level:debug,time:2024-09-30T15:41:3608:00,message:log.Debug相当于是log.Print}
{level:info,time:2024-09-30T15:41:3608:00,message:调整log1的消息级别为Info}
可以看到调整log1消息级别为Info后终于输出了。
3.3.2 zerolog
package mainimport (fmtgithub.com/rs/zerolog
)func main() {fmt.Println(zerolog.GlobalLevel())
}
调试结果
trace
可以看到zerolog默认的级别为trace也就是-1。
调整默认级别
package mainimport (fmtgithub.com/rs/zerolog
)func main() {// 3 Errorzerolog.SetGlobalLevel(zerolog.ErrorLevel)fmt.Println(zerolog.GlobalLevel())
}
调试结果
error
然后我们结合之前的代码
package mainimport (fmtgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {// 3 Errorzerolog.SetGlobalLevel(zerolog.ErrorLevel)fmt.Println(zerolog.GlobalLevel())fmt.Println(log.Logger)log1 : log.Logger.Level(zerolog.InfoLevel)fmt.Println(log1)log.Debug().Msg(log.Debug相当于是log.Print)log1.Debug().Msg(自定义日志级别的logger)log1.Info().Msg(调整log1的消息级别为Info)log1.Error().Msg(调整log1的消息级别为Error)
}
调试结果
error
{{0xc00000a020} -1 nil [123] [{}] false nil}
{{0xc00000a020} 1 nil [123] [{}] false nil}
{level:error,time:2024-09-30T17:02:2108:00,message:调整log1的消息级别为Error}
根据上述代码发现zerolog.SetGlobalLevel(zerolog.ErrorLevel)后原来log和log1的debug和info级别的消息都不打印了只有log1的error级别打印。
这说明SetGlobalLevel控制所有Logger的输出级别只有GlobalLevel的消息级别才能输出。
那也就是说消息级别 MAX(Logger记录器, GlobalLevel)才能最终输出。
3.3.3 消息与日志记录器 首先消息需要通过日志记录器才能输出不管是输出到屏幕还是文件。 如log1.Info().Msg()。 消息Msg()是Info级别Info()的通过log1这个日志记录器输出。 但消息输出的前提消息级别 MAX(Logger记录器此处是log1自己, GlobalLevel)才能最终输出。 3.4 上下文 zerolog是以Json对象格式输出的还可以自定义一些键值对字段增加到上下文中以输出。 3.4.1 自定义字段
package mainimport (github.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {zerolog.SetGlobalLevel(zerolog.InfoLevel)log.Info().Msg()
}
调试结果
{level:info,time:2024-10-08T11:45:2508:00}
从上面的输出可以看到Msg为空实际打印出来的消息体中也没有mssage。
那我们尝试着自定义一下
package mainimport (github.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {zerolog.SetGlobalLevel(zerolog.InfoLevel)log.Info().Str(School, magedu.com).Msg()log.Info().Str(School, magedu.com).Bool(Address, true).Msg()
}
调试结果
{level:info,School:magedu.com,time:2024-10-08T14:31:3408:00}
{level:info,School:magedu.com,Address:true,time:2024-10-08T14:31:3408:00}
可以看到可以通过Str来自定义内容且Str可以多个还可以使用其他数据类型。
3.5 错误日志
package mainimport (errorsgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {zerolog.SetGlobalLevel(zerolog.InfoLevel)err : errors.New(test error)log.Error().Msg(err.Error())log.Error().Err(err).Msg()log.Error().Err(err).Send()log.Fatal().Err(err).Send()log.Panic().Err(err).Send()
}
调试结果
{level:error,time:2024-10-09T10:29:5908:00,message:test error}
{level:error,error:test error,time:2024-10-09T10:29:5908:00}
{level:error,error:test error,time:2024-10-09T10:29:5908:00}
{level:fatal,error:test error,time:2024-10-09T10:29:5908:00}
package mainimport (errorsgithub.com/rs/zerologgithub.com/rs/zerolog/log // 全局logger
)func main() {zerolog.TimeFieldFormat zerolog.TimeFormatUnix // 自定义time字段时间的格式TimeFormatUnix时间戳// zerolog.ErrorFieldName err // 修改日志Json中的缺省字段名error// 错误日志err : errors.New(自定义的错误)log.Error(). // 错误级别消息Err(err). // err字段错误消息内容Send() // 有错误消息了message可以省略log.Fatal(). // fatal级别Err(err).Send()
}3.6 自定义全局logger
3.6.1 全局logger
// Logger is the global logger.
var Logger zerolog.New(os.Stderr).With().Timestamp().Logger() 默认使用是这样的
package mainimport (github.com/rs/zerolog/log
)func main() {log.Debug().Msg(debug)log.Error().Msg(Error)
}
调试结果
{level:debug,time:2024-10-09T10:44:3308:00,message:debug}
{level:error,time:2024-10-09T10:44:3308:00,message:Error}
3.6.2自定义全局Logger
不建议直接修改默认的全局logger如果有需求最好自定义一个。
package mainimport (github.com/rs/zerolog/log
)func main() {log.Logger log.With().Str(School, haha).Logger()log.Debug().Msg(debug)log.Error().Msg(Error)
}
调试结果
{level:debug,School:haha,time:2024-10-09T10:46:1108:00,message:debug}
{level:error,School:haha,time:2024-10-09T10:46:1108:00,message:Error}
package mainimport (github.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {zerolog.TimeFieldFormat zerolog.TimeFormatUnixlogger : log.With(). // With()返回基于全局Logger的子loggerStr(School, Magedu).Caller(). // 增加日志调用的位置信息字段Logger() // 返回Loggerlogger.Info().Send() // {level:info,School:Magedu,time:1223947070}log.Info().Send() // {level:info,time:1223947070} 全局Logger
}package mainimport (fmtosgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {zerolog.TimeFieldFormat zerolog.TimeFormatUnixlogger : zerolog.New(os.Stdout). // 不基于全局Logger重新构造了一个LoggerWith().Str(School, Magedu).Caller(). // 调用者信息增加日志函数调用的位置信息字段Logger(). // 返回LoggerLevel(zerolog.ErrorLevel) // 重新定义Logger级别为3 error返回Loggerfmt.Println(logger.GetLevel())logger.Info().Send() // {level:info,School:Magedu,time:1223947070}看颜色区别logger.Error().Send()log.Info().Send() // {level:info,time:1223947070} 全局Logger
}3.7 写入日志文件
3.7.1 只写入文件
package mainimport (fmtosgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {f, err : os.OpenFile(D:/个人/学习/Go/文件与目录操作/test.log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)if err ! nil {log.Panic().Err(err).Send()}defer f.Close()log1 : zerolog.New(f)log1.Info().Msg(测试写入)
}3.7.2 写入文件并输出到控制台
package mainimport (osgithub.com/rs/zerologgithub.com/rs/zerolog/log
)func main() {f, err : os.OpenFile(D:/个人/学习/Go/文件与目录操作/test.log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)if err ! nil {log.Panic().Err(err).Send()}defer f.Close()// log1 : zerolog.New(f)// log1.Info().Msg(测试写入)// 一分为二一边写文件一边控制台输出lw : zerolog.MultiLevelWriter(f, os.Stdout)log : zerolog.New(lw).With().Timestamp().Str(school, haha).Logger().Level(zerolog.InfoLevel)log.Info().Send()
}
调试结果
{level:info,school:haha,time:2024-10-09T17:46:1708:00} 4. 包管理 用任何语言来开发如果软件规模扩大会编写大量的函数、结构体、接口等代码这些代码不可能写在一个文件中这就会产生大量的文件。 如果这些文件杂乱无章就会造成名称冲突、重复定义、难以检索、无法引用、共享不便、版本管理等一系列问题。 如有一些功能模块如何复用如何共享方便其他项目使用。 所以一定要有模块化包管理解决以上诸多问题。 4.1 包 包由多个文件和目录组成。使用 package 包名 来定义包名。包名一般都采用小写符合标识符要求。当前目录名和 package 包名 中的包名不需要一致但最好保持一致。同级文件归属一个包就是说每个包目录的当前目录中只能统一使用同一个package的包名否则编译出错。 一般来说开发项目时可以把相关功能的代码集中放在某个包里面例如在main包目录中新建一个calc包将所有计算函数都放在其中以供别的代码调用。 同一个目录就是同一个包该包内go文件里的变量、函数、结构体互相可见可以直接使用。 跨目录就是跨包使用时需要导入别的包导入需要指定该包的路径。 4.1.1 包内可见演示
先在main.go文件中定义2个全局变量
package main// 定义一个小写全局变量包内(main包内)可见
var a 100// 大写全局变量包外(main包外)可见
var B 200func main() {}再在main.go文件同级目录中创建一个x.go文件并调用a和B变量
package mainimport fmt// 这个test函数也属于包内可见函数如果首字母大写那就是包外可见
func test() {fmt.Println(a, B)// 因为该文件也是属于main包的所以可以调用aB变量
}回到main.go文件中调用test函数
package main// 定义一个小写全局变量包内(main包内)可见
var a 100// 大写全局变量包外(main包外)可见
var B 200func main() {test()
}
调试结果
100 200 4.2 包管理的发展历程
4.2.1 GOPATH Go 1.11版本之前项目依赖包存于GOPATH。GOPATH是一个环境变量指向一个目录其中存放项 目依赖包的源码。 GOPATH默认值是 家目录/go 。 开发的代码放在 GOPATH/src 目录中编译这个目录的代码生成的二进制文件放到 GOPATH/bin 目录 下。 这会有以下问题 GOPATH不区分项目代码中任何import的路径均从GOPATH作为根目录开始。如果有多个项目不同项目依赖不同库的不同版本这就很难解决了。所有项目的依赖都放在GOPATH中很难知道当前项目的依赖项是哪些。 4.2.2 GOPATH vendor机制 Go1.5引入vendor机制。 vendor将项目依赖包复制到项目下的vendor目录在编译时使用项目下的vendor目录的包进行编译。 但依然不能解决不同项目依赖不同包版本问题。 该方式下的包搜索顺序 在当前包vendor目录查找向上级目录查找直到GOPATH/src/vendor目录 在GOPATH目录查找 在GOROOT目录查找标准库 4.2.3 Go Modules官方解决方案 Go Modules是从Go 1.11版本引入到1.13版本之后已经成熟Go Modules成为官方的依赖包管理解决方案。 优势 不受GOPATH限制代码可放在任意目录。自动管理和下载依赖且可以控制使用版本。不允许使用相对导入。 4.2.3.1 go modules 初始化
之前也演示过我们最开始写好代码后需要执行一段命令来初始化我们的代码
go mod init 自定义模块名
执行完后会在当前目录下生成一个go.mod文件且里面会包含我们使用的第三方库。这样每个目录都有自己的go.mod就不用担心冲突啥的。 如果想改模块名可以手动修改然后当前目录下所有文件都属于该模块。 5. Module模式
5.1 go mod命令 在Go1.11开始引入可以在任何目录使用go.mod创建项目。 go mod init name 命令在当前文件夹下初始化一个新的module, 创建go.mod文件。go mod tidy 命令自动分析依赖下载缺失的模块移除未使用的模块并更新go.mod文件。 5.1.1 go mod tidy演示
比如我下面这个代码引入了一些包 在go.mod中就是这样的 包括还有go.sum中也会生成内容 那如果我们不需要这些东西了该怎么办呢可以如下操作。
5.1.1.1 清理无用依赖 执行go mod tidy 5.1.1.2 恢复依赖 上面的报错是说这个包还在但是go.mod中没有所以没有办法使用。
解决办法如下 上面没有//indirect的是直接依赖有//indirect的是间接依赖就是直接依赖需要的依赖包。 5.2 导入子包
5.2.1 创建子目录和文件 package calc // 这个包名可以自定义但是建议和目录名一致。import fmt// 注意首字母大写才能包外可见
func Add(x, y int) int {fmt.Printf(这是calc.go里面打印的)return x y
}这里注意同一级目录中的.go文件package名必须一致。 5.2.2 在main包中调用calc.go中的Add函数
package mainimport (// 注意这个test一定要是go.mod中的module nametest/calc
)func main() {calc.Add(1, 2)
}
调试结果
这是calc.go里面打印的
5.2.3 在子包中创建子包
在子目录中创建子目录和代码文件 5.2.4 调用子包中的子包 5.3 import导入包
5.3.1 绝对导入
就是下面这种包都是绝对路径的。
package mainimport (// 注意这个test一定要是go.mod中的module nametest/calctest/calc/minus
)
5.3.2 别名导入
如果有两个导入的包冲突时可以重命名包来避免冲突
import m magedu.com/tools/calc/minus// 使用举例
m.Minus()
5.3.3 相对导入
不建议使用
import ./calc5.3.4 匿名导入
import _ magedu.com/tools/calc/minus 使用下划线作为别名就意味着无法使用了那其目的何在
这种情况下只能执行导入的包内的所有init函数了。主要作用是做包的初始化用。 5.4 导入本地私有包到其它项目 就是自己写的包想在其他项目用可以按照如下步骤进行导入。 5.4.1 创建模拟用的本地私有包
比如说我在这个目录下创建了新的代码文件 然后写了新的代码
package calcimport fmtfunc Multply(x, y int) int {fmt.Println(这是新的私有的calc包)return x * y
}
5.4.2 在mian.go中使用新的这个calc包
先在main.go中定义导入的包可以随便写一个包名
package mainimport (tools/abcd // 随便写
)
修改go.mod 但此时注意module test这里报错了 意思是在新的这个包路径中找不到go.mod所以需要初始化一下。
PS D:\个人\学习\Go\私有包\calc go mod init calc
go: creating new go.mod: module calc
go: to add module requirements and sums:go mod tidy
在main.go中使用这个新的私有包
package mainimport (c tools/abcd // c相当于是包的别名
)func main() {c.Multply(2, 3)
}
调试结果
这是新的私有的calc包
5.5 导入第三方包 这个不做演示了官放文档都有直接go get -u下载就行了。前面的文章也有演示百度也大把。 6. init函数
6.1 init函数的作用 init函数主要是配合匿名导入使用目的不是为了让你使用包内的资源而是运行该包所有的init。 所有的包中都可以定义init函数。 注意 init函数在本包内可以有n多个但是每个.go文件中只能有1个。同一个包内的多个init函数执行顺序是没有办法保证的。不同包的init函数的执行顺序由导入顺序决定init函数无参无返回值不能被其他函数调用。包中的init函数将在main函数之前自动执行。 6.2 代码演示
6.2.1 在子包中创建init函数
6.2.1.1 minus.go 6.2.1.2 calc.go 6.2.2 在main.go中调用
package mainimport (fmt_ test/calc_ test/calc/minus
)func main() {fmt.Println(这是main.go中的init函数测试)
}
调试结果
这是calc/calc.go中的init函数
这是calc/minus/minus.go中的init函数
这是main.go中的init函数测试
7. 反射 反射有很大的弊端这里略过了。 反射的弊端 代码难以阅读难以维护。编译期间不能发现类型错误覆盖测试难度很大有些Bug需要线上运行时才可能发现并造成严 重后果。反射性能很差通常比正常代码慢一到两个数量级。如果性能要求高时或反复调用的代码块里建 议不要使用反射。 反射主要应用场合就是写库或框架一般用不到再一个面试时候极低概率被问到。