常用库函数

38 阅读11分钟

Golang标准库文档Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)

strings包

go无法直接对字符串进行操作,也就是说无法像操作一个数组一样来操作string,不过go提供了strings包来帮助我们完成对string的操作

index系

用于获取字符串中的指定字符索引

 //返回子串sep在字符串s中第一次出现的索引值,不在的话返回-1.
 func Index(s, sep string) int
 //chars中任何一个Unicode代码点在s中首次出现的位置,不存在返回-1
 func IndexAny(s, chars string) int
 //查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
 func IndexFunc(s string, f func(rune) bool) int   //rune类型是int32别名,UTF-8字符格式编码。
 //返回字符c在s中第一次出现的位置
 func IndexByte(s string, c byte) int   //byte是字节类型
 // Unicode 代码点 r 在 s 中第一次出现的位置
 func IndexRune(s string, r rune) int
 ​
 // LastIndex系列
 //查找最后一次出现的位置,含义与之前差不多
 func LastIndex(s, sep string) int
 func LastIndexByte(s string, c byte) int
 func LastIndexAny(s, chars string) int
 func LastIndexFunc(s string, f func(rune) bool) int

contain系

用于查找子串str是否在指定的字符串中

 // 子串substr在s中,返回true
 func Contains(s, substr string) bool
 // chars中任何一个Unicode代码点在s中,返回true  
 func ContainsAny(s, chars string) bool
 // Unicode代码点r在s中,返回true
 func ContainsRune(s string, r rune) bool
 ​
 // 例如
 fmt.Println(strings.Contains("TigerwolfC", "wolf")) //true
 fmt.Println(strings.Contains("TigerwolfC", "bar"))  //false
 fmt.Println(strings.Contains("TigerwolfC", ""))     //true
 fmt.Println(strings.Contains("", ""))               //true 特别注意!!!
 fmt.Println(strings.Contains("我是中国人", "我"))         //true
 ​
 str := "Hello World"
 fmt.Println(strings.ContainsAny(str, "W"))   // true
 fmt.Println(strings.ContainsAny("foo", ""))  // false
 fmt.Println(strings.ContainsAny(str, "def")) // true
 fmt.Println(strings.ContainsAny("", ""))     // false

count系

用于判断字符 substr 在字符串 s 中总共出现的次数,如果不存在返回0

 //子串在s字符串中出现的次数
 func Count(s, sep string) int
 ​
 // 例如
 str := "Hello World"
 fmt.Println(strings.Count(str, "e")) //output: 1
 str1 := "Hello Weed"
 fmt.Println(strings.Count(str1, "e")) //output: 3
 str2 := "Hello World"
 fmt.Println(strings.Count(str2, "p")) //output: 0

split系

用于分割字符串

 // 以sep为分隔符将字符串分割为多个子字符串(不保留分割符)
 func Split(s, sep string) []string
 // 以sep为分隔符将字符串分割为多个子字符串(保留分割符)
 func SplitAfter(s, sep string) []string
 // 例子
 fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))         //  ["foo" "bar" "baz"]
 fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))    //  ["foo," "bar," "baz"]
 ​
 // 带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数
 // 当 n < 0 时,返回所有的子字符串;当 n == 0 时,返回的结果是 nil;
 // 当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割
 func SplitN(s, sep string, n int) []string 
 func SplitAfterN(s, sep string, n int) []string 
 // 例子
 fmt.Printf("%q\n", strings.SplitN("foo,bar,baz", ",", 2))   // ["foo" "bar,baz"]

替换

将旧字符串中的某个子串替换成目标字符串

 // 用 new 替换 s 中的 old,一共替换 n 个
 // 如果 n < 0,则不限制替换次数,即全部替换
 // 如果 old 为空,则在 s 的每个字符之间和 s 的头尾都插入一个 new
 func Replace(s, old, new string, n int) string
 ​
 //例子
 fmt.Println(strings.Replace("ABAACEDF", "A", "a", 2)) // aBaACEDF
 fmt.Println(strings.Replace("ABAACEDF", "A", "a", -1)) // aBaaCEDF

has系

用于判断是否具有某个前/后缀

 // s 中是否以 prefix (前缀)开始
 func HasPrefix(s, prefix string) bool
 ​
 // s 中是否以 suffix (后缀)结尾
 func HasSuffix(s, suffix string) bool
 ​
 //例子
 fmt.Println(strings.HasPrefix("Hello", "He")) // true
 fmt.Println(strings.HasPrefix("hello", "He")) // false
 ​
 fmt.Println(strings.HasSuffix("hello", "llo")) // true
 fmt.Println(strings.HasSuffix("HELLO", "llo")) // false

其他

参考Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)

(此为strings包里函数合集,有详细说明)

time包

表示时间两种类型

  1. 时间点(Time)

     type Time struct {
         // wall and ext encode the wall time seconds, wall time nanoseconds,
         // and optional monotonic clock reading in nanoseconds.
         //
         // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
         // a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
         // The nanoseconds field is in the range [0, 999999999].
         // If the hasMonotonic bit is 0, then the 33-bit field must be zero
         // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
         // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
         // unsigned wall seconds since Jan 1 year 1885, and ext holds a
         // signed 64-bit monotonic clock reading, nanoseconds since process start.
         wall uint64
         ext  int64
     ​
         // loc specifies the Location that should be used to
         // determine the minute, hour, month, day, and year
         // that correspond to this Time.
         // The nil location means UTC.
         // All UTC times are represented with loc==nil, never loc==&utcLoc.
         loc *Location
     }
    
  2. 时间段(Duration)

     type Duration int64
    

时间戳

时间戳是一个用来表示时间的整数,表示从从1970年1月1日0时0分0秒到当前时间点的所有秒数

 fmt.Println(time.Now().Unix())                       //获取当前时间戳 单位是秒
 fmt.Println(time.Now().UnixNano())                   //获取当前时间戳 单位是纳秒
 fmt.Println(time.Now().UnixMilli() / 1e6)             //将纳秒转换为毫秒
 fmt.Println(time.Now().UnixMicro() / 1e9)             //将纳秒转换为微秒
 ​
 fmt.Println(time.Unix(time.Now().UnixNano()/1e9, 0)) //将毫秒转换为 time 类型                                     
 // 获取指定时间的时间戳。只要是time类型都能获得时间戳
 dt, _ := time.Parse("2016-01-02 15:04:05", "2018-04-23 12:24:51")
 fmt.Println(dt.Unix())

时间计算

Time与Time不能直接相减

时间计算的相关方法:

 // 判断时刻 t 是否在 u 之前
 func (t Time) Before(u Time) bool
 // 判断时刻 t 是否在 u 之后
 func (t Time) After(u Time) bool
 // 判断时刻 t 是否与 u 相等
 func (t Time) Equal(u Time) bool   
 ​
 // 让程序休眠,休眠时当前goroutine处于阻塞状态,后续程序无法执行
 func Sleep(d Duration)  
 ​
 ​
 //计算时间间隔,相当于t-u,返回t到u的时间差
 func (t Time) Sub(u Time) Duration
 ​
 // 计算当前时间与时间t的时间差
 // 等价于 Now().Sub(t),可用来计算一段业务的消耗时间
 func Since(t Time) Duration   
 ​
 // 计算t与当前时间的间隔
 // 等价于 t.Sub(Now())
 func Until(t Time) Duration     
 ​
 // 返回与将给定时间段加到 t 所对应的时间
 func (t Time) Add(d Duration) Time
 ​
 // 返回与将给定的年数、月数和天数相加到 t 所对应的时间
 func (t Time) AddDate(years int, months int, days int) Time

两种计时器

  1. Timer

     type Timer struct {
         C <-chan Time
         r runtimeTimer
     }
    

    Timer 类型用来代表一个单独的事件,当设置的时间过期后,发送当前的时间到 channel, 我们可以通过以下两种方式来创建

     func AfterFunc(d Duration, fun func()) *Timer   
     ​
     func NewTimer(d Duration) *Timer
     ​
     //例子
     //例子
     timer := time.NewTimer(time.Second) //声明一个timer, time.Second是time包里声明的一个Duration
     <-timer.C   // 等待设置的时间
     ...
    
  2. Ticker

     type Ticker struct {
         C <-chan Time // The channel on which the ticks are delivered.
         r runtimeTimer
     }
    

    Ticker与Timer不同的点是,Ticker会每个固定的时间发送一条消息到管道里(与Timer一样,这个管道是Timer的C字段)。我们可以通过下面函数来获取一个Ticker:

     func NewTicker(d Duration) *Ticker
     ​
     //例子
     func main() {
         ticker := time.NewTicker(time.Second)
         for {
             select {
             case <-ticker.C:
                 fmt.Printf("哈哈哈")
             }
         }
     }
     ​
     //每隔一秒打印一个哈哈哈
    
  3. Timer有StopReset方法用于停止和重置定时器,Ticker有Stop方法关闭定时器(两者的Stop方法都不会关闭自身的管道)

     //Stop停止Timer的执行。如果停止了t会返回真;如果t已经被停止或者过期了会返回假。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。
     func (t *Timer) Stop() bool
     ​
     //Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。
     func (t *Timer) Reset(d Duration) bool
     ​
     //Stop关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功。
     func (t *Ticker) Stop(
    

strconv包

strconv是string conversion的缩写,主要实现对字符串和基本数据类型之间的转换。基本数据类型包括:布尔、整型(包括有/无符号、二进制、八进制、十进制和十六进制)和浮点型等。

在该库中,字符串向基本数据类型转换的函数都以Parse(译为解析)作为前缀,基本数据类型向字符串转换的函数都以Format(译为格式化)作为前缀。

strconv库的函数返回的error是自定义的——*NumError类型,里面包含的信息一般分为四种类型的错误信息:

  • ErrSyntax:语法有误,err信息里包含关键语句"invalid syntax"
  • ErrRange:结果超出类型范围,err信息里包含关键语句"value out of range"
  • ErrBase:数值进制有误,这只出现在整型和浮点型的转换上,err信息里包含关键语句"invalid base "
  • ErrBitSize:位大小有误,这只出现在整型和浮点型的转换上,err信息里包含关键语句"invalid bit size "

字符串与整形

base参数表示进位制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制

bitSize指定结果必须能无溢出赋值的整数bit位大小。在字符串转整型中,0、8、16、32、64 分别对应 int、int8、int16、int32、int64

 // Atoi是ParseInt(s,10,0)的简写,能将字符串转为10进制整数
 func Atoi(s string)(i int, err error)
 ​
 // 例子
 fmt.Println(strconv.ParseInt("-12", 10, 0)) // -12 <nil>
 fmt.Println(strconv.ParseInt("0xFF", 10, 0)) // 0 strconv.ParseInt: parsing "0xFF": invalid syntax
 fmt.Println(strconv.ParseInt("0xFF", 0, 0)) // 255 <nil>
 fmt.Println(strconv.ParseInt("FF", 16, 0)) // 255 <nil>
 ​
 fmt.Println(strconv.ParseUint("-12", 10, 0)) // 0 strconv.ParseUint: parsing "-12": invalid syntax
 fmt.Println(strconv.ParseUint("12", 10, 0)) // 12 <nil>
 ​
 ​
 ​
 // Itoa是FormatInt(i, 10) 的简写,能将10进制整数转为字符串
 func Itoa(i int) string
 ​
 // 例子
 fmt.Println(strconv.FormatInt(12, 2)) // 1100
 fmt.Println(strconv.FormatInt(-12, 2)) // -1100
 fmt.Println(strconv.FormatUint(12, 10)) // 12
 fmt.Println(strconv.Itoa(12)) // 12

字符串与浮点型

其中bitSize指定了期望的接收类型,32是float32(返回值可以不改变精确值的赋值给float32),64是float64;

bitSize 表示来源类型(32:float32、64:float64) // fmt 表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)。 prec 控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。

字符串转浮点数时,如果字符串合法的,函数会返回最为接近字符串表示值的一个浮点数(使用IEEE754规范舍入)。

 func ParseFloat(s string, bitSize int) (f float64, err error)
 ​
 // 例子
 v := "4.12345678"
 if s, err := strconv.ParseFloat(v, 32); err == nil {
     fmt.Printf("%T, %v\n", s, s) // float64, 4.123456954956055
 }
 if s, err := strconv.ParseFloat(v, 64); err == nil {
     fmt.Printf("%T, %v\n", s, s) // float64, 4.12345678
 }
 ​
 ​
 ​
 func FormatFloat(f float64, fmt byte, prec, bitSize int) string
 ​
 // 例子
 v1 := 4.12345678
 s32 := strconv.FormatFloat(v1, 'E', -1, 32)
 fmt.Printf("%T, %v\n", s32, s32) // string, 4.123457E+00
 s64 := strconv.FormatFloat(v1, 'E', -1, 64)
 fmt.Printf("%T, %v\n", s64, s64) // string, 4.12345678E+00

字符串与布尔型

字符串转布尔类型,只会接受值为"1"、"0"、"t"、"f"、"T"、"F"、"true"、"false"、"True"、"False"、"TRUE"、"FALSE"的字符串作为参数,否则返回错误

 func ParseBool(str string) (value bool, err error)
 ​
 // 例子
 fmt.Println(strconv.ParseBool("1")) // true
 fmt.Println(strconv.ParseBool("2")) // strconv.ParseBool: parsing "2": invalid syntax
 ​
 // 根据布尔值返回"true"或"false"
 func FormatBool(b bool) string
 ​
 // 例子
 fmt.Println(strconv.FormatBool(true)) // true

json包

json

JSON(JavaScript Object Notation)是由道格拉斯·克罗克福特构想和设计的一种轻量级资料交换格式。JSON是由属性和值组成的资料格式,因此JSON也有易于阅读和处理的优势,经常被用作数据交换,页面展示,序列化等场景,基本每种语言都有对应的json解析框架。目前也有许多编程语言都能够将其解析和字符串化,其广泛使用的程度也使其成为通用的资料格式。

Marshal与Unmarshal

 // 将一个目标v序列化,v可以是指针也可以是值,绝大多数情况下我们都传指针以减少程序开销
 func Marshal(v interface{}) ([]byte, error)
 ​
 // 将一个byte切片反序列化,并将结果绑定到v上
 // 注意,v一定要传入一个指针,否则将返回错误:json: Unmarshal(non-pointer)
 // 如果传入的byte有一些字段在v中
 func Unmarshal(data []byte, v interface{}) error

tag标签

结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

为结构体编写Tag时,必须严格遵守键值对的规则,例如不要在key和value之间添加空格或者是其他东西,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值

 `key1:"value1" key2:"value2"`
 //例子
 type Student struct {
     Name   string `json:"name" form:"username"`
     Gender string `json:"gender"`
 }

json标签的使用:

 type Person struct {
     Name  string `json:"name"` // 指定json序列化/反序列化时使用小写name
     Age  int64
     Email string  `json:"email,omitempty"` //omitempty 在最终的序列化结果中去掉空值字段
     Weight float64  `json:"-"` // 指定json序列化/反序列化时忽略此字段
     Profile
 }`

结构体嵌套的json编码

如果一个结构体里嵌套了另一个匿名的结构体,但是没有通过json标签来显式指定编码时key,那么被嵌套的结构体将不会被编码成对象,而是会内嵌到上一级结构体里:

 type Info struct {
     School string
     Class  int
 }
 ​
 type Student struct {
     Name   string `json:"name,omitempty"`
     Gender string
     Info  
 }
 ​
 func main() {
     x := Student{
         Name:   "ikun",
         Gender: "X",
         Info: Info{
             School: "重庆邮电大学",
             Class:  202222..,
         },
     }
     bytes, _ := json.Marshal(&x)         
     fmt.Println(string(bytes)) // output:{"name":"ikun","Gender":"X","School":"重庆邮电大学","Class":202222..}
 }

如果内嵌的结构体不是匿名的,或者通过json标签显式指定编码时key,那么该内嵌结构体将会被编码成一个对象。

 type Info struct {
     School string
     Class  int
 }
 ​
 type Student struct {
     Name   string `json:"name,omitempty"`
     Gender string
     Info   `json:"info"`
 }
 ​
 func main() {
     x := Student{
         Name:   "ikun",
         Gender: "X",
         Info: Info{
             School: "重庆邮电大学",
             Class:  202222.,
         },
     }
     bytes, _ := json.Marshal(&x)
     fmt.Println(string(bytes)) // output:{"name":"ikun","Gender":"X","info":{"School":"重庆邮电大学","Class":202222}}
 ​
 }

在函数里声明的匿名的结构体也可以加结构体标签并进行解码与解码:

 func main() {
     var x struct {
         Name   string `json:"name,omitempty"`
         Gender string
     }
     x.Name = "ikun"
     x.Gender = "X"
     bytes, _ := json.Marshal(&x)
     fmt.Println(string(bytes)) // output:{"name":"ikun","Gender":"X"}
 ​
     x.Name = ""
     x.Gender = ""
     json.Unmarshal(bytes, &x)
     fmt.Println(x) // output:{ikun X}
 }

tag标签

tag是什么

Tag是结构体在编译阶段关联到成员的元信息字符串,在运行的时候通过反射的机制读取出来。

如何使用tag

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

 //例子
 type login struct{
     Username string `json:"username" form:"user" binding:"required"`
     Password string `json:"password form:"pass" binding:"required"`
 }

key会指定反射的解析方式,如下: json(JSON标签) 、orm(Beego标签)、gorm(GORM标签)、bson(MongoDB标签)、form(表单标签)、binding(表单验证标签)

几个常见的tag标签

json

json序列化或反序列化时字段的名称,在gin框架中获取参数也会用到

db

sqlx模块中对应的数据库字段名

form

gin框架中对应的前端的数据字段名

binding

搭配 form 使用, 默认如果没查找到结构体中的某个字段则不报错值为空, binding为 required 代表没找到返回错误给前端

参考:[Go系列:结构体标签 - 掘金 (juejin.cn)](

flag包

log包

Print类

包括以下三种

 //Printf调用Output将生成的格式化字符串输出到标准logger,参数用和fmt.Printf相同的方法处理。
 func Printf(format string, v ...interface{})
 //Print调用Output将生成的格式化字符串输出到标准logger,参数用和fmt.Print相同的方法处理。
 func Print(v ...interface{})
 //Println调用Output将生成的格式化字符串输出到标准logger,参数用和fmt.Println相同的方法处理。
 func Println(v ...interface{})

Fatal类

也包括以下三类

 //Fatalf等价于{Printf(v...); os.Exit(1)}
 func Fatalf(format string, v ...interface{})
 //Fatal等价于{Print(v...); os.Exit(1)}
 func Fatal(v ...interface{})
 //Fatalln等价于{Println(v...); os.Exit(1)}
 func Fatalln(v ...interface{})

这一类在记录日志后会退出程序

Painc类

 //Panicf等价于{Printf(v...); panic(...)}
 func Panicf(format string, v ...interface{})
 //Panic等价于{Print(v...); panic(...)}
 func Panic(v ...interface{})
 //Panicln等价于{Println(v...); panic(...)}
 func Panicln(v ...interface{})

这一类在打印日志后会引发panic

配置日志

设置前缀

 //SetPrefix设置标准logger的输出前缀。
 func SetPrefix(prefix string)
 ​
 //Prefix返回标准logger的输出前缀。
 func Prefix() string

在调用这个函数后所有输出的日志都会带上前缀

设置输出目的地

 //SetOutput设置标准logger的输出目的地,默认是标准错误输出。
 func SetOutput(w io.Writer)

只要是实现了io.writer这个接口的类型都可以传进去,比如传入一个文件就可以实现将日志记录在一个文件中

设置输出选项

 //SetFlags设置标准logger的输出选项。
 func SetFlags(flag int)
 ​
 //Flags返回标准logger的输出选项。
 func Flags() int
 ​
 //以下常量用于设置flag
 const (
     Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23(日期)
     Ltime                         // the time in the local time zone: 01:23:23(时间)
     Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.(时间精确到微秒)
     Llongfile                     // full file name and line number: /a/b/c/d.go:23(文件路径和控制输出这条日志的行号)
     Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile(文件名和控制输出这条日志的行号)
     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(把前缀移到具体的message前面去)
     LstdFlags     = Ldate | Ltime // initial values for the standard logger(这是默认值,就是日期和时间)
 )
 //在定义常量时使用了iota的话,这个量等于0,往下依次加1,这里因为使用了移位符号,向左移了一位,所以往下依次是2,4,8...,最后一个是3

实现自定义的logger(日志分级)

使用new函数

 //第一个参数传入实现了io.writer接口的变量,这个变量控制了输出目的地,第二个变量传入前缀,第三个变量传入flag
 //返回一个*logger可以调用Print/f/ln,Fatal/f/ln,Panic/f/ln三个种方法
 func New(out io.Writer, prefix string, flag int) *Logger {
     l := &Logger{out: out, prefix: prefix, flag: flag}
     if out == io.Discard {
         l.isDiscard = 1
     }
     return l
 }

实现不同等级的日志,就是往New函数里面传入不同的参数

 var(
     Trace *log.Logger //默认情况下既不打印到终端也不输出到文件。此时,对程序运行效率几乎不产生影响。
     Debug *log.Logger //默认情况下会打印到终端输出,但是不会归档到日志文件。
     Info *log.Logger  //报告程序进度和状态信息。一般这种信息都是一过性的,不会大量反复输出。
     Warning *log.Logger //程序处理中遇到非法数据或者某种可能的错误。该错误是一过性的、可恢复的,不会影响程序继续运行,程序仍处在正常状态。
     Error *log.Logger //状态错误,该错误发生后程序仍然可以运行,但是极有可能运行在某种非正常的状态下,导致无法完成全部既定的功能。
     Fatal *log.Logger //致命的错误,表明程序遇到了致命的错误,必须马上终止运行。
 )
 ​
 ​
 func init(){
     file,_ :=os.Create("error.log")
     //io.discard是一个空结构体,使用这个的时候表示这个级别的日志不输出
     Trace =log.New(io.Discard,"TRACE: ",log.Ltime|log.Ldate|log.Lshortfile)
     //os.Stdout代表输出到终端
     Debug = log.New(os.Stdout,"DEBUG: ",log.Ltime|log.Ldate|log.Lshortfile)
     Info =log.New(os.Stdout,"INFO: ",log.Ltime|log.Ldate|log.Lshortfile)
     Warning =log.New(os.Stdout,"WARNING: ",log.Ltime|log.Ldate|log.Lshortfile)
     //io.MultiWriter参数是一些Writer接口的集合,返回一个iWriter,这个Writer就是传入的变量的集合
     Error = log.New(io.MultiWriter(os.Stderr,file),"ERROR: ",log.Ltime|log.Ldate|log.Lshortfile)
     Fatal = log.New(os.Stdout,"FATAL: ",log.Ltime|log.Ldate|log.Lshortfile)
 }
 ​
 //这三个都是文件的指针类型
 var (
     Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")//标准输入
     Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")//标准输出
     Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")//标准错误
 )