其实对于有C++基础的,非常容易懂。
例如下面例子,Adder()中的x就是闭包函数外、包裹着闭包函数的函数内的局部变量。main中的 f 就是调用者的局部变量。这样如果调用者想要多个全局变量,定义多个 f 即可。
package main
import (
"fmt"
"strings"
)
/*
* 1.1. 缩小变量作用域,减少对全局变量的污染。
* 下面的累加如果用全局变量进行实现,全局变量容易被其他人污染,同时,如果我要实现n个累加器,那么每次需要n个全局变量。
* 利用闭包,每个生成的累加器myAdder1, myAdder2 := adder(), adder()有自己独立的sum,sum可以看作为myAdder1.sum与myAdder2.sum。
*/
func Adder() func(int) int {
var x int
fmt.Println("Adder:", x) // 1.2. 只会执行一次
f := func(d int) int {
x += d
fmt.Println("bibao:", x) // 1.3. x生命周期长期存在,类似C/C++的static。
return x
}
return f
}
// 2.1 以形参为例
func makeSuffix(suffix string) func(string) string {
f := func(name string) string {
if strings.HasSuffix(name, suffix) == false { //没有匹配,则在suffix前添加name
suffix = name + suffix
return suffix
//return name + suffix // 2.2 注意这样写不行,因为没有修改到形参即全局变量suffix。
}
return name
}
return f
}
func main() {
// 1. 变量保存函数,这样变量f的值就是 返回的闭包函数。
// 结果可以看到,x在闭包中就是相当于全局变量,因为作用域不是全局但生命周期长期存在。
f := Adder()
fmt.Println(f(1)) // 1
fmt.Println(f(100)) // 101
fmt.Println("=======================")
// 2. 同样测试闭包,但是这次以形参替代1中的x。看到结果形参同样是支持的,与x的作用一样。
f1 := makeSuffix(".bmp")
fmt.Println(f1("test"))
fmt.Println(f1("pic"))
f2 := makeSuffix(".jpg")
fmt.Println(f2("test"))
fmt.Println(f2("pic"))
}
go的闭包减少全局变量类似C++使用类内成员来减少全局变量,即
class Addr{
public:
int sum(int a){return x+=a;}
public:
int x;
};
3.1 数组的相关概念、定义和注意点
var a[5]int // ok
//var x int = 1
//var aa[x]int // 报错error: non-constant array bound x
var a [3]int
b := a
b[0] = 100
fmt.Println(a) // a不会因b改变, 因此还是输出:[0 0 0]
// 例如var a []int = []int{1,2,3}切片, aa := [...]int{1,2,3}数组。
注意:
var a [...]int = []int{1,2,3}、
var a []int = [...]int{1,2,3}、
var a [...]int = [...]int{1,2,3}
这3种定义编译器会报错。
3.2 数组的初始化
一维数组:
// 数组的初始化。声明的同时并进行赋值,叫做初始化。
// 1. 数组的全部初始化。
var a [5]int = [5]int{0, 1, 2, 3, 4}
fmt.Println(a)
// 2. 部分初始化。没有初始化的默认补0.
var b [5]int = [5]int{1, 2, 3}
fmt.Println(b)
// 3. 指定下标初始化。
// 指定下标2的值为10,下标4的值为20,剩余的默认补0
var c [5]int = [5]int{2: 10, 4: 20}
fmt.Println(c)
// 此外可以使用...去自动求数组的长度,若是指定下标初始化,那么以最大下标+1为数组长度。
// 但是这种动态求长度的,更准确的叫法应该叫做切片。
var e = [...]int{38, 283, 48, 38, 348} // len: 5
var f = [...]int{1: 100, 3: 200} // len: 4
var g = [...]string{1: "hello", 3: "world"} // len: 4
fmt.Println(e)
fmt.Println(f)
fmt.Println(g)
二维数组:
// 1. 二维数组的全部初始化
var a [3][4]int = [3][4]int{ {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3} }
fmt.Println(a)
// 2. 二维数组的部分初始化
var b [3][4]int = [3][4]int{ {0, 1, 2}, {0, 1}}
fmt.Println(b)
// 3. 二维数组的指定初始化
var c [3][4]int = [3][4]int{ 1:{0, 1, 2, 3}}
fmt.Println(c)
3.3 数组的遍历
// 一维数组
//var aaa []uint64 // 这样相当于只声明了数组,此时数组打印为空:[]
//fmt.Println(aaa)
var a [10]int
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
// 或者
for index, val := range a {
fmt.Printf("a[%d]=%d\n", index, val)
}
// 二维数组
var a [2][5]int = [...][5]int{{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}}
for row, v := range a {
for col, v1 := range v {
fmt.Printf("(%d,%d)=%d ", row, col, v1)
}
fmt.Println()
}
3.4 数组的比较
var a [5]int = [5]int{0, 1, 2, 3, 4}
var b [5]int = [5]int{0, 1, 2, 3, 4}
fmt.Println("a==b:", a==b) // output: a==b: true
//var c [3]int = [3]int{0, 1, 2}
//fmt.Println("b==c:", b==c)// 报错
3.5 数组作为形参
例如:
func test1(arr [5]int) {
arr[0] = 1000
}
func test2(arr *[5]int) {
(*arr)[0] = 1000
}
var a [5]int
// 1. 数组做形参。 值传递,它会拷贝一份,所以不会改变原数组的值。
test1(a)
fmt.Println(a)
// 2. 数组指针做形参。地址传递,形参就是实参,所以会改变原数组的值。
test2(&a)
fmt.Println(a)
4.1 切片的重要公式
切片在切割时,有这样一条公式 [low:hign:max]。
4.2 数组与切片的区别
// 1. 数组长度是固定的
var a [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Printf("a len: %d, a cap: %d\n", len(a), cap(a))
fmt.Println("a", a)
fmt.Println("=====================")
// 2. 切片,[]里为空时,长度、容量是不固定的。
aa := []int{}
fmt.Printf("aa len: %d, aa cap: %d\n", len(aa), cap(aa))
fmt.Println("aa", aa)
aa = append(aa, 10)
aa = append(aa, 11,12) // 一次往尾部添加两个元素
fmt.Printf("aa len: %d, aa cap: %d\n", len(aa), cap(aa))
fmt.Println("aa", aa)
// 注意:为...时,append编译报错,因为编译器给你默认推导的是固定长度的数组。
//s := [...]int{1, 2}
//fmt.Printf("s len: %d, s cap: %d\n", len(s), cap(s))
//fmt.Println("s", s)
//s = append(s, 10) // 报错
//s = append(s, 11,12)
//fmt.Printf("aa len: %d, aa cap: %d\n", len(aa), cap(aa))
//fmt.Println("aa", aa)
切片的创建可以使用下面3种方法去定义。
// 切片的定义
// 1. 传统定义法:var 变量名 []类型。
var str []string
var arr []int
fmt.Println("str", str)
fmt.Println("arr", arr)
// 2. 自动推导类型
str1 := []string{"hh"}
arr1 := []int{1}
fmt.Println("str1", str1)
fmt.Println("arr1", arr1)
// 3. make函数。格式为:make(切片类型,长度,容量)
str2 := make([]int, 2, 2)
fmt.Println("len=", len(str2), "cap=", cap(str2))
// make函数没有指定容量时,与长度一样。
// 注意没有指定容量,与长度一样,这个长度指的是cap(array)的值。len同理,没有写len,其值默认是len(arr)
str3 := make([]int, 100)
fmt.Println("len=", len(str3), "cap=", cap(str3))
4.4 切片的截取
切片的截取不难,认真看和自己测试一下即可。
array := [10]int{0,1,2,3,4,5,6,7,8,9}
// 1. 截取数组所有的元素
s1 := array[:] // 不写时,默认low=0,hign=len(array),cap=cap(array)
fmt.Println("s1", s1)
fmt.Println("len=", len(s1), "cap=", cap(s1)) // 10 10
// 2. 参数都写时
//s2 := array[3:6:2] // 注这样写会报错,容量必须大于其它两个参数,err:Invalid index values, must be low <= high <= max
s2 := array[3:6:7]
fmt.Println("s2", s2) // [3 4 5]
fmt.Println("len=", len(s2), "cap=", cap(s2)) // len=6-3=3 cap=7-3=4
// 3. 只写一个hign参数时。常用。
s3 := array[:6]
fmt.Println("s3", s3) // [0 1 2 3 4 5]
fmt.Println("len=", len(s3), "cap=", cap(s3)) // len=6-0=6 cap=cap(array)-0=10-0=10.注意不是使用6.
// 注意没有指定容量,与长度一样,这个长度指的是cap(array)的值,而不是使用6.
// 4. 只写一个low参数时。
s4 := array[3:]
fmt.Println("s4", s4) // [3 4 5 6 7 8 9]
fmt.Println("len=", len(s4), "cap=", cap(s4)) // len=len(s4)-3=10-3=7 cap=cap(array)-3=10-3=7.
4.5 切片和底层数组的关系
// 切片与底层数组的关系
array := [10]int{0,1,2,3,4,5,6,7,8,9}
// 1. 新切片1
s1 := array[2:5] // s1 = [2 3 4], len=3,cap=8
//fmt.Println("len=", len(s1), "cap=", cap(s1))
// 通过切片改变值,原底层数组也会发生变化
s1[1] = 666
fmt.Println("s1", s1)
fmt.Println("array", array)
fmt.Println("=================")
// 2. 新切片2。
// 这里需要说一下:
// 1)虽然s1只有3个元素,而没有5个元素,但是它是去原底层数组去寻找的,所以写成2:7切5个元素是可以的,但不能比原数组大,会报panic错误。
// 2)len是7-2=5,那么cap怎么求呢,就是cap=cap(s1)-2=8-2=6. cap(s1)=8是因为上面切片s1的容量。
s2 := s1[2:7] // s2 = [4 5 6 7 8], len=5,cap=8-2=6
fmt.Println("len=", len(s2), "cap=", cap(s2))
s2[2] = 777
fmt.Println("s2", s2)
fmt.Println("array", array)
4.6 append、copy内建函数
// 原切片
var a []int = []int{1, 2, 3, 4, 5, 6}
fmt.Println("a", a)
fmt.Println("len=", len(a), "cap=", cap(a))
// 目的切片
b := make([]int, 1) // 容量不足时,只会拷贝对应的容量,例如这里只会拷贝1个。
//b := make([]int, 11) // 容量足够时,拷贝全部。
fmt.Println("b", b)
fmt.Println("len=", len(b), "cap=", cap(b))
// 将a拷贝到b
copy(b, a)
// 查看拷贝后b的元素
fmt.Println("=================")
fmt.Println(b)
fmt.Println("len=", len(b), "cap=", cap(b))
4.7 切片作为形参
5.1 map介绍
// 报错err:Invalid map key type: the comparison operators == and != must be fully defined for key type
dict := map[[]string]int
5.2 map的基本操作
// 1. 传统定义一个map
var m1 map[int]string
fmt.Println("m1 = ", m1)
fmt.Println("len = ", len(m1))// 对于map,只有len,没有cap
// 2. 通过map创建
m2 := make(map[int]string)
fmt.Println("m2 = ", m2)
fmt.Println("len = ", len(m2))
// 3. 通过make创建并指定长度,map的长度实际是容量,但是注意map时map里面是没有东西的。并且长度不足时,它会自动扩容。
m3 := make(map[int]string, 2)
m3[0] = "C"
m3[1] = "C++"
m3[2] = "lua" // 会自动扩容
fmt.Println("m3 = ", m3)
fmt.Println("len = ", len(m3))
// 4. 声明并赋值,即初始化
//m4 := map[int]string{0:"C", 1:"C++", 2:"lua", 3:"Go"}
m4 := map[int]string{0:"C", 1:"C++", 2:"lua", 3:"Go", 0:"AAA"}//err:duplicate key 0 in map literal previous key
fmt.Println("m4 = ", m4)
fmt.Println("len = ", len(m4))
5.3 map的遍历以及判断map中的某个key是否存在
// map的遍历
m1 := map[int]string{0:"C", 1:"C++", 2:"lua", 3:"Go"}
for k,v := range m1{
fmt.Println("k=", k, "v=", v)
}
// map如何判断一个key是否存在
value, ok := m1[0]
if true == ok{
fmt.Println("value=", value)
}else{
fmt.Println("key not exist")
}
5.4 map删除某个key值
直接使用delete函数即可。
m1 := map[int]string{0:"C", 1:"C++", 2:"lua", 3:"Go"}
fmt.Println("m1 = ", m1)
fmt.Println("len = ", len(m1))
delete(m1, 2)
fmt.Println("m1 = ", m1)
fmt.Println("len = ", len(m1))
5.5 map作为函数的形参
很简单,和切片一样,map做形参,为引用类型,同C++的引用作为形参去理解即可。
暂时省略,后续有空再补。