go语言基础-----04-----闭包、数组切片、map、package

一 闭包、数组切片、map、package

1 内置函数

  1. close:主要用来关闭channel。
  2. len:用来求长度,比如string、array、slice、map、channel。
  3. new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针。
  4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice。
  5. append:用来追加元素到数组、slice中。
  6. panic和recover:用来做错误处理。
  7. new和make的区别。
    1)简单来说,new就是开辟内存返回指针并初始化该内存为0,类似C++的new后,然后memset为0,所以new在go语言中,一般是用在值类型、数组、结构体等。
    2)而make是返回引用(这里可以理解为对象),并对内部进行初始化,但是这个初始化并不是初始化为0,而是给内部的成员进行必要的内存开辟,例如成员有指针,那么需要开辟内存而不是简单的初始化为0。所以这也是为什么slice、map、channel只能用make分配的原因,因为slice、map、channel内部成员都会含有对应的指针。具体see golang 内置函数new()和make()的区别。

其实对于有C++基础的,非常容易懂。

2 闭包

  • 1)闭包的概念:一个函数和与其相关的引用环境组合而成的实体。
  • 2)闭包的主要作用:闭包实际上是为了减少全局变量的使用。
  • 3)闭包可以做到与外界环境隔离,即缩小作用域但生命周期长期存在。

例如下面例子,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语言基础-----04-----闭包、数组切片、map、package_第1张图片

go的闭包减少全局变量类似C++使用类内成员来减少全局变量,即

class Addr{
	public:
		int sum(int a){return x+=a;}
	public:
		int x;
};

3 数组

3.1 数组的相关概念、定义和注意点

  1. 数组:是同一种数据类型的固定长度的序列。
  2. 数组定义:var a [len]int,比如:var a[5]int一旦定义,长度不能变。并且len要是常量,否则会报错。
var a[5]int		// ok
//var x int = 1
//var aa[x]int	// 报错error: non-constant array bound x
  1. 长度是数组类型的一部分,因此,vara[5] int和vara[10]int是不同的类型。
  2. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1。
  3. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic 。
  4. 数组是值类型,因此改变副本的值,不会改变本身的值。
var a [3]int
b := a

b[0] = 100
fmt.Println(a)		// a不会因b改变, 因此还是输出:[0 0 0]
  1. 数组的len为空时,有些人虽然也可以叫数组,但是更准确的叫法应该是切片。因为在使用时,它是len和cap是可以改变的。可以通过len函数和cap函数去观察即可。 而写成…时,它会被编译器自动推导成固定大小的数组,使用append添加元素时会报错,所以它是一个数组。
// 例如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)

go语言基础-----04-----闭包、数组切片、map、package_第2张图片

二维数组:

// 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)

go语言基础-----04-----闭包、数组切片、map、package_第3张图片

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 数组的比较

  • 1)go的数组是支持比较的,go的数组是值类型。所以它在对比时,对比的是两个数组的每一个元素是否一样,并且要求两个数组类型要一致,否则会直接报错。
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 数组作为形参

  • 1)数组作为形参,改变形参,原数组不会被改变,因为是值传递。
  • 2)数组指针作为形参,改变形参,原数组会发送改变,因为是地址传递。

例如:

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 切片

4.1 切片的重要公式
切片在切割时,有这样一条公式 [low:hign:max]。

  • 1)其中low代表切割的起始下标。
  • 2)hign代表切割的末尾下标,但不包含该下标。
  • 3)max该值用于控制容量的大小。
  • 4)切割后的长度len求法:len = hign - low。
  • 5)切割后的容量cap求法:cap = max - low。

4.2 数组与切片的区别

  • 1)数组与切片最明显的区别就是,数组里面的[len]的长度len不能变,而切片可以。但是需要注意,[]里为空是切片,为…是数组,因为编译器默认给你生成固定长度的数组。
    例如:
// 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)

go语言基础-----04-----闭包、数组切片、map、package_第4张图片

4.3 切片的创建

切片的创建可以使用下面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))

结果:
go语言基础-----04-----闭包、数组切片、map、package_第5张图片

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.

结果:
go语言基础-----04-----闭包、数组切片、map、package_第6张图片

4.5 切片和底层数组的关系

  • 1)切片实际上就是对该数组的引用,使用切片改变的值,那么该原数组也会发生变化,因为切片是该数组的引用,它们共用一片内存。所以当我们在对底层数组进行切片时,需要注意这一点。
// 切片与底层数组的关系
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)

go语言基础-----04-----闭包、数组切片、map、package_第7张图片

4.6 append、copy内建函数

  • 1)append:往切片尾部添加一个或者多个元素。
  • 2)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))

go语言基础-----04-----闭包、数组切片、map、package_第8张图片

4.7 切片作为形参

  • 1)切片做形参,为引用类型,同C++的引用作为形参去理解即可。这里就不给出例子了。

5 map

5.1 map介绍

  • 1)map的定义格式为:map[keyType]valueType。
  • 2)map的key都是唯一的,而且必须支持==、!=操作符的类型,map的key不能是切片、函数或者包含切片类型的结构体,因为这些是引用,会报非法key错误。
    例如:
// 报错err:Invalid map key type: the comparison operators == and != must be fully defined for key type
dict := map[[]string]int 
  • 3)go中map返回的key时是无序的,所以打印时可以看到每次结果可能都是不一样的。

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))

go语言基础-----04-----闭包、数组切片、map、package_第9张图片

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))

go语言基础-----04-----闭包、数组切片、map、package_第10张图片

5.5 map作为函数的形参
很简单,和切片一样,map做形参,为引用类型,同C++的引用作为形参去理解即可。

6 package

暂时省略,后续有空再补。

你可能感兴趣的:(Go,golang,开发语言,后端)