golangarg的簡單介紹

本文目錄一覽:

GOLang CLI

基本概念這裡不過多的敘述,也就是commands,arguments,和flags

這裡主要介紹下Generator,因為Generator是一個非常好用的工具,可以非常方便的添加commands。

基本命令

cobra init:初始化

cobra add:添加子command

添加子command

這個部分比較簡單,在使用的過程中直接創建對應的command即可

使用flags

不過flags有多種類型:

首先會用到的就是一個CLI的argument。可以直接通過

返回的args是一個slice,slice的第一個元素是程序的path,也就是運行這個go程序的相對路徑。args[1:]才是後邊的args。參數按照空格的方式分割。

經常使用還有CLI的一些option的內容,也就是根據flag獲得對應flag的參數。

示例

會根據flag的內容輸出對應的option的結果。

按照flag的例子接著使用一些argument的內容

調用命令的方式為./main -word=word -fork=true 1 2 3 4

最終的結果是flag對應的Arg內容是最後的參數內容。但是os.Args還是按照arguments的規則來區分的。

pflag的基本功能和flag相同,但是支持一些更豐富的操作。

一些特殊的需求可以使用pflag來實現。

Go 語言內存管理(三):逃逸分析

Go 語言較之 C 語言一個很大的優勢就是自帶 GC 功能,可 GC 並不是沒有代價的。寫 C 語言的時候,在一個函數內聲明的變數,在函數退出後會自動釋放掉,因為這些變數分配在棧上。如果你期望變數的數據可以在函數退出後仍然能被訪問,就需要調用 malloc 方法在堆上申請內存,如果程序不再需要這塊內存了,再調用 free 方法釋放掉。Go 語言不需要你主動調用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變數,使用堆內存。編譯器的這個分析過程就叫做逃逸分析。

所以你在一個函數中通過 dict := make(map[string]int) 創建一個 map 變數,其背後的數據是放在棧空間上還是堆空間上,是不一定的。這要看編譯器分析的結果。

可逃逸分析並不是百分百準確的,它有缺陷。有的時候你會發現有些變數其實在棧空間上分配完全沒問題的,但編譯後程序還是把這些數據放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。

Go 語言雖然在內存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發,但如果你要在性能上較真的話,還是要掌握這些基礎知識。

這裡不對堆內存和棧內存的區別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數的結束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:

這裡舉一個小例子,來對比下堆棧的差別:

stack 函數中的變數 i 在函數退出會自動釋放;而 heap 函數返回的是對變數 i 的引用,也就是說 heap() 退出後,表示變數 i 還要能被訪問,它會自動被分配到堆空間上。

他們編譯出來的代碼如下:

邏輯的複雜度不言而喻,從上面的彙編中可看到, heap() 函數調用了 runtime.newobject() 方法,它會調用 mallocgc 方法從 mcache 上申請內存,申請的內部邏輯前面文章已經講述過。堆內存分配不僅分配上邏輯比棧空間分配複雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計算資源對其進行標記回收(也就是 GC 成本)。

Go 編輯器會自動幫我們找出需要進行動態分配的變數,它是在編譯時追蹤一個變數的生命周期,如果能確認一個數據只在函數空間內訪問,不會被外部使用,則使用棧空間,否則就要使用堆空間。

我們在 go build 編譯代碼時,可使用 -gcflags ‘-m’ 參數來查看逃逸分析日誌。

以上面的兩個函數為例,編譯的日誌輸出是:

日誌中的 i escapes to heap 表示該變數數據逃逸到了堆上。

需要使用堆空間,所以逃逸,這沒什麼可爭議的。但編譯器有時會將 不需要 使用堆空間的變數,也逃逸掉。這裡是容易出現性能問題的大坑。網上有很多相關文章,列舉了一些導致逃逸情況,其實總結起來就一句話:

多級間接賦值容易導致逃逸 。

這裡的多級間接指的是,對某個引用類對象中的引用類成員進行賦值。Go 語言中的引用類數據類型有 func , interface , slice , map , chan , *Type(指針) 。

記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數據類型,則會導致 Value 逃逸。這裡的等號 = 不單單只賦值,也表示參數傳遞。

根據公式,我們假設一個變數 data 是以下幾種類型,相應的可以得出結論:

下面給出一些實際的例子:

如果變數值是一個函數,函數的參數又是引用類型,則傳遞給它的參數都會逃逸。

上例中 te 的類型是 func(*int) ,屬於引用類型,參數 *int 也是引用類型,則調用 te(j) 形成了為 te 的參數(成員) *int 賦值的現象,即 te.i = j 會導致逃逸。代碼中其他幾種調用都沒有形成 多級間接賦值 情況。

同理,如果函數的參數類型是 slice , map 或 interface{} 都會導致參數逃逸。

匿名函數的調用也是一樣的,它本質上也是一個函數變數。有興趣的可以自己測試一下。

只要使用了 Interface 類型(不是 interafce{} ),那麼賦值給它的變數一定會逃逸。因為 interfaceVariable.Method() 先是間接的定位到它的實際值,再調用實際值的同名方法,執行時實際值作為參數傳遞給方法。相當於 interfaceVariable.Method.this = realValue

向 channel 中發送數據,本質上就是為 channel 內部的成員賦值,就像給一個 slice 中的某一項賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導致發送到 channel 中的數據逃逸。

這本來也是情理之中的,發送給 channel 的數據是要與其他函數分享的,為了保證發送過去的指針依然可用,只能使用堆分配。

可變參數如 func(arg …string) 實際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數逃逸的原因。

例子非常多,這裡不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導致數據逃逸。這裡加上 容易 二字是因為隨著語言的發展,相信這些問題會被慢慢解決,但現階段,這個可以作為我們分析逃逸現象的依據。

下面代碼中包含 2 種很常規的寫法,但他們卻有著很大的性能差距,建議自己想下為什麼。

Benchmark 和 pprof 給出的結果:

熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,並進行優化。

多級間接賦值會導致 Go 編譯器出現不必要的逃逸,在一些情況下可能我們只需要修改一下數據結構就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因為它會增加一級訪問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。

大多數情況下,性能優化都會為程序帶來一定的複雜度。建議實際項目中還是怎麼方便怎麼寫,功能完成後通過性能分析找到瓶頸所在,再對局部進行優化。

基礎知識 – Golang 中的格式化輸入輸出

【格式化輸出】

// 格式化輸出:將 arg 列表中的 arg 轉換為字元串輸出

// 使用動詞 v 格式化 arg 列表,非字元串元素之間添加空格

Print(arg列表)

// 使用動詞 v 格式化 arg 列表,所有元素之間添加空格,結尾添加換行符

Println(arg列表)

// 使用格式字元串格式化 arg 列表

Printf(格式字元串, arg列表)

// Print 類函數會返回已處理的 arg 數量和遇到的錯誤信息。

【格式字元串】

格式字元串由普通字元和佔位符組成,例如:

“abc%+ #8.3[3]vdef”

其中 abc 和 def 是普通字元,其它部分是佔位符,佔位符以 % 開頭(註:%% 將被轉義為一個普通的 % 符號,這個不算開頭),以動詞結尾,格式如下:

%[旗標][寬度][.精度][arg索引]動詞

方括弧中的內容可以省略。

【旗標】

旗標有以下幾種:

空格:對於數值類型的正數,保留一個空白的符號位(其它用法在動詞部分說明)。

0 :用 0 進行寬度填充而不用空格,對於數值類型,符號將被移到所有 0 的前面。

其中 “0” 和 “-” 不能同時使用,優先使用 “-” 而忽略 “0”。

【寬度和精度】

「寬度」和「精度」都可以寫成以下三種形式:

數值 | * | arg索引*

其中「數值」表示使用指定的數值作為寬度值或精度值,「 」表示使用當前正在處理的 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到下一個。「arg索引 」表示使用指定 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到指定 arg 的下一個。

寬度值:用於設置最小寬度。

精度值:對於浮點型,用於控制小數位數,對於字元串或位元組數組,用於控制字元數量(不是位元組數量)。

對於浮點型而言,動詞 g/G 的精度值比較特殊,在適當的情況下,g/G 會設置總有效數字,而不是小數位數。

【arg 索引】

「arg索引」由中括弧和 arg 序號組成(就像上面示例中的 [3]),用於指定當前要處理的 arg 的序號,序號從 1 開始:

‘[‘ + arg序號 + ‘]’

【動詞】

「動詞」不能省略,不同的數據類型支持的動詞不一樣。

[通用動詞]

v:默認格式,不同類型的默認格式如下:

布爾型:t

整 型:d

浮點型:g

複數型:g

字元串:s

通 道:p

指 針:p

無符號整型:x

T:輸出 arg 的類型而不是值(使用 Go 語法格式)。

[布爾型]

t:輸出 true 或 false 字元串。

[整型]

b/o/d:輸出 2/8/10 進位格式

x/X :輸出 16 進位格式(小寫/大寫)

c :輸出數值所表示的 Unicode 字元

q :輸出數值所表示的 Unicode 字元(帶單引號)。對於無法顯示的字元,將輸出其轉義字元。

U :輸出 Unicode 碼點(例如 U+1234,等同於字元串 “U+%04X” 的顯示結果)

對於 o/x/X:

如果使用 “#” 旗標,則會添加前導 0 或 0x。

對於 U:

如果使用 “#” 旗標,則會在 Unicode 碼點後面添加相應的 ‘字元’(前提是該字元必須可顯示)

[浮點型和複數型]

b :科學計數法(以 2 為底)

e/E:科學計數法(以 10 為底,小寫 e/大寫 E)

f/F:普通小數格式(兩者無區別)

g/G:大指數(指數 = 6)使用 %e/%E,其它情況使用 %f/%F

[字元串或位元組切片]

s :普通字元串

q :雙引號引起來的 Go 語法字元串

x/X:十六進位編碼(小寫/大寫,以位元組為元素進行編碼,而不是字元)

對於 q:

如果使用了 “+” 旗標,則將所有非 ASCII 字元都進行轉義處理。

如果使用了 “#” 旗標,則輸出反引號引起來的字元串(前提是

字元串中不包含任何製表符以外的控制字元,否則忽略 # 旗標)

對於 x/X:

如果使用了 ” ” 旗標,則在每個元素之間添加空格。

如果使用了 “#” 旗標,則在十六進位格式之前添加 0x 前綴。

[指針類型]

p :帶 0x 前綴的十六進位地址值。

[符合類型]

複合類型將使用不同的格式輸出,格式如下:

結 構 體:{欄位1 欄位2 …}

數組或切片:[元素0 元素1 …]

映 射:map[鍵1:值1 鍵2:值2 …]

指向符合元素的指針:{}, [], map[]

複合類型本身沒有動詞,動詞將應用到複合類型的元素上。

結構體可以使用 “+v” 同時輸出欄位名。

【注意】

1、如果 arg 是一個反射值,則該 arg 將被它所持有的具體值所取代。

2、如果 arg 實現了 Formatter 介面,將調用它的 Format 方法完成格式化。

3、如果 v 動詞使用了 # 旗標(%#v),並且 arg 實現了 GoStringer 介面,將調用它的 GoString 方法完成格式化。

如果格式化操作指定了字元串相關的動詞(比如 %s、%q、%v、%x、%X),接下來的兩條規則將適用:

4。如果 arg 實現了 error 介面,將調用它的 Error 方法完成格式化。

5。如果 arg 實現了 string 介面,將調用它的 String 方法完成格式化。

在實現格式化相關介面的時候,要避免無限遞歸的情況,比如:

type X string

func (x X) String() string {

return Sprintf(“%s”, x)

}

在格式化之前,要先轉換數據類型,這樣就可以避免無限遞歸:

func (x X) String() string {

return Sprintf(“%s”, string(x))

}

無限遞歸也可能發生在自引用數據類型上面,比如一個切片的元素引用了切片自身。這種情況比較罕見,比如:

a := make([]interface{}, 1)

a[0] = a

fmt.Println(a)

【格式化輸入】

// 格式化輸入:從輸入端讀取字元串(以空白分隔的值的序列),

// 並解析為具體的值存入相應的 arg 中,arg 必須是變數地址。

// 字元串中的連續空白視為單個空白,換行符根據不同情況處理。

// \r\n 被當做 \n 處理。

// 以動詞 v 解析字元串,換行視為空白

Scan(arg列表)

// 以動詞 v 解析字元串,換行結束解析

Scanln(arg列表)

// 根據格式字元串中指定的格式解析字元串

// 格式字元串中的換行符必須和輸入端的換行符相匹配。

Scanf(格式字元串, arg列表)

// Scan 類函數會返回已處理的 arg 數量和遇到的錯誤信息。

【格式字元串】

格式字元串類似於 Printf 中的格式字元串,但下面的動詞和旗標例外:

p :無效

T :無效

e/E/f/F/g/G:功能相同,都是掃描浮點數或複數

s/v :對字元串而言,掃描一個被空白分隔的子串

對於整型 arg 而言,v 動詞可以掃描帶有前導 0 或 0x 的八進位或十六進位數值。

寬度被用來指定最大掃描寬度(不會跨越空格),精度不被支持。

如果 arg 實現了 Scanner 介面,將調用它的 Scan 方法掃描相應數據。只有基礎類型和實現了 Scanner 介面的類型可以使用 Scan 類方法進行掃描。

【注意】

連續調用 FScan 可能會丟失數據,因為 FScan 中使用了 UnreadRune 對讀取的數據進行撤銷,而參數 io.Reader 只有 Read 方法,不支持撤銷。比如:

Go語言命令行利器cobra使用教程

cobra是一個提供簡單介面來創建強大的現代CLI界面的庫類似git git tools,cobra也是一個應用程序,它會生成你的應用程序的腳手架來快速開發基於cobra的應用程序

cobra提供:

cobra建立在命令、參數、標誌的結構之上

commands代表動作,args是事物,flags是動作的修飾符

最好的應用程序在使用時讀起來就像句子,因此,用戶直觀地知道如何與它們交互

模式如下:APPNAME VERB NOUN –ADJECTIVE. or APPNAME COMMAND ARG –FLAG(APPNAME 動詞 名詞 形容詞 或者 APPNAME 命令 參數 標誌)

一些真實世界的好例子可以更好地說明這一點

kubectl 命令更能體現APPNAME 動詞 名詞 形容詞

如下的例子,server 是command,port是flag

這個命令中,我們告訴git 克隆url

命令是應用程序的中心點,應用程序支持的每一個交互都包含在一個命令中,命令可以有子命令,也可以運行操作

在上面的例子中,server是命令

更多關於cobra.Command

flag是一種修改命令行為的方式,cobra支持完全兼容POSIX標誌,也支持go flag package,cobra可以定義到子命令上的標誌,也可以僅對該命令可用的標誌

在上面的命令中,port是標誌

標誌的功能由 pflag library 提供,pflag library是flag標準庫的一個分支,在添加POSIX兼容性的同時維護相同的介面。

使用cobra很簡單,首先,使用go get按照最新版本的庫,這個命令會安裝cobra可執行程序以及庫和依賴項

下一步,引入cobra到應用程序中

雖然歡迎您提供自己的組織,但通常基於Cobra的應用程序將遵循以下組織結構:

在Cobra應用程序中,main.go文件通常非常簡單。它有一個目的:初始化Cobra。

使用cobra生成器

cobra提供了程序用來創建你的應用程序然後添加你想添加的命令,這是將cobra引入應用程序最簡單的方式

這兒 你可以發現關於cobra的更多信息

要手動實現cobra,需要創建一個main.go 和rootCmd文件,可以根據需要提供其他命令

Cobra不需要任何特殊的構造器。只需創建命令。

理想情況下,您可以將其放在app/cmd/root.go中:

在init()函數中定義標誌和處理配置

例子如下,cmd/root.go:

創建main.go

使用root命令,您需要讓主函數執行它。為清楚起見,Execute應該在根目錄下運行,儘管它可以在任何命令上調用。

在Cobra應用程序中,main.go文件通常非常簡單。它有一個目的:初始化Cobra。

可以定義其他命令,通常每個命令在cmd/目錄中都有自己的文件。

如果要創建版本命令,可以創建cmd/version.go並用以下內容填充它:

如果希望將錯誤返回給命令的調用者,可以使用RunE。

然後可以在execute函數調用中捕獲錯誤。

標誌提供修飾符來控制操作命令的操作方式。

由於標誌是在不同的位置定義和使用的,因此我們需要在外部定義一個具有正確作用域的變數來分配要使用的標誌。

有兩種不同的方法來分配標誌。

標誌可以是「持久」的,這意味著該標誌將可用於分配給它的命令以及該命令下的每個命令。對於全局標誌,在根上指定一個標誌作為持久標誌。

也可以在本地分配一個標誌,該標誌只應用於該特定命令。

默認情況下,Cobra只解析目標命令上的本地標誌,而忽略父命令上的任何本地標誌。通過啟用Command.TraverseChildren,Cobra將在執行目標命令之前解析每個命令上的本地標誌。

使用viper綁定標誌

在本例中,持久標誌author與viper綁定。注意:當用戶未提供–author標誌時,變數author將不會設置為config中的值。

更多關於 viper的文檔

Flags默認是可選的,如果希望命令在未設置標誌時報告錯誤,請根據需要進行標記:

持久性Flags

可以使用命令的Args欄位指定位置參數的驗證。

內置了以下驗證器:

在下面的示例中,我們定義了三個命令。兩個是頂級命令,一個(cmdTimes)是頂級命令之一的子命令。在這種情況下,根是不可執行的,這意味著需要一個子命令。這是通過不為「rootCmd」提供「Run」來實現的。

我們只為一個命令定義了一個標誌。

有關標誌的更多文檔,請訪問

對於一個更完整的例子更大的應用程序,請檢查 Hugo 。

當您有子命令時,Cobra會自動將help命令添加到應用程序中。當用戶運行「應用程序幫助」時,將調用此函數。此外,help還支持所有其他命令作為輸入。例如,您有一個名為「create」的命令,沒有任何附加配置;調用「app help create」時,Cobra將起作用。每個命令都會自動添加「-help」標誌。

以下輸出由Cobra自動生成。除了命令和標誌定義之外,不需要任何東西。

幫助就像其他命令一樣。它周圍沒有特殊的邏輯或行為。事實上,你可以提供你想提供的。

您可以為默認命令提供自己的幫助命令或模板,以用於以下功能:

當用戶提供無效的標誌或無效的命令時,Cobra通過向用戶顯示「用法」來響應。

你可以從上面的幫助中認識到這一點。這是因為默認幫助將用法作為其輸出的一部分嵌入。

您可以提供自己的使用函數或模板供Cobra使用。與幫助一樣,函數和模板也可以通過公共方法重寫:

如果在root命令上設置了version欄位,Cobra會添加一個頂級的’–version’標誌。運行帶有「-version」標誌的應用程序將使用版本模板將版本列印到標準輸出。可以使用cmd.SetVersionTemplate(s string)函數自定義模板。

可以在命令的主運行函數之前或之後運行函數。PersistentPreRun和PreRun函數將在運行之前執行。PersistentPostRun和PostRun將在運行後執行。如果子函數不聲明自己的函數,則它們將繼承Persistent*Run函數。這些函數按以下順序運行:

輸出:

當發生「未知命令」錯誤時,Cobra將列印自動建議。這使得Cobra在發生拼寫錯誤時的行為類似於git命令。例如:

基於註冊的每個子命令和Levenshtein距離的實現,建議是自動的。匹配最小距離2(忽略大小寫)的每個已註冊命令都將顯示為建議。

如果需要在命令中禁用建議或調整字元串距離,請使用:

or

您還可以使用SuggestFor屬性顯式設置將為其建議給定命令的名稱。這允許對在字元串距離方面不接近的字元串提供建議,但在您的一組命令中是有意義的,並且對於某些您不需要別名的字元串。例子:

Cobra可以基於子命令、標誌等生成文檔。請在 docs generation文檔 中閱讀更多關於它的信息。

Cobra可以為以下shell生成shell完成文件:bash、zsh、fish、PowerShell。如果您在命令中添加更多信息,這些補全功能將非常強大和靈活。在 Shell Completions 中閱讀更多關於它的信息。

Cobra is released under the Apache 2.0 license. See LICENSE.txt

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/249697.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-12 17:11
下一篇 2024-12-12 17:12

相關推薦

  • Python簡單數學計算

    本文將從多個方面介紹Python的簡單數學計算,包括基礎運算符、函數、庫以及實際應用場景。 一、基礎運算符 Python提供了基礎的算術運算符,包括加(+)、減(-)、乘(*)、除…

    編程 2025-04-29
  • Python滿天星代碼:讓編程變得更加簡單

    本文將從多個方面詳細闡述Python滿天星代碼,為大家介紹它的優點以及如何在編程中使用。無論是剛剛接觸編程還是資深程序員,都能從中獲得一定的收穫。 一、簡介 Python滿天星代碼…

    編程 2025-04-29
  • Python海龜代碼簡單畫圖

    本文將介紹如何使用Python的海龜庫進行簡單畫圖,並提供相關示例代碼。 一、基礎用法 使用Python的海龜庫,我們可以控制一個小海龜在窗口中移動,並利用它的「畫筆」在窗口中繪製…

    編程 2025-04-29
  • Python櫻花樹代碼簡單

    本文將對Python櫻花樹代碼進行詳細的闡述和講解,幫助讀者更好地理解該代碼的實現方法。 一、簡介 櫻花樹是一種圖形效果,它的實現方法比較簡單。Python中可以通過turtle這…

    編程 2025-04-28
  • Python大神作品:讓編程變得更加簡單

    Python作為一種高級的解釋性編程語言,一直被廣泛地運用於各個領域,從Web開發、遊戲開發到人工智慧,Python都扮演著重要的角色。Python的代碼簡潔明了,易於閱讀和維護,…

    編程 2025-04-28
  • 用Python實現簡單爬蟲程序

    在當今時代,互聯網上的信息量是爆炸式增長的,其中很多信息可以被利用。對於數據分析、數據挖掘或者其他一些需要大量數據的任務,我們可以使用爬蟲技術從各個網站獲取需要的信息。而Pytho…

    編程 2025-04-28
  • 如何製作一個簡單的換裝遊戲

    本文將從以下幾個方面,為大家介紹如何製作一個簡單的換裝遊戲: 1. 遊戲需求和界面設計 2. 使用HTML、CSS和JavaScript開發遊戲 3. 實現遊戲的基本功能:拖拽交互…

    編程 2025-04-27
  • Guava Limiter——限流器的簡單易用

    本文將從多個維度對Guava Limiter進行詳細闡述,介紹其定義、使用方法、工作原理和案例應用等方面,並給出完整的代碼示例,希望能夠幫助讀者更好地了解和使用該庫。 一、定義 G…

    編程 2025-04-27
  • 2的32次方-1:一個看似簡單卻又複雜的數字

    對於計算機領域的人來說,2的32次方-1(也就是十進位下的4294967295)這個數字並不陌生。它經常被用來表示IPv4地址或者無符號32位整數的最大值。但實際上,這個數字卻包含…

    編程 2025-04-27
  • 製作一個簡單的管理系統的成本及實現

    想要製作一個簡單的管理系統,需要進行技術選型、開發、測試等過程,那麼這個過程會花費多少錢呢?我們將從多個方面來闡述製作一個簡單的管理系統的成本及實現。 一、技術選型 當我們開始思考…

    編程 2025-04-27

發表回復

登錄後才能評論