您好,登錄后才能下訂單哦!
這篇文章主要介紹“Golang中的flag標(biāo)準(zhǔn)庫(kù)如何使用”,在日常操作中,相信很多人在Golang中的flag標(biāo)準(zhǔn)庫(kù)如何使用問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Golang中的flag標(biāo)準(zhǔn)庫(kù)如何使用”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
flag 基本使用示例代碼如下:
package main import ( "flag" "fmt" ) type flagVal struct { val string } func (v *flagVal) String() string { return v.val } func (v *flagVal) Set(s string) error { v.val = s return nil } func main() { // 1. 使用 flag.Type() 返回 *int 類(lèi)型命令行參數(shù) var nFlag = flag.Int("n", 1234, "help message for flag n") // 2. 使用 flag.TypeVar() 綁定命令行參數(shù)到 int 類(lèi)型變量 var flagvar int flag.IntVar(&flagvar, "flagvar", 1234, "help message for flagvar") // 3. 使用 flag.Var() 綁定命令行參數(shù)到實(shí)現(xiàn)了 flag.Value 接口的自定義類(lèi)型變量 val := flagVal{} flag.Var(&val, "val", "help message for val") // 解析命令行參數(shù) flag.Parse() fmt.Printf("nFlag: %d\n", *nFlag) fmt.Printf("flagvar: %d\n", flagvar) fmt.Printf("val: %+v\n", val) fmt.Printf("NFlag: %v\n", flag.NFlag()) // 返回已設(shè)置的命令行標(biāo)志個(gè)數(shù) fmt.Printf("NArg: %v\n", flag.NArg()) // 返回處理完標(biāo)志后剩余的參數(shù)個(gè)數(shù) fmt.Printf("Args: %v\n", flag.Args()) // 返回處理完標(biāo)志后剩余的參數(shù)列表 fmt.Printf("Arg(1): %v\n", flag.Arg(1)) // 返回處理完標(biāo)志后剩余的參數(shù)列表中第 i 項(xiàng) }
可以通過(guò)指定 --help/-h
參數(shù)來(lái)查看這個(gè)命令行程序的使用幫助:
$ go run main.go -h
Usage of ./main:
-flagvar int
help message for flagvar (default 1234)
-n int
help message for flag n (default 1234)
-val value
help message for val
這個(gè)程序接收三個(gè)命令行參數(shù):
int
類(lèi)型的 -flagvar
,默認(rèn)值為 2134
。
int
類(lèi)型的 -n
,默認(rèn)值為 2134
。
value
類(lèi)型的 -val
,無(wú)默認(rèn)值。
我們可以將 -flagvar
、-n
、-val
稱(chēng)作 flag
,即「標(biāo)志」,這也是 Go 內(nèi)置命令行參數(shù)解析庫(kù)被命名為 flag 的原因,見(jiàn)名知意。
這三個(gè)參數(shù)在示例代碼中,分別使用了三種不同形式來(lái)指定:
flag.Type()
:
-n
標(biāo)志是使用 var nFlag = flag.Int("n", 1234, "help message for flag n")
來(lái)指定的。
flag.Int
函數(shù)簽名如下:
func Int(name string, value int, usage string) *int
flag.Int
函數(shù)接收三個(gè)參數(shù),分別是標(biāo)志名稱(chēng)、標(biāo)志默認(rèn)參數(shù)值、標(biāo)志使用幫助信息。函數(shù)最終還會(huì)返回一個(gè) *int
類(lèi)型的值,表示用戶(hù)在執(zhí)行命令行程序時(shí)為這個(gè)標(biāo)志指定的參數(shù)。
除了使用 flag.Int
來(lái)設(shè)置 int
類(lèi)型標(biāo)志,flag 還支持其他多種類(lèi)型,如使用 flag.String
來(lái)設(shè)置 string
類(lèi)型標(biāo)志。
flag.TypeVar()
:
-flagvar
標(biāo)志是使用 flag.IntVar(&flagvar, "flagvar", 1234, "help message for flagvar")
來(lái)指定的。
flag.IntVar
函數(shù)簽名如下:
func IntVar(p *int, name string, value int, usage string)
與 flag.Int
不同的是,flag.IntVar
函數(shù)取消了返回值,而是會(huì)將用戶(hù)傳遞的命令行參數(shù)綁定到第一個(gè)參數(shù) p *int
。
除了使用 flag.IntVar
來(lái)綁定 int
類(lèi)型參數(shù)到標(biāo)志,flag 還提供其他多個(gè)函數(shù)來(lái)支持綁定不同類(lèi)型參數(shù)到標(biāo)志,如使用 flag.StringVar
來(lái)綁定 string
類(lèi)型標(biāo)志。
flag.Var()
:
-val
標(biāo)志是使用 flag.Var(&val, "val", "help message for val")
來(lái)指定的。
flag.Var
函數(shù)簽名如下:
func Var(value Value, name string, usage string)
flag.Var
函數(shù)接收三個(gè)參數(shù),后兩個(gè)參數(shù)分別是標(biāo)志名稱(chēng)、標(biāo)志使用幫助信息。而用戶(hù)傳遞的命令行參數(shù)將被綁定到第一個(gè)參數(shù) value
。
type Value interface { String() string Set(string) error }
我們可以自定義類(lèi)型,只要實(shí)現(xiàn)了 flag.Value
接口,都可以傳遞給 flag.Var
,這極大的增加了 flag 包的靈活性。
定義完三個(gè)標(biāo)志,我們還需要使用 flag.Parse()
來(lái)解析命令行參數(shù),只有解析成功以后,才會(huì)將戶(hù)傳遞的命令行參數(shù)值綁定到對(duì)應(yīng)的標(biāo)志變量中。之后就可以使用 nFlag
、flagvar
、val
的變量值了。
在 main
函數(shù)底部,使用 flag.NFlag()
、flag.NArg()
、flag.Args()
、flag.Arg(1)
幾個(gè)函數(shù)獲取并展示了命令行參數(shù)相關(guān)信息。
現(xiàn)在我們嘗試給這個(gè)命令行程序傳遞幾個(gè)參數(shù)并執(zhí)行它,看下輸出結(jié)果:
$ go run main.go -n 100 -val test a b c d
nFlag: 100
flagvar: 1234
val: {val:test}
NFlag: 2
NArg: 4
Args: [a b c d]
Arg(1): b
我們通過(guò) -n 100
為 -n
標(biāo)志指定了參數(shù)值 100
,最終會(huì)被賦值給 nFlag
變量。
由于沒(méi)有指定 flagvar
標(biāo)志的參數(shù)值,所以 flagvar
變量會(huì)被賦予默認(rèn)值 1234
。
接著,我們又通過(guò) -val test
為 -val
標(biāo)志指定了參數(shù)值 test
,最終賦值給了自定義的 flagVal
結(jié)構(gòu)體的 val
字段。
因?yàn)橹辉O(shè)置了 -n
和 -val
兩個(gè)標(biāo)志的參數(shù)值,所以函數(shù) flag.NFlag()
返回結(jié)果為 2。
a b c d
四個(gè)參數(shù)由于沒(méi)有被定義,所以 flag.NArg()
返回結(jié)果為 4。
flag.Args()
返回的切片中存儲(chǔ)了 a b c d
四個(gè)參數(shù)。
flag.Arg(1)
返回切片中下標(biāo)為 1
位置的參數(shù),即 b
。
在上面的示例中,我們展示了 int
類(lèi)型和自定義的 flag.Value
的使用,flag 包支持的所有標(biāo)志類(lèi)型匯總?cè)缦拢?/p>
參數(shù)類(lèi)型 | 合法值 |
---|---|
bool | strconv.ParseBool 能夠解析的有效值,接受:1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False。 |
time.Duration | time.ParseDuration 能夠解析的有效值,如:”300ms”, “-1.5h” or “2h55m”,合法單位:”ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”。 |
float64 | 合法的浮點(diǎn)數(shù)類(lèi)型。 |
int/int64/uint/uint64 | 合法的整數(shù)類(lèi)型,如:1234, 0664, 0x1234,也可以是負(fù)數(shù)。 |
string | 合法的字符串類(lèi)型。 |
flag.Value | 實(shí)現(xiàn)了該接口的類(lèi)型。 |
除了支持幾種 Go 默認(rèn)的原生類(lèi)型外,如果我們想實(shí)現(xiàn)其他類(lèi)型標(biāo)志的定義,都可以通過(guò) flag.Value
接口類(lèi)型來(lái)完成。其實(shí) flag 包內(nèi)部對(duì)于 bool
、int
等所有類(lèi)型的定義,都實(shí)現(xiàn)了 flag.Value
接口,在稍后講解源碼過(guò)程中將會(huì)有所體現(xiàn)。
命令行標(biāo)志支持多種語(yǔ)法:
語(yǔ)法 | 說(shuō)明 |
---|---|
-flag | bool 類(lèi)型標(biāo)志可以使用,表示參數(shù)值為 true。 |
–flag | 支持兩個(gè) - 字符,與 -flag 等價(jià)。 |
-flag=x | 所有類(lèi)型通用,為標(biāo)志 flag 傳遞參數(shù)值 x。 |
-flag x | 作用等價(jià)于 -flag=x,但是僅限非 bool 類(lèi)型標(biāo)志使用,假如這樣使用 cmd -x * ,其中 * 是 Unix shell 通配符,如果存在名為 0、false 等文件,則參數(shù)值結(jié)果會(huì)發(fā)生變化。 |
flag 解析參數(shù)時(shí)會(huì)在第一個(gè)非標(biāo)志參數(shù)之前(單獨(dú)的一個(gè) -
字符也是非標(biāo)志參數(shù))或終止符 --
之后停止。
注意:本文以 Go 1.19.4 源碼為例,其他版本可能存在差異。
熟悉了 flag 包的基本使用,接下來(lái)我們就要深入到 flag 的源碼,來(lái)探究其內(nèi)部是如何實(shí)現(xiàn)。
閱讀 flag 包的源碼,我們可以從使用 flag 包的流程來(lái)入手。
在 main
函數(shù)中,我們首先通過(guò)如下代碼定義了一個(gè)標(biāo)志 -n
。
var nFlag = flag.Int("n", 1234, "help message for flag n")
flag.Int
函數(shù)定義如下:
func Int(name string, value int, usage string) *int { return CommandLine.Int(name, value, usage) }
可以發(fā)現(xiàn),flag.Int
函數(shù)調(diào)用并返回了 CommandLine
對(duì)象的 Int
方法,并將參數(shù)原樣傳遞進(jìn)去。
來(lái)看看 CommandLine
是個(gè)什么:
var CommandLine = NewFlagSet(os.Args[0], ExitOnError) func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { f := &FlagSet{ name: name, errorHandling: errorHandling, } f.Usage = f.defaultUsage return f }
CommandLine
是使用 NewFlagSet
創(chuàng)建的 FlagSet
結(jié)構(gòu)體指針,在構(gòu)造 FlagSet
對(duì)象時(shí),需要兩個(gè)參數(shù) os.Args[0]
和 ExitOnError
。
我們知道 os.Args
存儲(chǔ)了程序執(zhí)行時(shí)指定的所有命令行參數(shù),os.Args[0]
就是當(dāng)前命令行程序的名稱(chēng),ExitOnError
是一個(gè)常量,用來(lái)標(biāo)記在出現(xiàn) error
時(shí)應(yīng)該如何做,ExitOnError
表示在遇到 error
時(shí)退出程序。
來(lái)看下 FlagSet
是如何定義:
type FlagSet struct { Usage func() name string parsed bool actual map[string]*Flag formal map[string]*Flag args []string // arguments after flags errorHandling ErrorHandling output io.Writer // nil means stderr; use Output() accessor }
Usage
字段是一個(gè)函數(shù),根據(jù)名字大概能夠猜測(cè)出,這個(gè)函數(shù)會(huì)在指定 --help/-h
參數(shù)查看命令行程序使用幫助時(shí)被調(diào)用。
parsed
用來(lái)標(biāo)記是否調(diào)用過(guò) flag.Parse()
。
actual
和 formal
分別用來(lái)存儲(chǔ)從命令行解析的標(biāo)志參數(shù)和在程序中指定的默認(rèn)標(biāo)志參數(shù)。它們都使用 map
來(lái)存儲(chǔ) Flag
類(lèi)型的指針,FlagSet
可以看作是 Flag
結(jié)構(gòu)體的「集合」。
args
用來(lái)保存處理完標(biāo)志后剩余的參數(shù)列表。
errorHandling
標(biāo)記在出現(xiàn) error
時(shí)應(yīng)該如何做。
output
用來(lái)設(shè)置輸出位置,這可以改變 --help/-h
時(shí)展示幫助信息的輸出位置。
現(xiàn)在來(lái)看下 Flag
的定義:
type Flag struct { Name string // 標(biāo)志名稱(chēng) Usage string // 幫助信息 Value Value // 標(biāo)志所對(duì)應(yīng)的命令行參數(shù)值 DefValue string // 用來(lái)記錄字符串類(lèi)型的默認(rèn)值,它不會(huì)被改變 }
Flag
用來(lái)記錄一個(gè)命令行參數(shù),里面存儲(chǔ)了一個(gè)標(biāo)志所有信息。
可以說(shuō) Flag
和 FlagSet
兩個(gè)結(jié)構(gòu)體就是 flag 包的核心,所有功能都是圍繞這兩個(gè)結(jié)構(gòu)體設(shè)計(jì)的。
標(biāo)志所對(duì)應(yīng)的命令行參數(shù)值為 flag.Value
接口類(lèi)型,在前文中已經(jīng)見(jiàn)過(guò)了,定義如下:
type Value interface { String() string Set(string) error }
之所以使用接口,是為了能夠存儲(chǔ)任何類(lèi)型的值,除了 flag 包默認(rèn)支持的內(nèi)置類(lèi)型,用戶(hù)也可以定義自己的類(lèi)型,只要實(shí)現(xiàn)了 Value
接口即可。
如我們?cè)谇拔氖纠绦蛑卸x的 flagVal
類(lèi)型。
現(xiàn)在 CommandLine
的定義以及內(nèi)部實(shí)現(xiàn)我們都看過(guò)了,是時(shí)候回過(guò)頭來(lái)看一看 CommandLine
對(duì)象的 Int
方法了:
func (f *FlagSet) Int(name string, value int, usage string) *int { p := new(int) f.IntVar(p, name, value, usage) return p }
Int
方法內(nèi)部調(diào)用了 f.IntVar()
方法,定義如下:
func (f *FlagSet) IntVar(p *int, name string, value int, usage string) { f.Var(newIntValue(value, p), name, usage) }
IntVar
方法又調(diào)用了 f.Var()
方法。
Var
方法第一個(gè)參數(shù)為 newIntValue(value, p)
,我們來(lái)看看 newIntValue
函數(shù)是如何定義的:
type intValue int func newIntValue(val int, p *int) *intValue { *p = val return (*intValue)(p) } func (i *intValue) Set(s string) error { v, err := strconv.ParseInt(s, 0, strconv.IntSize) if err != nil { err = numError(err) } *i = intValue(v) return err } func (i *intValue) Get() any { return int(*i) } func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
newIntValue
是一個(gè)構(gòu)造函數(shù),用來(lái)創(chuàng)建一個(gè) intValue
類(lèi)型的指針,intValue
底層類(lèi)型實(shí)際上是 int
。
定義 intValue
類(lèi)型的目的就是為了實(shí)現(xiàn) flag.Value
接口。
再來(lái)看下 Var
方法如何定義:
func (f *FlagSet) Var(value Value, name string, usage string) { // Flag must not begin "-" or contain "=". if strings.HasPrefix(name, "-") { panic(f.sprintf("flag %q begins with -", name)) } else if strings.Contains(name, "=") { panic(f.sprintf("flag %q contains =", name)) } // Remember the default value as a string; it won't change. flag := &Flag{name, usage, value, value.String()} _, alreadythere := f.formal[name] if alreadythere { var msg string if f.name == "" { msg = f.sprintf("flag redefined: %s", name) } else { msg = f.sprintf("%s flag redefined: %s", f.name, name) } panic(msg) // Happens only if flags are declared with identical names } if f.formal == nil { f.formal = make(map[string]*Flag) } f.formal[name] = flag }
name
參數(shù)即為標(biāo)志名,在 Var
方法內(nèi)部,首先對(duì)標(biāo)志名的合法性進(jìn)行了校驗(yàn),不能以 -
開(kāi)頭且不包含 =
。
接著,根據(jù)參數(shù)創(chuàng)建了一個(gè) Flag
類(lèi)型,并且校驗(yàn)了標(biāo)志是否被重復(fù)定義。
最后將 Flag
保存在 formal
屬性中。
到這里,整個(gè)函數(shù)調(diào)用關(guān)系就結(jié)束了,我們來(lái)梳理一下代碼執(zhí)行流程:
flag.Int
-> CommandLine.Int
-> CommandLine.IntVar
-> CommandLine.Var
。
經(jīng)過(guò)這個(gè)調(diào)用過(guò)程,我們就得到了一個(gè) Flag
對(duì)象,其名稱(chēng)為 n
、默認(rèn)參數(shù)值為 1234
、值的類(lèi)型為 intValue
、幫助信息為 help message for flag n
。并將這個(gè) Flag
對(duì)象保存在了 CommandLine
這個(gè)類(lèi)型為 FlagSet
的結(jié)構(gòu)體指針對(duì)象的 formal
屬性中。
我們?cè)谑纠绦蛑羞€使用了另外兩種方式定義標(biāo)志。
使用 flag.IntVar(&flagvar, "flagvar", 1234, "help message for flagvar")
定義標(biāo)志 -flagvar
。
flag.IntVar
定義如下:
func IntVar(p *int, name string, value int, usage string) { CommandLine.Var(newIntValue(value, p), name, usage) }
可以發(fā)現(xiàn),flag.IntVar
函數(shù)內(nèi)部沒(méi)有調(diào)用 CommandLine.Int
和 CommandLine.IntVar
的過(guò)程,而是直接調(diào)用 CommandLine.Var
。
另外,我們還使用 flag.Var(&val, "val", "help message for val")
定義了 -val
標(biāo)志。
flag.Var
定義如下:
func Var(value Value, name string, usage string) { CommandLine.Var(value, name, usage) }
flag.Var
函數(shù)內(nèi)部同樣直接調(diào)用了 CommandLine.Var
,并且由于參數(shù) value
已經(jīng)是 Value
接口類(lèi)型,可以無(wú)需調(diào)用 newIntValue
這類(lèi)構(gòu)造函數(shù)將 Go 內(nèi)置類(lèi)型轉(zhuǎn)為 Value
類(lèi)型,直接傳遞參數(shù)即可。
命令行參數(shù)定義完成了,終于到了解析部分,可以使用 flag.Parse()
解析命令行參數(shù)。
flag.Parse
函數(shù)代碼如下:
func Parse() { CommandLine.Parse(os.Args[1:]) }
內(nèi)部同樣是調(diào)用 CommandLine
對(duì)象對(duì)應(yīng)的方法,并且將除程序名稱(chēng)以外的命令行參數(shù)都傳遞到 Parse
方法中,Parse
方法定義如下:
func (f *FlagSet) Parse(arguments []string) error { f.parsed = true f.args = arguments for { seen, err := f.parseOne() if seen { continue } if err == nil { break } switch f.errorHandling { case ContinueOnError: return err case ExitOnError: if err == ErrHelp { os.Exit(0) } os.Exit(2) case PanicOnError: panic(err) } } return nil }
首先將 f.parsed
標(biāo)記為 true
,在調(diào)用 f.Parsed()
方法時(shí)會(huì)被返回:
func (f *FlagSet) Parsed() bool { return f.parsed }
接著又將 arguments
保存在 f.args
屬性中。
然后就是循環(huán)解析命令行參數(shù)的過(guò)程,每調(diào)用一次 f.parseOne()
解析一個(gè)標(biāo)志,直到解析完成或遇到 error
退出程序。
parseOne
方法實(shí)現(xiàn)如下:
func (f *FlagSet) parseOne() (bool, error) { if len(f.args) == 0 { return false, nil } s := f.args[0] if len(s) < 2 || s[0] != '-' { return false, nil } numMinuses := 1 if s[1] == '-' { numMinuses++ if len(s) == 2 { // "--" terminates the flags f.args = f.args[1:] return false, nil } } name := s[numMinuses:] if len(name) == 0 || name[0] == '-' || name[0] == '=' { return false, f.failf("bad flag syntax: %s", s) } // it's a flag. does it have an argument? f.args = f.args[1:] hasValue := false value := "" for i := 1; i < len(name); i++ { // equals cannot be first if name[i] == '=' { value = name[i+1:] hasValue = true name = name[0:i] break } } m := f.formal flag, alreadythere := m[name] // BUG if !alreadythere { if name == "help" || name == "h" { // special case for nice help message. f.usage() return false, ErrHelp } return false, f.failf("flag provided but not defined: -%s", name) } if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg if hasValue { if err := fv.Set(value); err != nil { return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err) } } else { if err := fv.Set("true"); err != nil { return false, f.failf("invalid boolean flag %s: %v", name, err) } } } else { // It must have a value, which might be the next argument. if !hasValue && len(f.args) > 0 { // value is the next arg hasValue = true value, f.args = f.args[0], f.args[1:] } if !hasValue { return false, f.failf("flag needs an argument: -%s", name) } if err := flag.Value.Set(value); err != nil { return false, f.failf("invalid value %q for flag -%s: %v", value, name, err) } } if f.actual == nil { f.actual = make(map[string]*Flag) } f.actual[name] = flag return true, nil }
parseOne
代碼稍微多一點(diǎn),不過(guò)整體脈絡(luò)還是比較清晰的。
首先對(duì) f.args
參數(shù)進(jìn)行了校驗(yàn),接著提取標(biāo)志前導(dǎo)符號(hào) -
的個(gè)數(shù)放到 numMinuses
變量中,然后取出標(biāo)志名并對(duì)標(biāo)志語(yǔ)法做了檢查。
接下來(lái)取出參數(shù) value
,并且判斷標(biāo)志名是否為 -help/-h
,如果是則說(shuō)明用戶(hù)只想打印程序使用幫助信息,打印后 parseOne
會(huì)返回 ErrHelp
,上層的調(diào)用者 f.Parse
就會(huì)捕獲到 ErrHelp
,然后調(diào)用 os.Exit(0)
直接退出程序。
其中 f.usage()
實(shí)現(xiàn)了打印幫助信息的功能,內(nèi)部具體實(shí)現(xiàn)這里就不講解了,因?yàn)榛旧鲜莾?nèi)容排版的實(shí)現(xiàn),不是核心功能,感興趣可以自己嘗試看一看。
最后就是根據(jù)參數(shù)值是否為 bool
類(lèi)型分別進(jìn)行參數(shù)綁定,將參數(shù)設(shè)置到對(duì)應(yīng)的標(biāo)志變量中,并將標(biāo)志保存到 f.actual
中。
以上步驟都執(zhí)行完成后,在執(zhí)行 fmt.Printf("nFlag: %d\n", *nFlag)
時(shí),就能夠獲取到 nFlag
被賦予的參數(shù)值了。
至此,flag 包源碼的整體脈絡(luò)都已經(jīng)清晰了。
在我們的示例代碼最后,還打印了 NFlag()
、NArg()
、Args()
、Arg(1)
幾個(gè)函數(shù)的結(jié)果。
這幾個(gè)函數(shù)實(shí)現(xiàn)非常簡(jiǎn)單,代碼如下:
func NFlag() int { return len(CommandLine.actual) } func NArg() int { return len(CommandLine.args) } func Args() []string { return CommandLine.args } func Arg(i int) string { return CommandLine.Arg(i) }
由于代碼過(guò)于簡(jiǎn)單,我就不進(jìn)行解釋了,相信通過(guò)上面的講解,這幾個(gè)函數(shù)的作用你也能做到一目了然。
flag 包還有一些其他類(lèi)型,如 stringValue
、float64Value
,這些類(lèi)型實(shí)現(xiàn)思路都是一樣的,也不再一一講解。
到此,關(guān)于“Golang中的flag標(biāo)準(zhǔn)庫(kù)如何使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。