Go 語言數(shù)據(jù)類型包含基礎類型和復合類型兩大類。
基礎數(shù)據(jù)類型包括:布爾型、整型、浮點型、復數(shù)型、字符型、字符串型、錯誤類型。
復合數(shù)據(jù)類型包括:指針、數(shù)組、切片、字典、通道、結構體、接口。
Go 語言在聲明變量時會默認給變量賦個當前類型的空值,聲明變量的方式:
| 聲明方式 | 說明 |
|---|---|
| var 變量名 <變量類型> | 聲明單個變量 |
| var 變量名1, 變量名2,... <變量類型> | 聲明多個同類型變量 |
| 變量名 := 值 | 聲明變量,并賦值;Go 語言會根據(jù)所賦值推斷變量的類型 |
| 變量名1, 變量名2,... := 值1, 值2,... | 聲明多個同類型變量并賦值,幾個變量必須賦幾個值 |
一、布爾類型 (bool)
值:true 和 false,默認值為 false
package main
import "fmt"
func main() {
var v1, v2 bool // 聲明變量,默認值為 false
v1 = true // 賦值
v3, v4 := false, true // 聲明并賦值
fmt.Print("v1:", v1) // v1 輸出 true
fmt.Print("\nv2:", v2) // v2 沒有重新賦值,顯示默認值:false
fmt.Print("\nv3:", v3) // v3 false
fmt.Print("\nv4:", v4) // v4 true
}
二、數(shù)字類型
數(shù)字類型比較多,默認值都是 0。定義int類型時,默認根據(jù)系統(tǒng)類型設置取值范圍,32位系統(tǒng)與int32的值范圍相同,64位系統(tǒng)與int64的值范圍相同。見下表:
| 類型 | 名稱 | 存儲空間 | 值范圍 | 數(shù)據(jù)級別 |
|---|---|---|---|---|
| uint8 | 無符號8位整形 | 8-bit | 0 ~ 255 | 百 |
| uint16 | 無符號16位整形 | 16-bit | 0 ~65535 | 6萬多 |
| uint32 | 無符號32位整形 | 32-bit | 0 ~ 4294967295 | 40多億 |
| uint64 | 無符號64位整形 | 64-bit | 0 ~ 18446744073709551615 | 大到?jīng)]概念 |
| int8 | 8位整形 | 8-bit | -128 ~ 127 | 正負百 |
| int16 | 16位整形 | 16-bit | -32768 ~ 32767 | 正負3萬多 |
| int32 | 32位整形 | 32-bit | -2147483648 ~ 2147483647 | 正負20多億 |
| int64 | 64位整形 | 64-bit | -9223372036854775808 ~ 9223372036854775807 | 正負大到?jīng)]概念 |
| int | 系統(tǒng)決定 | 系統(tǒng)決定 | 32位系統(tǒng)為int32的值范圍,64位系統(tǒng)為int64的值范圍 |
|
| - | ||||
| float32 | 32位浮點數(shù) | 32-bit | IEEE-754 1.401298464324817070923729583289916131280e-45 ~ 3.402823466385288598117041834516925440e+38 | 精度6位小數(shù) |
| float64 | 64位浮點數(shù) | 64-bit | IEEE-754 4.940656458412465441765687928682213723651e-324 ~ 1.797693134862315708145274237317043567981e+308 | 精度15位小數(shù) |
| - | ||||
| complex64 | 復數(shù),含 float32 位實數(shù)和 float32 位虛數(shù) | 64-bit | 實數(shù)、虛數(shù)的取值范圍對應 float32 | |
| complex128 | 復數(shù),含 float64 位實數(shù)和 float64 位虛數(shù) | 128-bit | 實數(shù)、虛數(shù)的取值范圍對應 float64 | |
| - | ||||
| byte | 字符型,unit8 別名 | 8-bit | 表示 UTF-8 字符串的單個字節(jié)的值,對應 ASCII 碼的字符值 | |
| rune | 字符型,int32 別名 | 32-bit | 表示 單個 Unicode 字符 | |
| uintptr | 無符號整型 | 由系統(tǒng)決定 | 能存放指針地址即可 |
package main
import "fmt"
func main() {
// 無符號整形,默認值都是0
var u8 uint8
var u16 uint16
var u32 uint32
var u64 uint64
fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64) // 默認值都為0
u8 = 255
u16 = 65535
u32 = 4294967295
u64 = 18446744073709551615
fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64)
// 整型
var i8 int8
var i16 int16
var i32 int32
var i64 int64
fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64) // 默認值都為0
i8 = 127
i16 = 32767
i32 = 2147483647
i64 = 9223372036854775807
fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64)
// int 型,取值范圍32位系統(tǒng)為 int32,64位系統(tǒng)為 int64,取值相同但為不同類型
var i int
//i = i32 // 報錯,編譯不通過,類型不同
//i = i64 // 報錯,編譯不通過,類型不同
i = -9223372036854775808
fmt.Println("i: ", i)
// 浮點型,f32精度6位小數(shù),f64位精度15位小數(shù)
var f32 float32
var f64 float64
fmt.Printf("f32: %f, f64: %f\n", f32, f64) // 默認值都為 0.000000
f32 = 1.12345678
f64 = 1.12345678901234567
fmt.Printf("f32: %v, f64: %v\n", f32, f64) // 末位四舍五入,輸出:f32: 1.1234568, f64: 1.1234567890123457
// 復數(shù)型
var c64 complex64
var c128 complex128
fmt.Printf("c64: %v, c128: %v\n", c64, c128) // 實數(shù)、虛數(shù)的默認值都為0
c64 = 1.12345678 + 1.12345678i
c128 = 2.1234567890123456 + 2.1234567890123456i
fmt.Printf("c64: %v, c128: %v\n", c64, c128) // 輸出:c64: (1.1234568+1.1234568i), c128: (2.1234567890123457+2.1234567890123457i)
// 字符型
var b byte // uint8 別名
var r1, r2 rune // uint16 別名
fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 默認值為0
b = 'a'
r1 = 'b'
r2 = '字'
fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 輸出:b: 97(ASCII表示的數(shù)), r1: 98(utf-8表示的數(shù)), r2: 23383 (utf-8表示的數(shù))
b = u8
r1 = i32
fmt.Printf("b: %v, r1: %v\n", b, r1) // 輸出:b: 255, r1: 2147483647
// 指針地址
var p uintptr
fmt.Printf("p: %v\n", p) // 默認值為0
p = 18446744073709551615 // 64位系統(tǒng)最大值
//p = 18446744073709551616 // 報錯:超出最大值
fmt.Printf("p: %v\n", p)
}
三、字符串 (string)
Go 語言默認編碼都是 UTF-8。
package main
import "fmt"
func main() {
var str1 string // 默認值為空字符串 ""
str1 = `hello world`
str2 := "你好世界"
str := str1 + " " + str2 // 字符串連接
fmt.Println(str1)
fmt.Println(str2)
fmt.Println(str) // 輸出:hello world 你好世界
// 遍歷字符串
l := len(str)
for i := 0; i < l; i++ {
chr := str[i]
fmt.Println(i, chr) // 輸出字符對應的編碼數(shù)字
}
}
四、指針(pointer)
指針其實就是指向一個對象(任何一種類型數(shù)據(jù)、包括指針本身)的地址值,對指針的操作都會映射到指針所指的對象上。
package main
import (
"fmt"
)
func main() {
var p *int // 定義指向int型的指針,默認值為空:nil
// nil指針不指向任何有效存儲地址,操作系統(tǒng)默認不能訪問
//fmt.Printf("%x\n", *p) // 編譯報錯
var a int = 10
p = &a // 取地址
add := a + *p // 取值
fmt.Println(a) // 輸出:10
fmt.Println(p) // 輸出:0xc0420080b8
fmt.Println(add) // 輸出:20
}
五、數(shù)組(array)
數(shù)組為一組相同數(shù)據(jù)類型數(shù)據(jù)的集合,數(shù)組定義后大小固定,不能更改,每個元素稱為element,聲明的數(shù)組元素默認值都是對應類型的0值。而且數(shù)組在Go語言中是一個值類型(value type),所有值類型變量在賦值和作為參數(shù)傳遞時都會產(chǎn)生一次復制動作,即對原值的拷貝。
package main
import "fmt"
func main() {
// 1.聲明后賦值
// var <數(shù)組名稱> [<數(shù)組長度>]<數(shù)組元素>
var arr [2]int // 數(shù)組元素的默認值都是 0
fmt.Println(arr) // 輸出:[0 0]
arr[0] = 1
arr[1] = 2
fmt.Println(arr) // 輸出:[1 2]
// 2.聲明并賦值
// var <數(shù)組名稱> = [<數(shù)組長度>]<數(shù)組元素>{元素1,元素2,...}
var intArr = [2]int{1, 2}
strArr := [3]string{`aa`, `bb`, `cc`}
fmt.Println(intArr) // 輸出:[1 2]
fmt.Println(strArr) // 輸出:[aa bb cc]
// 3.聲明時不設定大小,賦值后語言本身會計算數(shù)組大小
// var <數(shù)組名稱> [<數(shù)組長度>]<數(shù)組元素> = [...]<元素類型>{元素1,元素2,...}
var arr1 = [...]int{1, 2}
arr2 := [...]int{1, 2, 3}
fmt.Println(arr1) // 輸出:[1 2]
fmt.Println(arr2) // 輸出:[1 2 3]
//arr1[2] = 3 // 編譯報錯,數(shù)組大小已設定為2
// 4.聲明時不設定大小,賦值時指定索引
// var <數(shù)組名稱> [<數(shù)組長度>]<數(shù)組元素> = [...]<元素類型>{索引1:元素1,索引2:元素2,...}
var arr3 = [...]int{1: 22, 0: 11, 2: 33}
arr4 := [...]string{2: "cc", 1: "bb", 0: "aa"}
fmt.Println(arr3) // 輸出:[11 22 33]
fmt.Println(arr4) // 輸出:[aa bb cc]
// 遍歷數(shù)組
for i := 0; i < len(arr4); i++ {
v := arr4[i]
fmt.Printf("i:%d, value:%s\n", i, v)
}
}
六、切片(slice)
因為數(shù)組的長度定義后不可修改,所以需要切片來處理可變長數(shù)組數(shù)據(jù)。切片可以看作是一個可變長的數(shù)組,是一個引用類型。它包含三個數(shù)據(jù):1.指向原生數(shù)組的指針,2.切片中的元素個數(shù),3.切片已分配的存儲空間大小
聲明一個切片,或從數(shù)組中取一段作為切片數(shù)據(jù):
package main
import "fmt"
func main() {
var sl []int // 聲明一個切片
sl = append(sl, 1, 2, 3) // 往切片中追加值
fmt.Println(sl) // 輸出:[1 2 3]
var arr = [5]int{1, 2, 3, 4, 5} // 初始化一個數(shù)組
var sl1 = arr[0:2] // 冒號:左邊為起始位(包含起始位數(shù)據(jù)),右邊為結束位(不包含結束位數(shù)據(jù));不填則默認為頭或尾
var sl2 = arr[3:]
var sl3 = arr[:5]
fmt.Println(sl1) // 輸出:[1 2]
fmt.Println(sl2) // 輸出:[4 5]
fmt.Println(sl3) // 輸出:[1 2 3 4 5]
sl1 = append(sl1, 11, 22) // 追加元素
fmt.Println(sl1) // 輸出:[1 2 11 22]
}
使用make直接創(chuàng)建切片,語法:make([]類型, 大小,預留空間大小),make() 函數(shù)用于聲明slice切片、map字典、channel通道。
package main
import "fmt"
func main() {
var sl1 = make([]int, 5) // 定義元素個數(shù)為5的切片
sl2 := make([]int, 5, 10) // 定義元素個數(shù)5的切片,并預留10個元素的存儲空間(預留空間不知道有什么用?)
sl3 := []string{`aa`, `bb`, `cc`} // 直接創(chuàng)建并初始化包含3個元素的數(shù)組切片
fmt.Println(sl1, len(sl1)) // 輸出:[0 0 0 0 0] 5
fmt.Println(sl2, len(sl2)) // 輸出:[0 0 0 0 0] 5
fmt.Println(sl3, len(sl3)) // [aa bb cc] 3
sl1[1] = 1 // 聲明或初始化大小中的數(shù)據(jù),可以指定賦值
sl1[4] = 4
//sl1[5] = 5 // 編譯報錯,超出定義大小
sl1 = append(sl1, 5) // 可以追加元素
fmt.Println(sl1, len(sl1)) // 輸出:[0 1 0 0 4 5] 6
sl2[1] = 1
sl2 = append(sl2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
fmt.Println(sl2, len(sl2)) // 輸出:[0 1 0 0 0 1 2 3 4 5 6 7 8 9 10 11] 16
// 遍歷切片
for i := 0; i < len(sl2); i++ {
v := sl2[i]
fmt.Printf("i: %d, value:%d \n", i, v)
}
}
七、字典/映射(map)
map 是一種鍵值對的無序集合,與 slice 類似也是一個引用類型。map 本身其實是個指針,指向內(nèi)存中的某個空間。
聲明方式與數(shù)組類似,聲明方式:var 變量名 map[key類型]值類型 或直接使用 make 函數(shù)初始化:make(map[key類型]值類型, 初始空間大小)
其中key值可以是任何可以用==判斷的值類型,對應的值類型沒有要求。
package main
import (
"fmt"
"unsafe"
)
func main() {
// 聲明后賦值
var m map[int]string
fmt.Println(m) // 輸出空的map:map[]
//m[1] = `aa` // 向未初始化的map中賦值報錯:panic: assignment to entry in nil map
// 聲明并初始化,初始化使用{} 或 make 函數(shù)(創(chuàng)建類型并分配空間)
var m1 = map[string]int{}
var m2 = make(map[string]int)
m1[`a`] = 11
m2[`b`] = 22
fmt.Println(m1) // 輸出:map[a:11]
fmt.Println(m2) // 輸出:map[b:22]
// 初始化多個值
var m3 = map[string]string{"a": "aaa", "b": "bbb"}
m3["c"] = "ccc"
fmt.Println(m3) // 輸出:map[a:aaa b:bbb c:ccc]
// 刪除 map 中的值
delete(m3, "a") // 刪除鍵 a 對應的值
fmt.Println(m3) // 輸出:map[b:bbb c:ccc]
// 查找 map 中的元素
v, ok := m3["b"]
if ok {
fmt.Println(ok)
fmt.Println("m3中b的值為:", v) // 輸出:m3中b的值為: bbb
}
// 或者
if v, ok := m3["b"]; ok { // 流程處理后面講
fmt.Println("m3中b的值為:", v) // 輸出:m3中b的值為: bbb
}
fmt.Println(m3["c"]) // 直接取值,輸出:ccc
// map 中的值可以是任意類型
m4 := make(map[string][5]int)
m4["a"] = [5]int{1, 2, 3, 4, 5}
m4["b"] = [5]int{11, 22, 33}
fmt.Println(m4) // 輸出:map[a:[1 2 3 4 5] b:[11 22 33 0 0]]
fmt.Println(unsafe.Sizeof(m4)) // 輸出:8,為8個字節(jié),map其實是個指針,指向某個內(nèi)存空間
}
八、通道(channel)
說到通道 channel,則必須先了解下 Go 語言的 goroutine 協(xié)程(輕量級線程)。channel 就是為 goroutine 間通信提供通道。goroutine 是 Go 語言提供的語言級的協(xié)程,是對 CPU 線程和調度器的一套封裝。
channel 也是類型相關的,一個 channel 只能傳遞一種類型的值。
聲明:var 通道名 chan 通道傳遞值類型 或 make 函數(shù)初始化:make(chan 值類型, 初始存儲空間大小)
package main
import (
"fmt"
"time"
)
func main() {
var ch1 chan int // 聲明一個通道
ch1 = make(chan int) // 未初始化的通道不能存儲數(shù)據(jù),初始化一個通道
ch2 := make(chan string, 2) // 聲明并初始化一個帶緩沖空間的通道
// 通過匿名函數(shù)向通道中寫入數(shù)據(jù),通過 <- 方式寫入
go func() { ch1 <- 1 }()
go func() { ch2 <- `a` }()
v1 := <-ch1 // 從通道中讀取數(shù)據(jù)
v2 := <-ch2
fmt.Println(v1) // 輸出:1
fmt.Println(v2) // 輸出:a
// 寫入,讀取通道數(shù)據(jù)
ch3 := make(chan int, 1) // 初始化一個帶緩沖空間的通道
go readFromChannel(ch3)
go writeToChannel(ch3)
// 主線程休眠1秒,讓出執(zhí)行權限給子 Go 程,即通過 go 開啟的 goroutine,不然主程序會直接結束
time.Sleep(1 * time.Second)
}
func writeToChannel(ch chan int) {
for i := 1; i < 10; i++ {
fmt.Println("寫入:", i)
ch <- i
}
}
func readFromChannel(ch chan int) {
for i := 1; i < 10; i++ {
v := <-ch
fmt.Println("讀?。?, v)
}
}
// ------ 輸出:--------
1
a
寫入: 1
寫入: 2
寫入: 3
讀?。?1
讀?。?2
讀取: 3
寫入: 4
寫入: 5
寫入: 6
讀?。?4
讀?。?5
讀取: 6
寫入: 7
寫入: 8
寫入: 9
讀?。?7
讀取: 8
讀?。?9
goroutine 和 channel 的詳細講解待補充。
九、結構體(struct)
結構體是一種聚合的數(shù)據(jù)類型,是由零個或多個任意類型的值聚合成的實體。每個值稱為結構體的成員。
package main
import "fmt"
// 定義一個結構體 person
type person struct {
name string
age int
}
func main() {
var p person // 聲明一個 person 類型變量 p
p.name = "max" // 賦值
p.age = 12
fmt.Println(p) // 輸出:{max 12}
p1 := person{name: "mike", age: 10} // 直接初始化一個 person
fmt.Println(p1.name) // 輸出:mike
p2 := new(person) // new函數(shù)分配一個指針,指向 person 類型數(shù)據(jù)
p2.name = `張三`
p2.age = 15
fmt.Println(*p2) // 輸出:{張三 15}
}
十、接口(interface)
接口用來定義行為。Go 語言不同于面向對象語言,沒有類的概念,也沒有傳統(tǒng)意義上的繼承。Go 語言中的接口,用來定義一個或一組行為,某些對象實現(xiàn)了接口定義的行為,則稱這些對象實現(xiàn)了(implement)該接口,類型即為該接口類型。
定義接口也是使用 type 關鍵字,格式為:
// 定義一個接口
type InterfaceName interface {
FuncName1(paramList) returnType
FuncName2(paramList) returnType
...
}
實例:
package main
import (
"fmt"
"strconv"
)
// 定義一個 Person 接口
type Person interface {
Say(s string) string
Walk(s string) string
}
// 定義一個 Man 結構體
type Man struct {
Name string
Age int
}
// Man 實現(xiàn) Say 方法
func (m Man) Say(s string) string {
return s + ", my name is " + m.Name
}
// Man 實現(xiàn) Walk 方法,strconv.Itoa() 數(shù)字轉字符串
func (m Man) Walk(s string) string {
return "Age: " + strconv.Itoa(m.Age) + " and " + s
}
func main() {
var m Man // 聲明一個類型為 Man 的變量
m.Name = "Mike" // 賦值
m.Age = 30
fmt.Println(m.Say("hello")) // 輸出:hello, my name is Mike
fmt.Println(m.Walk("go work")) // 輸出:Age: 30 and go work
jack := Man{Name: "jack", Age: 25} // 初始化一個 Man 類型數(shù)據(jù)
fmt.Println(jack.Age)
fmt.Println(jack.Say("hi")) // 輸出:hi, my name is jack
}
十一、錯誤(error)
error 類型本身是 Go 語言內(nèi)部定義好的一個接口,接口里定義了一個 Error() 打印錯誤信息的方法,源碼如下:
type error interface {
Error() string
}
自定義錯誤信息:
package main
import (
"errors"
"fmt"
)
func main() {
// 使用 errors 定制錯誤信息
var e error
e = errors.New("This is a test error")
fmt.Println(e.Error()) // 輸出:This is a test error
// 使用 fmt.Errorf() 定制錯誤信息
err := fmt.Errorf("This is another error")
fmt.Println(err) // 輸出:This is another test error
}