Go語言中根據型別的特點可以分成三類,分別是內建型別、參照型別和結構型別。下面就來分別為大家介紹一下這三種型別。
內建型別
內建型別是由語言提供的一組型別。分別是數值型別、字串型別和布林型別,我們將在後面的講解中一一為大家介紹這些型別。內建型別本質上是原始的型別。因此,當對這些值進行增加或者刪除的時候,會建立一個新值。
基於這個結論,當把這些型別的值傳遞給方法或者函數時,應該傳遞一個對應值的副本。讓我們看一下標準庫裡使用這些內建型別的值的函數,如下面程式碼所示。
func Trim(s string, cutset string) string {
if s == "" || cutset == "" {
return s
}
return TrimFunc(s, makeCutsetFunc(cutset))
}
通過上面的程式碼可以看到標準庫裡 string 包的 Trim 函數。Trim 函數傳入一個 string 型別的值 s 作操作,再傳入一個 string 型別的值 cutset 用於查詢。之後函數會返回一個新的 string 值作為操作結果。這個函數對呼叫者原始的 string 值的一個副本做操作,並返回一個新的 string 值的副本。
字串(string)跟整數、浮點數和布林值一樣,本質上是一種很原始的資料值,所以在函數或方法內外傳遞時,要傳遞字串的一份副本。
讓我們看一下體現內建型別原始本質的第二個例子,如下面程式碼所示。
func isShellSpecialVar(c uint8) bool {
switch c {
case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return true
}
return false
}
上面的程式碼展示了 env 包裡的 isShellSpecialVar 函數。這個函數傳入了一個 uint8 型別的值,並返回一個 bool 型別的值。注意,這裡的引數沒有使用指標來共用引數的值或者返回值。呼叫者傳入了一個 uint8 值的副本,並接受一個返回值 true 或者 false。
參照型別
Go語言裡的參照型別有如下幾個:切片、對映、通道、介面和函數型別。當宣告上述型別的變數時,建立的變數被稱作檔頭(header)值。字串也是一種參照型別。
每個參照型別建立的檔頭值都包含一個指向底層資料結構的指標。每個參照型別還包含一組獨特的欄位,用於管理底層資料結構。檔頭值是為複製而設計的,其中包含一個指標,因此通過複製來傳遞一個參照型別值的副本,本質上就是在共用底層資料結構。
結構型別
結構型別可以用來描述一組資料值,如果決定在某些東西需要刪除或者新增某個結構型別值的時候該結構型別的值不應該被更改,那麼需要遵守之前提到的內建型別和參照型別的規範。讓我們從標準庫裡的一個原始本質型別的結構實現開始,程式碼如下所示。
type Time struct {
// sec 給出自公元 1 年1 月1 日00:00:00
// 開始的秒數
sec int64
// nsec 指定了一秒內的納秒偏移,
// 這個值是非零值,
// 必須在[0, 999999999]範圍內
nsec int32
// loc 指定了一個Location,
// 用於決定該時間對應的當地的分、小時、
// 天和年的值
// 只有Time 的零值,其loc 的值是nil
// 這種情況下,認為處於UTC 時區
loc *Location
}
上述程式碼中的 Time 結構選自 time 包。當獲取時間值時,應該意識到給定的一個時間點的時間是不能修改的。所以標準庫裡也是這樣實現 Time 型別的。讓我們看一下 Now 函數是如何建立 Time 型別的值的,程式碼如下所示。
func Now() Time {
sec, nsec := now()
return Time{sec + unixToInternal, nsec, Local}
}
上述程式碼中的程式碼展示了 Now 函數的實現。這個函數建立了一個 Time 型別的值,並給呼叫者返回了 Time 值的副本。這個函數沒有使用指標來共用 Time 值。之後,讓我們來看一個 Time 型別的方法,程式碼如下所示。
func (t Time) Add(d Duration) Time {
t.sec += int64(d / 1e9)
nsec := int32(t.nsec) + int32(d%1e9)
if nsec >= 1e9 {
t.sec++
nsec -= 1e9
} else if nsec < 0 {
t.sec--
nsec += 1e9
}
t.nsec = nsec
return t
}
上面程式碼中的 Add 方法使用按值傳遞,並返回了一個新的 Time 值。該方法操作的是呼叫者傳入的 Time 值的副本,並且給呼叫者返回了一個方法內的 Time 值的副本。
至於是使用返回的值替換原來的 Time 值,還是建立一個新的 Time 變數來儲存結果,是由呼叫者決定的事情。