為什么會有這個系列?
因為我要往架構方向靠攏啊。
關於架構,其實架構的書我看了《架構整潔之道》,也有《實現驅動領域設計》。但是我感覺明顯還不夠,所以我在極客時間買了一個架構相關的專欄,這個專欄寫的編程語言是 go,為了更好的學習與理解,所以才有這個系列。
我在使用vscode進行go編程時,總會顯示一下警告
type Service struct {
a *ClassName
}
exported type Service should have comment or be unexported
這是因為你安裝插件 gopls
對代碼會有一些規則。即如果你定一個 變量/類 時,如果開頭是大寫字母,那么編輯器就會檢測出你沒有針對這個 變量/類 進行注釋。這個時候有兩種選擇
- 將大寫字母改成小寫字母 service
- 進行特定格式注釋,如上面注釋就是這樣
//Service 服務類
其實除了這個還有一種做法,直接設置vscod的相關檢測的屬性"go.lintFlags":["--disable=all"]
,這樣就不用寫那些煩人的注釋啦。
golang 的編碼習慣有個很有意思,就是它的所有變量、方法、類 等等在代碼的末句全都不需要打分號,就算你打了分號,編輯器一樣也會給你自動省略
怎么導入第三方庫?
直接在官方倉庫地址選擇自己要導入的模塊,地址見:https://pkg.go.dev
import ("moduleName")
但是我當初這么做的時候,發現雖然編譯器沒有檢測到錯誤,但是在引用 module 的 api 時,沒有智能提示。后來發現跟 nuget 是一樣的,有快捷鍵導入 module: shift + command + p
然后選擇 Go: Add Import
選中你本地 clone 下來的第三方類庫。但是這里有一個疑問,難道要每次下載源代碼嗎?而是不能夠直接下載一個類似 dll 的可執行的 “微文件” 么。講道理是肯定有的,不可能一個項目發布出去,還把人家的源代碼發不出去的。這個之后弄到發布的時候在回過頭來查資料吧
錯誤1: expected ';', found f
這個是不同編輯器的編碼問題,拿我現在用的 vscode 為例,我新建的 go 項目的默認編碼是 LF/CRLF
,切換成 CRLF/LF
。注意,如果切換的時候發現還是報同樣的錯誤,那極有可能是 vscode 沒有反應過來,只要在當前頁面隨便輸個空格在保存即可。
接下來就是各種變量函數的基本用法介紹了
null 值:golong 用 nil 代表 null,這個很特別啊,大多數語言都是 null
定義類:用 type 關鍵字 type ClassName struct{ someField int}
申明變量:var scopeVar = "string"
我試了一下,這種顯示寫法也行 var scopeVar string = ""
。
但是有這么一種寫法 localVar := ""
,也很有趣,我嘗試了一下,這個好像只能在方法里面寫(就相當於 var localVar string = ""
),這種寫法提升到 “全局” 則不行。
golong 具有指針概念
指針保存的是變量的內存地址。比如 var p *int
p 表示的是整形的地址,其零值是 nil
這個跟 c++ 的指針是一樣的,比較復雜,當時上大學的我上課上到這個地方的時候很懵,什么“指針”,“指針的引用”,“指針的指針”,“ * ”,“ & ” 等總是搞不清楚。在用 vc++ 6.0 時代下,編寫代碼沒有任何提示,簡直是難如登天。
但是現在時代不同啦,ide/編輯器 可以自動幫你做正確的選擇。這次偶然的機會學習 golang,不過我還是有必要把這地方的知識弄清楚。
首先看下面代碼,我把注釋寫在邊上
var p *int // 變量 p 代表是整形的內存地址
i := 11 // 就一般的變量賦值
p = &i // 給內存地址指針變量 p 賦值 11 的指針變量
*p = 1 // p 地址的值賦值為 1
第二行我就不解釋了。
第一行代碼就是定義一個指向整形的內存地址的變量 p。
第三行代碼表示你要給一個整形的地址賦值,那么肯定不是直接賦值一個整數 i,而是這個變量 i 指向的整數的地址 &i。其實可以理解為 i 的一個引用。
第四行我要直接給 p 指針指向的地址具體的值,那就是我們之前說的 “指針的指針:*p = 1”。
函數申明
函數在 golang 里面同 js 是一樣 —— 一等公民。也就是你無論寫在哪里,它都是可以在當前域是有效,可以引用的。
函數申明分兩種
- 無返回值:
func SomeMethod() {}
- 帶參數:
func SomeMethod(a int) {}
- 帶參數:
- 有返回值:
func SomeMethodAndReturn() ReturnValue {}
函數這里面有個好玩的約定:
- 函數名首字母是小寫就是 private 私有方法
- 函數名首字母是大寫則是 public 共有方法
我們還可以定一個函數類(函數類就相當於 C# 的委托,委托對於 CLR 而言就是一個含有這么一個函數的類,也可以當做 Java 中的內部類處理)。實例代碼如下所示
type delegateFunc func(string) // 定一個委托
func serve(msg string){
fmt.Printf(msg)
}
func main(){
d := delegateFunc(serve) // 把函數當作參數傳遞
d("marson shine")
}
方法定義
方法的定義跟函數很像: func (type類型參數) MethodName(parameters) ReturnValue {}
先來看官網對方法的定義:
一個 type 指定的類型可以關聯方法集。一個接口類型的方法集是其接口。任何類型 T 的方法集,由它作為接收器接收所有方法。對應的指針類型 *T 的方法集是由接收器 *T 或者是 T 申明的方法集(那也就說,它包含了 T 的所有方法集)。更多的規則運用在包含那些匿名字段的結構(struct)上。任何類型都有空的方法集。在一個方法集中,每個方法必須有一個唯一的不為空的名稱。
我們舉個例子來說明:
func (typeName ClassName) MethodName(parameter string) string {
}
這里我們定義了一個方法,指定的接收器就是 ClassName 類型,即我們得先有個接收器,才能有這個方法集 MethodName
type ClassName struct {
userName string
}
那么這個時候我就可以出實話一個 ClassName,然后就可以調用方法 MethodName 了。
對於上面的方法定義,其實還有一種寫法是這樣的:
func (typeName *ClassName) MethodName(parameter string) string {
}
這個我翻閱了下資料,發現這個還是很有趣的,這個跟編譯器以及 golang 本身的 “函數式編程” 的特性有關。函數式有個很重要的特征,就是 “無狀態” 的。舉個例子,我新建一個函數,這個函數本身是無狀態的,只要你傳入的參數不變,那么這個函數得到的值就是恆定值。我們拿之前的方法為例子 func (typeName ClassName) MethodName(parameter string) string {}
這個就是說你無法更改 typeName 這個值,它是不變量的。你只能在這個函數領域下更改,一旦這個方法返回(指定的棧地址)則傳入的 typeName 就還是之前的狀態。如果你要像 C# 一樣傳遞一個引用,在局部更改返回后,這個引用對象同樣也會更改的話,改怎么實現呢?也很簡單,只要在原來的基礎之上加個 “ * ”,也就是上面的寫法。