類型系統(tǒng)
Go
語言是更好的 C
語言,很多思想來源于 C
語言,畢竟 Go
的設(shè)計(jì)者就是 C
的設(shè)計(jì)者在幾十年之后再創(chuàng)新高。
變量的聲明引入和 JavaScript
一樣的關(guān)鍵字 var
,不一樣的機(jī)制,JavaScript
中聲明變量時(shí)沒有類型,Go
語言聲明的變量在后面要帶上類型。
變量
var v int // 聲明變量 v,自動(dòng)初始化值為零值(int 類型為 0)
v = 1 // 給變量 v 賦值
v := 1 // 聲明變量 v,并初始化值為 1,編譯器自動(dòng)推導(dǎo)類型為 int
上面兩種方式完全等價(jià),:=
的寫法更簡潔,也是官方推薦的寫法,編譯器可以自動(dòng)從右值推導(dǎo)出聲明的變量是哪種類型,v
必須是沒被聲明過的變量。
多重賦值
基本的賦值方法都是類似的,但 Go 提供了動(dòng)態(tài)語言中才有的多重賦值功能。
i, j = j, i
交換變量如此簡單,不需要引入中間變量。
匿名變量
函數(shù)的返回值中可能只有一個(gè)感興趣的,別的值都不需要,是否還需要定義變量去接收這幾個(gè)返回值?使用匿名變量!
func GetName() (firstName, lastName, nickName string) {
return "May", "Chan", "Chibi"
}
_, _, nickName := GetName()
常量
常量值必須是編譯期可確定的數(shù)字、字符串、布爾值。即常說的字符字面量(literal),根據(jù)字面量可以推測出常量的類型。
const x,y int = 1, 2 // 多常量初始化
const Pi float64 = 3.1415926
const zero = 0.0 // 類型推斷
const ( // 常量組
size int64 = 1024
eof = -1 // 類型推斷
Eof // 在常量組中,如果不提供類型和初始化值,那么視作與上?常量相同。
)
iota
iota
比較特殊,可以認(rèn)為是編譯器內(nèi)置的一個(gè)寄存器,在每一個(gè) const
關(guān)鍵字出現(xiàn)時(shí)被重置為 0
,然后在下一個(gè) const
出現(xiàn)時(shí),每出現(xiàn)一次 iota
,其所代表的數(shù)字就會(huì)增 1
。
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)
枚舉
Go
語言中不存在枚舉類型,可以通過自定義類型的方式構(gòu)造。
type Color int // 定義類型 Color,傳遞 Color 的地發(fā)不能傳遞 int
const (
Black Color = iota // 定義常量 Black = 0
Red
Blue
)
func test(c Color) {}
func main() {
c := Black
test(c)
x := 1
test(x) // Error: cannot use x (type int) as type Color in function argument
test(1) // 常量會(huì)被編譯器自動(dòng)轉(zhuǎn)換
基本類型
類型 | ?度 | 默認(rèn)值(零值) | 說明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8 |
rune | 4 | 0 | Unicode Code Point, int32 |
int, uint | 4 或 8 | 0 | 32 或 64 位 |
int8, uint8 | 1 | 0 | -128 ~ 127, 0 ~ 255 |
int16, uint16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535 |
int32, uint32 | 4 | 0 | -21億 ~ 21 億, 0 ~ 42 億 |
int64, uint64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | |
complex64 | 8 | ||
complex128 | 16 | ||
uintptr | 4 或 8 | ?以存儲(chǔ)指針的 uint32 或 uint64 整數(shù) | |
array | 值類型 | ||
struct | 值類型 | ||
string | "" | UTF-8 字符串 | |
slice | nil | 引?類型 | |
map | nil | 引?類型 | |
channel | nil | 引?類型 | |
interface | nil | 接? | |
function | nil | 函數(shù) |
int、uint、uintpter
類型在 32 位系統(tǒng)上一般是 32 位,在 64 位系統(tǒng)上是 64 位。
格式化打印類型信息和類型的技巧。
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
)
func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
}
引用類型
引用類型包括 slice、map 和 channel。它們有復(fù)雜的內(nèi)部結(jié)構(gòu),除了申請內(nèi)存外,還需要初始化相關(guān)的屬性。
內(nèi)置函數(shù) new
,計(jì)算類型大小,為其分配零值內(nèi)存,并返回指向內(nèi)存的指針。
內(nèi)置函數(shù) make
,會(huì)被編譯器翻譯成具體的創(chuàng)建函數(shù),由其分配內(nèi)存和初始化成員結(jié)構(gòu),返回對象而非指針。
a := []int{0, 0, 0} // 提供初始化表達(dá)式。
a[1] = 10
b := make([]int, 3) // 初始化長度為 3 的 slice
b[1] = 10
c := new([]int)
c[1] = 10 // Error: invalid operation: c[1] (index of type *[]int)
類型轉(zhuǎn)換
不支持隱式類型轉(zhuǎn)化,即便是從窄向?qū)掁D(zhuǎn)換也不行。
表達(dá)式 T(v)
將值 v
轉(zhuǎn)換為類型 T
。
Bool
Bool
類型不接受其他類型的值,不支持強(qiáng)制類型轉(zhuǎn)換,其他類型不能當(dāng) Bool
值使用。
Float
浮點(diǎn)數(shù)比較不建議使用 ==
,可以使用函數(shù)判斷精度。
import "math"
// p 為自定義精度
func IsEqual(f1, f2, p float64) bool {
return math.Fdim(f1, f2) < p
}
字符串
字符串內(nèi)容在初始化后不可變!
- 默認(rèn)值是空字符串
""
- 使用索引號(hào)訪問的是某個(gè)字節(jié)
s[i]
- 不能用序號(hào)獲取字節(jié)元素指針,
&s[i]
非法 - 不可變類型,無法修改字節(jié)數(shù)組
- 字節(jié)數(shù)組尾部不包含 NULL
struct String {
byte* str; // 指向字節(jié)數(shù)組的指針
intgo len; // 長度
}
?持?兩個(gè)索引號(hào)返回?串。?串依然指向原字節(jié)數(shù)組,僅修改了指針和?度屬性。
s := "Hello, World!"
s1 := s[:5] // Hello
s2 := s[7:] // World!
s3 := s[1:5] // ello
字符串遍歷
// 以字節(jié)數(shù)組遍歷
str := "Hello,世界"
n := len(str)
for i := 0; i < n; i++ {
ch := str[i] // 取出下標(biāo)上的字符,類型為 byte
}
// 每個(gè)中文字符在 UTF-8 占 3 個(gè)字節(jié)
// 以 Unicode 字符遍歷
for i, ch := range str {
fmt.Println(i, ch) // ch 的類型為 rune
}
修改字符串
單引號(hào)字符常量表? Unicode Code Point
,?持 \uFFFF、\U7FFFFFFF、\xFF
格式。
對應(yīng) rune
類型(int32 的別名,4 字節(jié)表示),UCS-4
。
func main() {
fmt.Printf("%T\n", 'a')
var c1, c2 rune = '\u6211', '們'
println(c1 == '我', string(c2) == "\xe4\xbb\xac")
}
輸出
int32 // rune 是 int32 的別名
true true
要修改字符串,可先將其轉(zhuǎn)換成 []rune
或 []byte
,完成后再轉(zhuǎn)換為 string
。?論哪種轉(zhuǎn)
換,都會(huì)重新分配內(nèi)存,并復(fù)制字節(jié)數(shù)組。
func main() {
s := "abcd"
bs := []byte(s)
bs[1] = 'B'
println(string(bs)) // aBcd
u := "電腦"
us := []rune(u)
us[1] = '話'
println(string(us)) // 電話
}
指針
指針類型 *T
是指向類型 T
的指針,零值為 nil
。
&
取址運(yùn)算符。
*
間接引用,訪問對象。
指針和 C
都是一樣的,不同的是 Go
沒有指針運(yùn)算。
可以通過 unsafe.Pointer
和任意類型指針間進(jìn)?轉(zhuǎn)換。
func main() {
x := 0x12345678
p := unsafe.Pointer(&x) // *int -> Pointer
n := (*[4]byte)(p) // Pointer -> *[4]byte 轉(zhuǎn)換成數(shù)組指針
// 78 45 34 12
for i := 0; i < len(n); i++ {
fmt.Printf("%X ", n[i])
}
}
局部變量的指針
返回局部變量的指針是安全的,編譯器會(huì)根據(jù)需要將其分配在 GC Heap
上。
func test() *int {
x := 100
return &x // 在堆上分配 x 內(nèi)存。但在內(nèi)聯(lián)時(shí),也可能直接分配在?標(biāo)棧。
}
變相實(shí)現(xiàn)指針運(yùn)算
將 Pointer
轉(zhuǎn)換成 uintptr
,可變相實(shí)現(xiàn)指針運(yùn)算。
func main() {
d := struct {
s string
x int
}{"abc", 100}
p := uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr
p += unsafe.Offsetof(d.x) // uintptr + offset
p2 := unsafe.Pointer(p) // uintptr -> Pointer
px := (*int)(p2) // Pointer -> *int
*px = 200 // d.x = 200
fmt.Printf("%#v\n", d)
}
輸出
struct { s string; x int }{s:"abc", x:200}
注意:GC 把 uintptr 當(dāng)成普通整數(shù)對象,它?法阻? "關(guān)聯(lián)" 對象被回收。
自定義類型
可將類型分為命名和非命名兩大類。
命名類型包括 bool、int、string
等。
非命令類型則是 array、slice、map
等和具體元素類型、長度等有關(guān)。
具有相同聲明的未命名類型被視為同一類型
- 具有相同基類型指針
- 具有相同元素類型和長度的
array
- 具有相同元素類型的
slice
- 具有相同鍵值類型的
map
- 具有相同元素類型和傳送方向的
channel
- 具體相同字段序列(字段名、類型、標(biāo)簽、順序)的匿名
struct
- 簽名相同的(參數(shù)和返回值,不包括參數(shù)名稱)
function
- 方法集相同的(方法名、方法簽名相同,和次序無關(guān))
interface
var a struct { x int `a` }
var b struct { x int `ab` }
// cannot use a (type struct { x int "a" }) as type struct { x int "ab" } in assignment
// 結(jié)構(gòu)體標(biāo)簽不同
b = a
Type
可以使用 type
在全局或函數(shù)內(nèi)定義新類型。
func main() {
type bigint int64
var x bigint = 100
println(x)
}
新類型不是原類型的別名,除擁有相同數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)外,它們之間沒有任何關(guān)系,不會(huì)持有原類型任何信息。
除?目標(biāo)類型是未命名類型,否則必須顯式轉(zhuǎn)換。
x := 1234
var b bigint = bigint(x) // 必須顯式轉(zhuǎn)換,除?是常量。
var b2 int64 = int64(b)
// slice 只要存儲(chǔ)相同類型的值
var s myslice = []int{1, 2, 3} // 未命名類型,隱式轉(zhuǎn)換。
var s2 []int = s