内置函数
// 1. close:主要用来关闭channel // 2. len:用来求长度,比如string、array、slice、map、channel // 3. new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 // 4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice // 5. append:用来追加元素到数组、slice中
示例如下:
// new 示例: package main import "fmt" func main(){ var i int fmt.Println(i) j := new(int) fmt.Println(j) *j = 100 fmt.Println(*j) } // 运行结果如下: [root@NEO example01_new]# go run main/main.go 0 0xc0000160b0 100 [root@NEO example01_new]# // append 示例: package main import "fmt" func main(){ var a []int // [] 中不写数字表示是一个切片 slice a = append(a,1,2) // append 中也是可变参数 fmt.Println(a) a = append(a,a...) // 添加一个切片;a...表示 切片a 中的所有元素 fmt.Println(a) } // 运行结果如下: [root@NEO example01_append]# go run main/main.go [1 2] [1 2 1 2] [root@NEO example01_append]# // recover 示例: package main import "fmt" func test(){ defer func(){ // func(){} 是匿名函数; func(){...}() 执行匿名函数 if err := recover(); err != nil{ // recover() 是捕获异常 fmt.Println(err) } }() a := 0 b := 1/a fmt.Println(b) return } func main(){ test() fmt.Println("hello world") } // 运行结果如下: [root@NEO example01_recover]# go run main/main.go runtime error: integer divide by zero hello world [root@NEO example01_recover]#
递归函数
一个函数调用自己,就叫做递归。示例代码如下:
// 示例代码1: package main import ( "fmt" ) func calc(n int) int { if n == 1 { return 1 } return calc(n-1) * n } func main() { n := calc(5) fmt.Println(n) } // 示例2:斐波那契数 package main import "fmt" func fab(n int) int { if n <= 1 { return 1 } return fab(n-1)+fab(n-2) } func main(){ n := fab(10) fmt.Println(n) }
递归的设计原则
1)一个大的问题能够分解成相似的小问题 2)定义好出口条件
闭包
闭包:一个函数和与其相关的引用环境组合而成的实体。示例如下:
// 示例代码: package main import "fmt" func Adder() func(int) int { // 函数 Adder 的返回值是也一个函数 var x int return func(d int) int { // 该处的 return 要和上面定义的返回值类型的 return 的函数一样 x += d return x } } func main() { f := Adder() // f 是一个函数 fmt.Println("f(1)",f(1)) // 此时 x 的值为默认的 0 fmt.Println("f(10)",f(10)) // 此时 x 的值变成了 1 fmt.Println("f(100)",f(100)) // 此时 x 的值变成了 10 } // 运行结果如下: [root@NEO example03_closure]# go run main/main.go f(1) 1 f(10) 11 f(100) 111 [root@NEO example03_closure]#
数组和切片
// 1. 数组:是同一种数据类型的固定长度的序列。 // 2. 数组定义:var a [len]int,比如:var a[5]int,一旦定义,长度不能变;元素的值默认为0 // 3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型 // 4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1 for i := 0; i < len(a); i++ { } for index, v := range a { } // 5. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic // 6. 数组是值类型,因此改变副本的值,不会改变本身的值 arr2 := arr1 arr2[2] = 100
数组的初始化
1. var age0 [5]int = [5]int{1,2,3} // 定义的时候初始化 2. var age1 = [5]int{1,2,3,4,5} 3. var age2 = […]int{1,2,3,4,5,6} // ... 的作用:编译器会自动帮你数里面有几个元素,然后就是长度为多少的数组 4. var str = [5]string{3:”hello world”, 4:”tom”} // 定义时指定下标对应的元素
多维数组
1. var age [5][3]int // 二维数组:可理解成5行3列 2. var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} // ... 表示 编译器自己去数有多少行
多维数组遍历
// 示例代码: package main import "fmt" func test(){ var arr [2][5]int = [...][5]int{{1,2,3,4,5},{6,7,8,9,10}} for row,v_row := range arr{ for col,v_col := range v_row { fmt.Printf("(%d,%d)=%d ",row,col,v_col) } fmt.Println() } } func main(){ test() } // 运行结果如下: [root@NEO example04_multi_dim_arr]# go run main/main.go (0,0)=1 (0,1)=2 (0,2)=3 (0,3)=4 (0,4)=5 (1,0)=6 (1,1)=7 (1,2)=8 (1,3)=9 (1,4)=10 [root@NEO example04_multi_dim_arr]#
切片
// 1. 切片:切片是数组的一个引用,因此切片是引用类型;把一个切片传入函数中修改切片中元素的值,那么该切片在函数外的值也会改变,数组则不会 // 2. 切片的长度可以改变,因此,切片是一个可变的数组 // 3. 切片遍历方式和数组一样,并且可以用len()求长度 // 4. cap可以求出slice最大的容量,0 <= len(slice) <= cap(array),其中array是slice引用的数组 // 5. 切片的定义:var 变量名 []类型,比如 var str []string 和 var arr []int (这种方式只是定义了切片,但并没有对切片初始化,所以此时不能直接 arr[0]=1 这样赋值)
示例如下:
// 切片是引用类型的示例1: package main import "fmt" func modifySlice(a []int){ // 该函数的需要传入的参数是切片类型;slice map 和 chan 类型的参数传入的就是地址 a[1] = 100 // 修改 切片 a 的元素的值后,函数外 a 的值也会改变 } func testSlice(){ var s []int = []int{1,2,3} modifySlice(s) // 切片是引用类型,是数组的一个引用(可理解成切片是一个地址);此函数传了一个引用(引用中包含指针);可理解成给该函数传了一个地址 fmt.Println(s) } func main(){ testSlice() } // 运行结果如下: [root@NEO example05_slice_ref_type]# go run main/main.go [1 100 3] // 函数外切片的值也发生了改变 [root@NEO example05_slice_ref_type]# // 切片是引用类型的示例2: package main import "fmt" func testSliceRefArr(){ var a = [10]int{1,2,3} // 声明并初始化一个数组 b := a[1:4] // 声明并初始化一个切片 fmt.Printf("%p ",b) // %p 表示十六进制表示,可用于打印一个地址 fmt.Printf("%p ",&a[1]) // 数组 a 第一个元素的地址 } func main(){ testSliceRefArr() } // 运行结果如下: [root@NEO example05_slice_ref_arr]# go run main/main.go 0xc000064008 0xc000064008 // 切片 b 的地址和 数组a第一个元素的地址是一样的;所以 切片是一个地址 [root@NEO example05_slice_ref_arr]# // 示例代码: package main import "fmt" func testSlice(){ var slice []int // 声明一个数组;只是定义了数组,但并没有对切片初始化,所以此时不能直接 arr[0]=1 这样赋值 var arr [5]int = [...]int{1,2,3,4,5} // 声明并初始化一个数组 slice = arr[2:5] // 对切片初始化; 顾首不顾尾;可简写成 arr[2:] fmt.Println(slice) fmt.Println("len:",len(slice)) fmt.Println("cap:",cap(slice)) slice = slice[0:1] fmt.Println("len:",len(slice)) fmt.Println("cap:",cap(slice)) } func main(){ testSlice() } // slice、map、channel 都可以用 make 来初始化 // 运行结果如下: [root@NEO example05_slice01]# go run main/main.go [3 4 5] len: 3 cap: 3 len: 1 cap: 3 [root@NEO example05_slice01]#
切片的创建方式1:通过 数组 创建
// 1. 切片初始化:var slice []int = arr[start:end] ;包含start到end之间的元素,但不包含end // 2. var slice []int = arr[0:end]可以简写为 var slice []int=arr[:end] // 3. var slice []int = arr[start:len(arr)] 可以简写为 var slice[]int = arr[start:] // 4. Var slice []int = arr[0, len(arr)] 可以简写为 var slice[]int = arr[:] // 5. 如果要切片最后一个元素去掉,可以这么写: Slice = slice[:len(slice)-1]
切片的创建方式2: 通过make来创建切片
var slice []type = make([]type, len) slice := make([]type, len) slice := make([]type, len, cap)
用append内置函数操作切片
// 示例: slice = append(slice, 10) var a = []int{1,2,3} var b = []int{4,5,6} a = append(a, b…) // b... 表示 切片 b 中的所有元素 // append 的示例: package main import "fmt" func testSlice(){ var a [5]int = [...]int{1,2,3,4,5} b := a[1:] fmt.Printf("b=%p a[1]=%p ",b,&a[1]) // 此时切片 b 的地址和 数组a第一个元素的地址是一样的 b[1] = 100 fmt.Println("before a:",a) // 此时 改变切片 b 的值 数组a 也会改变 b = append(b,10) b = append(b,10) b = append(b,10) b = append(b,10) b = append(b,10) b = append(b,10) // 添加了5个元素后,已经超过了切片 b 的容量 fmt.Println(b) fmt.Printf("b=%p a[1]=%p ",b,&a[1]) // 此时切片 b 的地址和 数组a第一个元素的地址就不再一样;因为在声明并定义切片b时没有超过b的容量,此时b指向原来的数组,当超过b的容量后,系统会重新开辟一块内存,并把切片b原有的值放到新开辟的内存中,然再把追加的内容放进去 b[1] = 1000 fmt.Println("after a:",a) // 此时改变 b 值, a的值将不会改变,因为 切片 b 指向的内存地址已经不是 a 了 } func main(){ testSlice() // 切片是可变的,就是因为内部会根据容量的大小重新分配内存 } // 运行结果如下: [root@NEO example05_slice_beyond_cap]# go run main/main.go b=0xc000014158 a[1]=0xc000014158 before a: [1 2 100 4 5] // a 中对应的值也发生了改变 [2 100 4 5 10 10 10 10 10 10] b=0xc000062080 a[1]=0xc000014158 after a: [1 2 100 4 5] // 此时 a 中的值并没有改变 [root@NEO example05_slice_beyond_cap]#
切片resize
var a = []int {1,3,4,5} b := a[1:2] b = b[0:3]
切片拷贝:copy()
s1 := []int{1,2,3,4,5} s2 := make([]int, 10) copy(s2, s1) // 把 切片 s1 拷贝到 切片 s2 中;拷贝不会扩容 // 示例代码: package main import "fmt" func testCopy(){ var a []int = []int{1,2,3,4,5} b := make([]int,10) c := make([]int,1) copy(b,a) // 把a拷贝到b中 copy(c,a) // 把a拷贝到c中 fmt.Println(b) fmt.Println(c) // 拷贝不会扩容 } func main(){ testCopy() } // 运行结果如下: [root@NEO example05_slice_copy]# go run main/main.go [1 2 3 4 5 0 0 0 0 0] [1] [root@NEO example05_slice_copy]#
切片示例:使用非递归的方式实现斐波那契数列,打印前10个数。
// 示例代码如下: package main import "fmt" func fab(n int){ var a []int // 声明一个切片;[]中没有数字,所以是切片 a = make([]int,n) // 为切片a 分配内存 a[0] = 1 a[1] = 1 for i := 2; i < len(a); i++ { a[i] = a[i-1] + a[i-2] } for _,v := range a{ fmt.Println(v) } } func main() { fab(10) } // 运行结果如下: [root@NEO example04_fab_slice]# go run main/main.go 1 1 2 3 5 8 13 21 34 55 [root@NEO example04_fab_slice]#
string与slice
string底层就是一个byte的数组,因此,也可以进行切片操作 str := “hello world” s1 := str[0:5] fmt.Println(s1) s2 := str[5:] fmt.Println(s2) string本身是不可变的,因此要改变string中字符,需要如下操作:先把字符串转换成数组 str := “hello world” s := []byte(str) s[0] = ‘o’ str = string(s)
示例代码:
// 示例代码如下: package main import "fmt" func strModifyByte(){ str := "hello world" s := []byte(str) // 把字符串转换为 byte 数组 fmt.Println(s) s[0] = 'o' str = string(s) // 再把 byte 数组转换为字符串 fmt.Println(str) } func strChangeRune(){ str := "hello 中国" s := []rune(str) // 把字符串转换为 字符 数组 (可以转换中文) fmt.Println(s) s[5] = '大' // 字符类型 一定要用 单引号 str = string(s) // 把 字符数组 转换成字符串 fmt.Println(str) } func main(){ strModifyByte() strChangeRune() } // 运行结果如下: [root@NEO example05_string_change]# go run main/main.go [104 101 108 108 111 32 119 111 114 108 100] oello world [104 101 108 108 111 32 20013 22269] hello大中国 [root@NEO example05_string_change]#
排序和查找操作
// 排序操作主要都在 sort包中,导入就可以使用了 import(“sort”) // sort.Ints对整数进行排序, sort.Strings对字符串进行排序, sort.Float64s对浮点数进行排序. // 查找 sort.SearchInts(a []int, b int) 从数组a中查找b,前提是a必须有序 sort.SearchFloats(a []float64, b float64) 从数组a中查找b,前提是a必须有序 sort.SearchStrings(a []string, b string) 从数组a中查找b,前提是a必须有序
示例代码:
// 示例代码: package main import ( "fmt" "sort" ) func testSortInt(){ var a = [...]int{1, 8, 38, 2, 348, 484} sort.Ints(a[:]) // 数组是值类型,所以对数组排序时需要传入其切片 fmt.Println(a) } func testSortStrings(){ var a = [...]string{"abc", "efg", "b", "A", "eeee"} sort.Strings(a[:]) fmt.Println(a) } func testSortFloat(){ var a = [...]float64{2.3, 0.8, 28.2, 392342.2, 0.6} sort.Float64s(a[:]) fmt.Println(a) } func testSearchInts(){ var a = [...]int{1, 8, 38, 2, 348, 484} sort.Ints(a[:]) // 搜索前需要先将数组排序;如果没有先排序,搜索到的索引值也是排序后的索引值 index := sort.SearchInts(a[:],348) // 传入切片 fmt.Println(index) } func main(){ testSortInt() testSortStrings() testSortFloat() testSearchInts() } // 运行结果如下: [root@NEO example05_sort_search]# go run main/main.go [1 2 8 38 348 484] [A abc b eeee efg] [0.6 0.8 2.3 28.2 392342.2] 4 [root@NEO example05_sort_search]#
map数据结构
map简介:key-value的数据结构,又叫字典或关联数组 map 声明: var map1 map[keytype]valuetype var a map[string]string var a map[string]int var a map[int]string var a map[string]map[string]string // 声明是不会分配内存的,初始化需要make map相关操作 var a map[string]string = map[string]string{“hello”: “world”} a = make(map[string]string, 10) a[“hello”] = "world" // 插入和更新 val, ok := a[“hello”] // 查找; val 和 ok 都是变量名 for k, v := range a { // 遍历 fmt.Println(k,v) } delete(a, “hello”) // 删除 "hello" 这个key 对应的 k-v;删除map所有元素可以用 for 循环 len(a) // 长度
示例代码:
// 示例代码: package main import "fmt" func testMap(){ var a map[string]string // 声明、定义一个map;声明是不会分配内存的,初始化需要make(初始化会分配内存) a = make(map[string]string,10) // 初始化 a["a"] = "NEO" // 未初始化的 map 不能直接使用(如这一步的赋值操作) fmt.Println(a) var b map[string]string = map[string]string{ // 声明并初始化 "b":"neo", } fmt.Println(b) c := make(map[string]string,3) // 声明并初始化 fmt.Println(c) e := map[string]string{ "key":"val", } fmt.Println(e) d := make(map[string]map[string]string,100) // map 中嵌套 map;此处的map声明并初始化只是对最外层的 map 进行了 初始化,里面的 map 只进行了声明,但并未初始化 d["key1"] = make(map[string]string,100) // 在对里面那个 map 进行操作之前,需要先对里面的 map 初始化 d["key1"]["key2"] = "abc" // 对里面的map赋值 d["key1"]["key3"] = "de" fmt.Println(d) } func testMapSearch(a map[string]map[string]string){ val,is_existed := a["zhangsan"] // 在 map a 中查找 "zhangsan" 这个key 是否存在;存在的话把"zhangsan"对应的值返回val,is_existed 为 true,不存在的话, is_existed 为 false fmt.Println(val,is_existed) if is_existed { val["nickname"] = "胖哥" val["passwd"] = "123456" } else { a["zhangsan"] = make(map[string]string) // map 中不存在 "zhangsan" 这个 key,则在对其操作前要先对其初始化 a["zhangsan"]["nickname"] = "胖哥" a["zhangsan"]["passwd"] = "123456" } } func main(){ testMap() a := make(map[string]map[string]string,100) testMapSearch(a) fmt.Println("testMapSearch:",a) } // 值类型的数据定义(声明)之后就能直接使用,但 map 这种引用类型的数据定义之后一定要初始化之后才能使用 // 运行结果如下: [root@NEO example06_map_var]# go run main/main.go map[a:NEO] map[b:neo] map[] map[key:val] map[key1:map[key2:abc key3:de]] map[] false testMapSearch: map[zhangsan:map[nickname:胖哥 passwd:123456]] [root@NEO example06_map_var]#
map是引用类型
func modify(a map[string]int) { a[“one”] = 134 // 函数外 a 的值也会改变 }
slice of map
items := make([]map[int][int], 5) For i := 0; i < 5; i++ { items[i] = make(map[int][int]) // 全部初始化;也可以用到哪个的时候再对哪个初始化 } // map 和 slice 等是否为空用 nil 来判断
map排序
a. 先获取所有key,把key进行排序 (// 每次遍历时,map 的 key的顺序都是不一样的 (key是无序的)) b. 按照排序好的key,进行遍历
示例代码:
// 示例代码: package main import ( "fmt" "sort" ) func testMapSort(){ var a map[int]string a = make(map[int]string,100) a[2] = "hello" a[1] = "中国" a[25] = "aaa" a[8] = "abc" a[72] = "world" // 先获取所有key,把key进行排序 var keys []int for k,_ := range a{ keys = append(keys,k) } sort.Ints(keys) // 按照排序好的key,进行遍历 for _,v := range keys{ fmt.Println(v,a[v]) } } func main(){ testMapSort() } // 运行结果如下: [root@NEO example06_map_sort]# go run main/main.go 1 中国 2 hello 8 abc 25 aaa 72 world [root@NEO example06_map_sort]#
Map反转
// 初始化另外一个map,把key、value互换即可
锁
a. import(“sync”) b. 互斥锁, var mu sync.Mutex c. 读写锁, var mu sync.RWMutex // 互斥锁:同一时间只有一个 goroutine 能进去 (不论读写);适用于 写操作多的场景 // 读写锁:多个 goroutine 可同时进行读操作(一个协程进去加了个读锁,后面的读协程也可以进去,但写协程进不去),但写协程还是互斥的;适用于 读多写少的场景
示例代码:
// 示例代码: package main import ( "fmt" "math/rand" "sync" "sync/atomic" "time" ) var lock sync.Mutex // 互斥锁 var rwLock sync.RWMutex // 读写锁 func testMap() { var a map[int]int a = make(map[int]int, 5) a[8] = 10 a[3] = 10 a[2] = 10 a[1] = 10 a[18] = 10 for i := 0; i < 2; i++ { go func(b map[int]int) { // 匿名函数 lock.Lock() b[8] = rand.Intn(100) // 写操作;中间的代码加锁 lock.Unlock() }(a) } lock.Lock() fmt.Println(a) // 读操作;中间的代码加锁 lock.Unlock() time.Sleep(time.Second) } func testRWLock() { var a map[int]int a = make(map[int]int, 5) var count int32 a[8] = 10 a[3] = 10 a[2] = 10 a[1] = 10 a[18] = 10 for i := 0; i < 2; i++ { go func(b map[int]int) { rwLock.Lock() b[8] = rand.Intn(100) time.Sleep(10 * time.Millisecond) rwLock.Unlock() }(a) } for i := 0; i < 100; i++ { go func(b map[int]int) { for { rwLock.RLock() time.Sleep(time.Millisecond) //fmt.Println(a) rwLock.RUnlock() atomic.AddInt32(&count, 1) // 修改公共数据 count : 进行原子性 写操作 } }(a) } time.Sleep(time.Second * 3) fmt.Println(atomic.LoadInt32(&count)) // 读取进行原子性的数据 } func main() { //testMap() testRWLock() }