Go CLI 教程:Fortune 克隆

我之前写了两篇 CLI 应用的教程,分别是构建 gololcat 和 gocowsay。在这两篇教程中,我都使用了 fortune 作为输入生成器。 在本文中,我将用 Go 完成这个“管道三部曲”的最后一部分 - gofortune。 首先,什么是 fortune?参考 维基百科的定义,Fortune 是一款简单的程序,从一个引述数据库中随机显示一条信息。 说简单点,它是一个随机引述生成器。 它的历史可以追溯到 Unix Version 7 (1979)。至今仍然广泛应用。许多 Linux 发行版都默认安装了它,而在 MacOS 上,可以通过 brew install fortune 命令进行安装。 在某些系统上,它还会在使用 shell 时作为欢迎语或告别语。 维基百科上还提到: 许多人选择将 fortune 输入到 cowsay 命令中,以增添对话的幽默感。 而对于我来说,我会使用我的 gocowsay 命令。 好了,先到这里,让我们用 Go 构建一个 Fortune 的克隆版本。 下面是程序的功能概述。 fortunes 文件夹的位置与系统和发行版有关,可以使用构建标志硬编码它,或者使用环境变量。但为了锻炼,我要做一件“肮脏”的事情,直接询问 fortune,通过执行 fortune -f 命令,并得到如下输出: 输出的第一行包含 fortunes 文件夹的路径。 package main import ( "fmt" "os/exec" ) func main() { out, err := exec....

Go 中的函式

函式是一段具有名稱的程式碼塊,其中包含一些指令。 在「Hello, World!」的範例中,我們建立了一個 main 函式,這是程式的入口點。 package main import "fmt" func main() { fmt.Println("Hello, World!") } 這是一個特殊的函式。 通常,我們會給函式取一個自訂的名稱: func doSomething() { } 然後你可以呼叫它: doSomething() 函式可以接受參數,我們必須像這樣設定參數的型別: func doSomething(a int, b int) { } doSomething(1, 2) a 和 b 是我們內部與函式相關聯的參數名稱。 函式可以返回一個值,像這樣: func sumTwoNumbers(a int, b int) int { return a + b } result := sumTwoNumbers(1, 2) 請注意我們指定了返回值的 型別 在 Go 中,函式可以返回超過一個值: func performOperations(a int, b int) (int, int) { return a + b, a - b } sum, diff := performOperations(1, 2) 這很有趣,因為許多語言只允許返回一個值。...

Go 中的地圖(Maps)

在 Go 中,地圖(map)是一種非常有用的資料類型。 在其他程式語言中,它也被稱為字典、哈希表或關聯陣列。 以下是如何創建一個地圖: agesMap := make(map[string]int) 你不需要設定地圖可以容納多少項目。 你可以以這種方式將新項目添加到地圖中: agesMap["flavio"] = 39 你還可以使用以下語法直接初始化地圖的值: agesMap := map[string]int{"flavio": 39} 你可以使用以下方式獲取與鍵關聯的值: age := agesMap["flavio"] 你可以使用 delete() 函式來從地圖中刪除項目: delete(agesMap, "flavio")

Go 中的指標

假設你有一個變數: age := 20 使用 &age 可以取得這個變數的指標,也就是它在記憶體裡的位址。 當你有了這個變數的指標後,可以使用 * 運算子來取得它指向的值: age := 20 ageptr := &age agevalue := *ageptr 這在你想要呼叫一個函式並將該變數作為參數傳遞時很有用。預設情況下,Go 會在函式中複製該變數的值,所以這不會改變 age 的值: func increment(a int) { a = a + 1 } func main() { age := 20 increment(age) //age 仍然是 20 } 你可以使用指標來解決這個問題: func increment(a *int) { *a = *a + 1 } func main() { age := 20 increment(&age) //age 現在變成了 21 }

Go 資料結構:二元搜尋樹

在這篇文章中,我們將分析和實現二元搜尋樹的資料結構。 樹 是一個有階層結構的表示法。通常我們可以通過想像一個家族的族譜樹來理解樹的概念。 二元搜尋樹是一種每個節點最多有兩個子節點的樹。 二元搜尋樹具有左節點的值小於右節點的值的特性。 這是我們在本文中要建構的資料結構。它是一個非常有用的資料結構,可以有效地存儲和索引數據,並且可以快速檢索數據。 詞彙定義: 根:樹的第 0 層 子節點:除了根節點以外的每個節點 內部節點:具有至少一個子節點的節點 葉節點:沒有子節點的每個節點 子樹:以特定節點為根的一組節點 預備信息: 一個二元搜尋樹資料結構將公開以下方法: Insert(t):插入項目 t 到樹中 Search(t):如果項目 t 存在於樹中,則返回 true InOrderTraverse():按中序遍歷訪問所有節點 PreOrderTraverse():按先序遍歷訪問所有節點 PostOrderTraverse():按後序遍歷訪問所有節點 Min():返回存儲在樹中的最小值項目 Max():返回存儲在樹中的最大值項目 Remove(t):從樹中刪除項目 t String():打印可讀取的樹形結構 我將創建一個 ItemBinarySearchTree 的泛型類型,並且支持併發,它可以通過使用 genny 來生成包含任何類型的樹,從而封裝了包含數據的實際值特定資料結構。 我將節點定義為: // Node 組成樹的單個節點 type Node struct { key int value Item left *Node // 左節點 right *Node // 右節點 } 這個 key 值允許使用任何類型的項目,並且使用一個單獨的值來計算正確的位置。我實現它為整數,但它可以使用任何可以進行比較的類型。 將項目插入樹中需要使用遞歸,因為我們需要找到確定的位置來插入它。規則是,如果節點的鍵值小於當前節點的鍵值,我們將其作為左子節點插入,如果沒有左子節點。否則,我們使用左子節點作為基礎節點重新計算位置。對於右子節點也是同樣的原則。 遍歷是指遍歷樹的過程。我們實現三種不同的方法來遍歷,因為有三種不同的方法。以二元搜尋樹為例: 我們可以以以下方式進行遍歷: 中序遍歷:通過遵循最小鏈接來訪問所有節點,直到找到最左側的葉節點,然後處理該葉節點,通過進入與當前節點關聯的下一個最小鍵值來移動到其他節點。在上面的圖中,遍歷順序為:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11。 先序遍歷:在訪問子節點之前,先訪問節點本身。在上面的圖中,遍歷順序為:8 -> 4 -> 2 -> 1 -> 3 -> 6 -> 5 -> 7 -> 10 -> 9 -> 11。 後序遍歷:找到最小的葉節點(最左側的葉節點),然後處理它的兄弟節點和父節點,然後進入下一個子樹,並向上遍歷到父節點。在上面的圖中,遍歷順序為:1 -> 3 -> 2 -> 5 -> 7 -> 6 -> 4 -> 9 -> 11 -> 10 -> 8。 我們在測試中使用的 String 方法會打印出上述樹的可視化結果:...

golang-slices

#Go中的切片 切片是一种类似于数组的数据结构,但其大小可变。 在底层,切片使用数组,并且它们是在数组之上构建的抽象,使它们更加灵活和有用(将数组视为较低级别)。 您将以与在高级语言中使用数组非常相似的方式使用切片。 您可以类似于数组定义一个切片,省略长度: var mySlice []string //字符串切片 您可以使用值初始化切片: var mySlice = []string{"First", "Second", "Third"} //或者 mySlice := []string{"First", "Second", "Third"} 您可以使用make()函数创建一个指定长度的空切片: mySlice := make([]string, 3) //长度为3的空字符串切片 您可以从现有切片创建一个新的切片,并向其附加一个或多个项目: mySlice := []string{"First", "Second", "Third"} newSlice := append(mySlice, "Fourth", "Fifth") 请注意,我们需要将append()的结果赋给一个新切片,否则会出现编译器错误。原始切片不会被修改,我们得到的是一个全新的切片。 您还可以使用copy()函数复制一个切片,使其不与其他切片共享内存,并且是独立的: mySlice := []string{"First", "Second", "Third"} newSlice := make([]string, 3) copy(newSlice, mySlice) 如果要复制到的切片没有足够的空间(比原始切片短),则仅复制第一个项目(直到有空间为止)。 您可以从数组初始化一个切片: myArray := [3]string{"First", "Second", "Third"} mySlice = myArray[:] 多个切片可以使用同一数组作为底层数组: myArray := [3]string{"First", "Second", "Third"} mySlice := myArray[:] mySlice2 := myArray[:] mySlice[0] = "test" fmt....

Go中的循環結構

Go中的循環結構 Go語言中最好的功能之一是為你提供更少的選擇。 我們只有一個循環語句:for。 我們可以像這樣使用它: for i := 0; i < 10; i++ { fmt.Println(i) } 我們首先初始化一個循環變量,然後設置我們每次迭代檢查的條件,以決定是否結束循環,最後在每次迭代結束時執行後置語句,在這個例子中是對i進行增量運算。 i++對變量i進行遞增。 < 運算符 用於比較i和數字10,返回true或false,決定是否執行循環主體。 與C或JavaScript等其他語言不同,我們不需要在這個區塊周圍加上括號。 其他語言提供了不同類型的循環結構,但Go只有這一種。如果你熟悉具有while循環結構的語言,我們可以像這樣模擬一個while循環: i := 0 for i < 10 { fmt.Println(i) i++ } 我們還可以完全省略條件,並在需要時使用break結束循環: i := 0 for { fmt.Println(i) if i < 10 { break } i++ } 我在循環主體內使用了一個if語句,但我們還沒有看到條件語句!我們將在下一節介紹它。 現在我要介紹的一個東西是range。 我們可以使用for來使用以下語法迭代一個數組: numbers := []int{1, 2, 3} for i, num := range numbers { fmt.Printf("%d: %d\n", i, num) } //0: 1 //1: 2 //2: 3 注意:我使用了fmt....

Go中的結構體

結構體是一種包含一個或多個變量的類型。它類似於一組變量,我們稱之為字段。這些字段可以具有不同的類型。 下面是一個結構體定義的示例: type Person struct { Name string Age int } 請注意,我們使用大寫字母命名字段,否則這些字段將對包私有。當您將結構體傳遞給其他包提供的函數(例如用於處理 JSON 或數據庫的函數)時,將無法訪問這些字段。 一旦我們定義了結構體,我們可以使用該類型初始化變量: flavio := Person{"Flavio", 39} 並且我們可以使用點語法訪問個別的字段: flavio.Age // 39 flavio.Name // "Flavio" 您還可以以以下方式從結構體初始化新變量: flavio := Person{Age: 39, Name: "Flavio"} 這使您只能初始化一個字段: flavio := Person{Age: 39} 或者甚至可以在不指定任何值的情況下初始化它: flavio := Person{} // 或者 var flavio Person 然後再設置其值: flavio.Name = "Flavio" flavio.Age = 39 結構體非常有用,因為您可以將不相關的數據進行分組,並將其傳遞給/從函數中,存儲在切片中等等。 定義後,結構體就像int或string一樣是一種類型,這意味著您還可以在其他結構體中使用它: type FullName struct { FirstName string LastName string } type Person struct { Name FullName Age int }

Go工作空間介紹

Go的一個特點是我們稱之為工作空間。 工作空間是Go的“主體”。 默認情況下,Go選擇$HOME/go路徑,所以你會在家目錄中看到一個go文件夾。 它首次在安裝封包時創建(稍後我們將看到),也用於存儲一些工具。例如,當我在VS Code中加載hello.go文件時,它提示我安裝[gopls](https://pkg.go.dev/golang.org/x/tools/gopls)命令、Delve調試器(dlv)和[staticcheck linter](https://staticcheck.io/)。 它們被自動安裝在$HOME/go下: 當你使用go install安裝封包時,它們將存儲在這裡。這就是我們所謂的GOPATH。 你可以更改GOPATH環境變量以更改Go應該安裝封包的位置。 這在同時處理不同項目並希望隔離使用的庫時非常有用。

Go程式語言介紹

這篇文章是關於Go語言的新系列開始。 Go是一種令人驚嘆、簡單、現代且快速的程式語言。 它是編譯型的、開源的、強類型的。 它是由Google工程師創建的,目標如下: 讓他們的專案編譯(和運行)更快 簡單易懂,人們可以在很短的時間內上手 具有足夠的低級功能,但也避免了一些過於低級的問題 可攜性強(編譯的Go程序是二進位文件,不需要其他文件來運行,並且跨平台,因此可以輕鬆分發) 乏味、穩定、可預測,提供少量犯錯的機會 方便利用多處理器系統的優點 它旨在成為C和C++的替代品。 此外,由於其兼容性特性,它可以與C和C++代碼庫一起使用。 Go可用於許多不同的需求,既可以解決簡單的需求,也可以解決非常複雜的需求。 您可以創建命令行工具、網路伺服器,並且在許多不同的情境中被廣泛使用。 Docker和Kubernetes都是使用Go編寫的。 我最喜歡的靜態網站生成器(Hugo)是用Go編寫的。 Caddy,一個相當流行的網頁伺服器,也是用Go編寫的。 有很多不同的常用工具在底層使用這個程式語言。 本手冊將向您介紹這個程式語言。 在我們深入研究語言的具體細節之前,以下是您應該知道的一些事項。 首先,https://go.dev是這個程式語言的主頁。這將成為您的首選資源: 從https://go.dev/doc/install下載Go二進制文件(go命令和其他相關工具) 參考官方Go文檔https://go.dev/doc/ 查看所有Go封包https://pkg.go.dev/ 訪問Go Playgroundhttps://go.dev/play/ …等等 前往https://go.dev/doc/install,並下載適合您操作系統的套件。 運行安裝程式,在流程結束時,您將在終端機中可以使用go命令: 打開終端機,運行go version,您應該看到類似於這樣的內容: 注意:在運行該程式之前,您可能需要打開一個新的終端機,因為安裝程式將Go二進制文件夾添加到了路徑中。 Go安裝文件的具體位置取決於您的操作系統。 在macOS中,它位於/usr/local/go,並且二進制文件位於/usr/local/go/bin。 在Windows上,它將位於C:\Program Files\go。 Windows和Mac的安裝程式將自動設置Go二進制文件路徑。 在macOS上,您還可以通過使用brew install golang通過Homebrew安裝Go。這樣將更容易進行後續的更新。 在Linux上,您需要在解壓縮Linux套件到/usr/local/go之後,將Go二進制文件夾添加到終端機路徑中,具體方法如下: echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile source $HOME/.profile 我推薦使用VS Code(又稱VS Code)作為您的編輯器。 閱讀有關**在VS Code中使用Go**的指南,快速進行“上手”設置。至少安裝Go擴展。 這個擴展將為您提供更簡單的生活,它提供IntelliSense(語法高亮顯示、自動完成、懸停提示、錯誤突出顯示…)和其他功能,如自動格式化、安裝封包的菜單選項、測試等等。 我建議您在VS Code設置中啟用“在保存時格式化”和“在粘貼時格式化”功能: 在Go中,注釋使用了通常的C/C++/JavaScript/Java語法: // 這是單行注釋 /* 多行 注釋 */ 該語言沒有語義上重要的空格。與C、C++、Rust、Java、JavaScript一樣,不同於Python,其中空格具有意義,用於創建塊而不是花括號。 分號是可選的,就像JavaScript一樣。而不像C、C++、Rust或Java。 Go對縮排和視覺順序非常重視。 當我們安裝Go時,還可以使用gofmt命令行工具來格式化Go程序。VS Code在幕後使用該工具來格式化Go源文件。 這非常有趣和創新,因為格式化和類似“應該將花括號放在循環定義的同一行還是下一行”的問題是一個巨大的浪費時間的問題。 語言創建者定義了規則,每個人都使用這些規則。...