##7.1 分類 編程
##7.2 指針 指針是一個表明着某個內存地址的值。這個內存地址每每是在內存中存儲的另外一個變量的值的起始位置。Go語言對指針的支持介於Java語言和C/C++語言之間,它既沒有想Java語言那樣取消了代碼對指針的直接操做的能力,也避免了C/C++語言中因爲對指針的濫用而形成的安全和可靠性問題。###7.2.1 基本操做 Go語言雖然保留了指針,但與其它編程語言不一樣的是: 默認值 nil,沒有 NULL 常量 操做符 "&" 取變量地址, "*" 經過指針訪問目標對象 不支持指針運算,不支持 "->" 運算符,直接⽤ "." 訪問目標成員數組
func main() {
var a int = 10 //聲明一個變量,同時初始化
fmt.Printf("&a = %p\n", &a) //操做符 "&" 取變量地址
var p *int = nil //聲明一個變量p, 類型爲 *int, 指針類型
p = &a
fmt.Printf("p = %p\n", p)
fmt.Printf("a = %d, *p = %d\n", a, *p)
*p = 111 //*p操做指針所指向的內存,即爲a
fmt.Printf("a = %d, *p = %d\n", a, *p)
}
複製代碼
###7.2.2 new函數 表達式new(T)將建立一個T類型的匿名變量,所作的是爲T類型的新值分配並清零一塊內存空間,而後將這塊內存空間的地址做爲結果返回,而這個結果就是指向這個新的T類型值的指針值,返回的指針類型爲*T。安全
func main() {
var p1 *int
p1 = new(int) //p1爲*int 類型, 指向匿名的int變量
fmt.Println("*p1 = ", *p1) //*p1 = 0
p2 := new(int) //p2爲*int 類型, 指向匿名的int變量
*p2 = 111
fmt.Println("*p2 = ", *p2) //*p1 = 111
}
複製代碼
咱們只需使用new()函數,無需擔憂其內存的生命週期或怎樣將其刪除,由於Go語言的內存管理系統會幫咱們打理一切。bash
###7.2.3 指針作函數參數數據結構
func swap01(a, b int) {
a, b = b, a
fmt.Printf("swap01 a = %d, b = %d\n", a, b)
}
func swap02(x, y *int) {
*x, *y = *y, *x
}
func main() {
a := 10
b := 20
//swap01(a, b) //值傳遞
swap02(&a, &b) //變量地址傳遞
fmt.Printf("a = %d, b = %d\n", a, b)
}
複製代碼
##7.3 數組 ###7.3.1 概述 數組是指一系列同一類型數據的集合。數組中包含的每一個數據被稱爲數組元素(element),一個數組包含的元素個數被稱爲數組的長度。app
數組⻓度必須是常量,且是類型的組成部分。 [2]int 和 [3]int 是不一樣類型。 var n int = 10 var a [n]int //err, non-constant array bound n var b [10]int //ok編程語言
###7.3.2 操做數組 數組的每一個元素能夠經過索引下標來訪問,索引下標的範圍是從0開始到數組長度減1的位置。函數
var a [10]int
for i := 0; i < 10; i++ {
a[i] = i + 1
fmt.Printf("a[%d] = %d\n", i, a[i])
}
複製代碼
//range具備兩個返回值,第一個返回值是元素的數組下標,第二個返回值是元素的值 for i, v := range a { fmt.Println("a[", i, "]=", v) }性能
內置函數 len(長度) 和 cap(容量) 都返回數組⻓度 (元素數量):ui
a := [10]int{}
fmt.Println(len(a), cap(a))//10 10
複製代碼
初始化:
a := [3]int{1, 2} // 未初始化元素值爲 0
b := [...]int{1, 2, 3} // 經過初始化值肯定數組長度
c := [5]int{2: 100, 4: 200} // 經過索引號初始化元素,未初始化元素值爲 0
fmt.Println(a, b, c) //[1 2 0] [1 2 3] [0 0 100 0 200]
//支持多維數組
d := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
e := [...][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}} //第二維不能寫"..."
f := [4][2]int{1: {20, 21}, 3: {40, 41}}
g := [4][2]int{1: {0: 20}, 3: {1: 41}}
fmt.Println(d, e, f, g)
複製代碼
相同類型的數組之間可使用 == 或 != 進行比較,但不可使用 < 或 >,也能夠相互賦值:
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2}
fmt.Println(a == b, b == c) //true false
var d [3]int
d = a
fmt.Println(d) //[1 2 3]
複製代碼
###7.3.3 在函數間傳遞數組 根據內存和性能來看,在函數間傳遞數組是一個開銷很大的操做。在函數之間傳遞變量時,老是以值的方式傳遞的。若是這個變量是一個數組,意味着整個數組,無論有多長,都會完整複製,並傳遞給函數。
func modify(array [5]int) {
array[0] = 10 // 試圖修改數組的第一個元素
//In modify(), array values: [10 2 3 4 5]
fmt.Println("In modify(), array values:", array)
}
func main() {
array := [5]int{1, 2, 3, 4, 5} // 定義並初始化一個數組
modify(array) // 傳遞給一個函數,並試圖在函數體內修改這個數組內容
//In main(), array values: [1 2 3 4 5]
fmt.Println("In main(), array values:", array)
}
複製代碼
數組指針作函數參數:
func modify(array *[5]int) {
(*array)[0] = 10
//In modify(), array values: [10 2 3 4 5]
fmt.Println("In modify(), array values:", *array)
}
func main() {
array := [5]int{1, 2, 3, 4, 5} // 定義並初始化一個數組
modify(&array) // 數組指針
//In main(), array values: [10 2 3 4 5]
fmt.Println("In main(), array values:", array)
}
複製代碼
##7.4 slice ###7.4.1 概述 數組的長度在定義以後沒法再次修改;數組是值類型,每次傳遞都將產生一份副本。顯然這種數據結構沒法徹底知足開發者的真實需求。Go語言提供了數組切片(slice)來彌補數組的不足。
切片並非數組或數組指針,它經過內部指針和相關屬性引⽤數組⽚段,以實現變⻓⽅案。
slice並非真正意義上的動態數組,而是一個引用類型。slice老是指向一個底層array,slice的聲明也能夠像array同樣,只是不須要長度。
###7.4.2 切片的建立和初始化 slice和數組的區別:聲明數組時,方括號內寫明瞭數組的長度或使用...自動計算長度,而聲明slice時,方括號內沒有任何字符。
var s1 []int //聲明切片和聲明array同樣,只是少了長度,此爲空(nil)切片
s2 := []int{}
//make([]T, length, capacity) //capacity省略,則和length的值相同
var s3 []int = make([]int, 0)
s4 := make([]int, 0, 0)
s5 := []int{1, 2, 3} //建立切片並初始化
複製代碼
注意:make只能建立slice、map和channel,而且返回一個有初始值(非零)。
###7.4.3 切片的操做 ####7.4.3.1 切片截取
####7.4.3.2 切片和底層數組關係 s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[2:5] //[2 3 4] s1[2] = 100 //修改切片某個元素改變底層數組 fmt.Println(s1, s) //[2 3 100] [0 1 2 3 100 5 6 7 8 9]
s2 := s1[2:6] // 新切片依舊指向原底層數組 [100 5 6 7] s2[3] = 200 fmt.Println(s2) //[100 5 6 200]
fmt.Println(s) //[0 1 2 3 100 5 6 200 8 9]
####7.4.3.3 內建函數
var s1 []int //建立nil切換
//s1 := make([]int, 0)
s1 = append(s1, 1) //追加1個元素
s1 = append(s1, 2, 3) //追加2個元素
s1 = append(s1, 4, 5, 6) //追加3個元素
fmt.Println(s1) //[1 2 3 4 5 6]
s2 := make([]int, 5)
s2 = append(s2, 6)
fmt.Println(s2) //[0 0 0 0 0 6]
s3 := []int{1, 2, 3}
s3 = append(s3, 4, 5)
fmt.Println(s3)//[1 2 3 4 5]
複製代碼
append函數會智能地底層數組的容量增加,一旦超過原底層數組容量,一般以2倍容量從新分配底層數組,並複製原來的數據:
func main() {
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
/*
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
*/
}
複製代碼
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[8:] //{8, 9}
s2 := data[:5] //{0, 1, 2, 3, 4}
copy(s2, s1) // dst:s2, src:s1
fmt.Println(s2) //[8 9 2 3 4]
fmt.Println(data) //[8 9 2 3 4 5 6 7 8 9]
複製代碼
###7.4.4 切片作函數參數
func test(s []int) { //切片作函數參數
s[0] = -1
fmt.Println("test : ")
for i, v := range s {
fmt.Printf("s[%d]=%d, ", i, v)
//s[0]=-1, s[1]=1, s[2]=2, s[3]=3, s[4]=4, s[5]=5, s[6]=6, s[7]=7, s[8]=8, s[9]=9,
}
fmt.Println("\n")
}
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
test(slice)
fmt.Println("main : ")
for i, v := range slice {
fmt.Printf("slice[%d]=%d, ", i, v)
//slice[0]=-1, slice[1]=1, slice[2]=2, slice[3]=3, slice[4]=4, slice[5]=5, slice[6]=6, slice[7]=7, slice[8]=8, slice[9]=9,
}
fmt.Println("\n")
}
複製代碼
##7.5 map ###7.5.1 概述 Go語言中的map(映射、字典)是一種內置的數據結構,它是一個無序的key—value對的集合,好比以身份證號做爲惟一鍵來標識一我的的信息。
map格式爲: map[keyType]valueType
在一個map裏全部的鍵都是惟一的,並且必須是支持==和!=操做符的類型,切片、函數以及包含切片的結構類型這些類型因爲具備引用語義,不能做爲映射的鍵,使用這些類型會形成編譯錯誤: dict := map[ []string ]int{} //err, invalid map key type []string
map值能夠是任意類型,沒有限制。map裏全部鍵的數據類型必須是相同的,值也必須如何,但鍵和值的數據類型能夠不相同。
注意:map是無序的,咱們沒法決定它的返回順序,因此,每次打印結果的順利有可能不一樣。
###7.5.2 建立和初始化 ####7.5.2.1 map的建立
var m1 map[int]string //只是聲明一個map,沒有初始化, 此爲空(nil)map
fmt.Println(m1 == nil) //true
//m1[1] = "mike" //err, panic: assignment to entry in nil map
//m2, m3的建立方法是等價的
m2 := map[int]string{}
m3 := make(map[int]string)
fmt.Println(m2, m3) //map[] map[]
m4 := make(map[int]string, 10) //第2個參數指定容量
fmt.Println(m4) //map[]
複製代碼
####7.5.2.2 初始化
//一、定義同時初始化
var m1 map[int]string = map[int]string{1: "mike", 2: "yoyo"}
fmt.Println(m1) //map[1:mike 2:yoyo]
//二、自動推導類型 :=
m2 := map[int]string{1: "mike", 2: "yoyo"}
fmt.Println(m2)
複製代碼
###7.5.3 經常使用操做 ####7.5.3.1 賦值
m1 := map[int]string{1: "mike", 2: "yoyo"}
m1[1] = "xxx" //修改
m1[3] = "lily" //追加, go底層會自動爲map分配空間
fmt.Println(m1) //map[1:xxx 2:yoyo 3:lily]
m2 := make(map[int]string, 10) //建立map
m2[0] = "aaa"
m2[1] = "bbb"
fmt.Println(m2) //map[0:aaa 1:bbb]
fmt.Println(m2[0], m2[1]) //aaa bbb
複製代碼
####7.5.3.2 遍歷
m1 := map[int]string{1: "mike", 2: "yoyo"}
//迭代遍歷1,第一個返回值是key,第二個返回值是value
for k, v := range m1 {
fmt.Printf("%d ----> %s\n", k, v)
//1 ----> mike
//2 ----> yoyo
}
//迭代遍歷2,第一個返回值是key,第二個返回值是value(可省略)
for k := range m1 {
fmt.Printf("%d ----> %s\n", k, m1[k])
//1 ----> mike
//2 ----> yoyo
}
//判斷某個key所對應的value是否存在, 第一個返回值是value(若是存在的話)
value, ok := m1[1]
fmt.Println("value = ", value, ", ok = ", ok) //value = mike , ok = true
value2, ok2 := m1[3]
fmt.Println("value2 = ", value2, ", ok2 = ", ok2) //value2 = , ok2 = false
複製代碼
####7.5.3.3 刪除
m1 := map[int]string{1: "mike", 2: "yoyo", 3: "lily"}
//迭代遍歷1,第一個返回值是key,第二個返回值是value
for k, v := range m1 {
fmt.Printf("%d ----> %s\n", k, v)
//1 ----> mike
//2 ----> yoyo
//3 ----> lily
}
delete(m1, 2) //刪除key值爲3的map
for k, v := range m1 {
fmt.Printf("%d ----> %s\n", k, v)
//1 ----> mike
//3 ----> lily
}
複製代碼
###7.5.4 map作函數參數 在函數間傳遞映射並不會製造出該映射的一個副本,不是值傳遞,而是引用傳遞:
func DeleteMap(m map[int]string, key int) {
delete(m, key) //刪除key值爲3的map
for k, v := range m {
fmt.Printf("len(m)=%d, %d ----> %s\n", len(m), k, v)
//len(m)=2, 1 ----> mike
//len(m)=2, 3 ----> lily
}
}
func main() {
m := map[int]string{1: "mike", 2: "yoyo", 3: "lily"}
DeleteMap(m, 2) //刪除key值爲3的map
for k, v := range m {
fmt.Printf("len(m)=%d, %d ----> %s\n", len(m), k, v)
//len(m)=2, 1 ----> mike
//len(m)=2, 3 ----> lily
}
}
複製代碼
##7.6 結構體 ###7.6.1 結構體類型 有時咱們須要將不一樣類型的數據組合成一個有機的總體,如:一個學生有學號/姓名/性別/年齡/地址等屬性。顯然單獨定義以上變量比較繁瑣,數據不便於管理。
結構體是一種聚合的數據類型,它是由一系列具備相同類型或不一樣類型的數據構成的數據集合。每一個數據稱爲結構體的成員。
###7.6.2 結構體初始化 ####7.6.2.1 普通變量
type Student struct {
id int
name string
sex byte
age int
addr string
}
func main() {
//一、順序初始化,必須每一個成員都初始化
var s1 Student = Student{1, "mike", 'm', 18, "sz"}
s2 := Student{2, "yoyo", 'f', 20, "sz"}
//s3 := Student{2, "tom", 'm', 20} //err, too few values in struct initializer
//二、指定初始化某個成員,沒有初始化的成員爲零值
s4 := Student{id: 2, name: "lily"}
}
複製代碼
####7.6.2.2 指針變量
type Student struct {
id int
name string
sex byte
age int
addr string
}
func main() {
var s5 *Student = &Student{3, "xiaoming", 'm', 16, "bj"}
s6 := &Student{4, "rocco", 'm', 3, "sh"}
}
複製代碼
###7.6.3 結構體成員的使用 ####7.6.3.1 普通變量
//===============結構體變量爲普通變量
//一、打印成員
var s1 Student = Student{1, "mike", 'm', 18, "sz"}
//結果:id = 1, name = mike, sex = m, age = 18, addr = sz
fmt.Printf("id = %d, name = %s, sex = %c, age = %d, addr = %s\n", s1.id, s1.name, s1.sex, s1.age, s1.addr)
//二、成員變量賦值
var s2 Student
s2.id = 2
s2.name = "yoyo"
s2.sex = 'f'
s2.age = 16
s2.addr = "guangzhou"
fmt.Println(s2) //{2 yoyo 102 16 guangzhou}
複製代碼
####7.6.3.2 指針變量
//===============結構體變量爲指針變量
//三、先分配空間,再賦值
s3 := new(Student)
s3.id = 3
s3.name = "xxx"
fmt.Println(s3) //&{3 xxx 0 0 }
//四、普通變量和指針變量類型打印
var s4 Student = Student{4, "yyy", 'm', 18, "sz"}
fmt.Printf("s4 = %v, &s4 = %v\n", s4, &s4) //s4 = {4 yyy 109 18 sz}, &s4 = &{4 yyy 109 18 sz}
var p *Student = &s4
//p.成員 和(*p).成員 操做是等價的
p.id = 5
(*p).name = "zzz"
fmt.Println(p, *p, s4) //&{5 zzz 109 18 sz} {5 zzz 109 18 sz} {5 zzz 109 18 sz}
複製代碼
###7.6.4 結構體比較 若是結構體的所有成員都是能夠比較的,那麼結構體也是能夠比較的,那樣的話兩個結構體將可使用 == 或 != 運算符進行比較,但不支持 > 或 < 。
func main() {
s1 := Student{1, "mike", 'm', 18, "sz"}
s2 := Student{1, "mike", 'm', 18, "sz"}
fmt.Println("s1 == s2", s1 == s2) //s1 == s2 true
fmt.Println("s1 != s2", s1 != s2) //s1 != s2 false
}
複製代碼
###7.6.5 結構體做爲函數參數 ####7.6.5.1 值傳遞
func printStudentValue(tmp Student) {
tmp.id = 250
//printStudentValue tmp = {250 mike 109 18 sz}
fmt.Println("printStudentValue tmp = ", tmp)
}
func main() {
var s Student = Student{1, "mike", 'm', 18, "sz"}
printStudentValue(s) //值傳遞,形參的修改不會影響到實參
fmt.Println("main s = ", s) //main s = {1 mike 109 18 sz}
}
複製代碼
####7.6.5.2 引用傳遞
func printStudentPointer(p *Student) {
p.id = 250
//printStudentPointer p = &{250 mike 109 18 sz}
fmt.Println("printStudentPointer p = ", p)
}
func main() {
var s Student = Student{1, "mike", 'm', 18, "sz"}
printStudentPointer(&s) //引用(地址)傳遞,形參的修改會影響到實參
fmt.Println("main s = ", s) //main s = {250 mike 109 18 sz}
}
複製代碼
###7.6.6 可見性 Go語言對關鍵字的增長很是吝嗇,其中沒有private、 protected、 public這樣的關鍵字。
要使某個符號對其餘包(package)可見(便可以訪問),須要將該符號定義爲以大寫字母開頭。
目錄結構:
test.go示例代碼以下:
//test.go
package test
//student01只能在本文件件引用,由於首字母小寫
type student01 struct {
Id int
Name string
}
//Student02能夠在任意文件引用,由於首字母大寫
type Student02 struct {
Id int
name string
}
複製代碼
main.go示例代碼以下:
// main.go
package main
import (
"fmt"
"test" //導入test包
)
func main() {
//s1 := test.student01{1, "mike"} //err, cannot refer to unexported name test.student01
//err, implicit assignment of unexported field 'name' in test.Student02 literal
//s2 := test.Student02{2, "yoyo"}
//fmt.Println(s2)
var s3 test.Student02 //聲明變量
s3.Id = 1 //ok
//s3.name = "mike" //err, s3.name undefined (cannot refer to unexported field or method name)
fmt.Println(s3)
}
複製代碼