四、常用结构
1、内置函数、闭包
- close:主要用来关闭channel
- len:用来求长度,比如string、 array、 slice、 map、 channel
- new:用来分配内存,主要用来分配值类型,比如int、 struct。返回的是指针
- make:用来分配内存,主要用来分配引用类型,比如chan、 map、 slice
- append:用来追加元素到数组、 slice中
- panic和recover:用来做错误处理
闭包:一个函数和与其相关的引用环境组合而成的实体
package main import "fmt" func Adder() func(int) int { var x int//只初始化一次 return func(delta int) int { x += delta return x } } //上述代码类似于cpp中在类中定义成员x,func函数对x进行+delta func main() { var f = Adder() fmt.Println("1:", f(1))//0 fmt.Println("2:", f(20))//21 fmt.Println("3:", f(300))//321 }
2、数组与切片
数组:是同一种数据类型的固定长度的序列
数组定义: var a [len]int,比如: var a[5]int 一旦定义,长度不能变
长度是数组类型的一部分,因此, var a[5] int和var a[10]int是不同的类型
访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
数组是值类型,因此参数传递改变的是副本的值,不会改变本身的值
数组初始化及遍历:
多维数组及遍历:
var age [5][3]int var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} for k1, v1 := range f { for k2, v2 := range v1 { fmt.Printf("(%d,%d)=%d ", k1, k2, v2) } fmt.Println() }
切片定义:
切片:切片是数组的一个引用,因此切片是引用类型
切片的长度可以改变,因此, 切片是一个可变的数组
切片遍历方式和数组一样,可以用len()求长度
cap可以求出slice最大的容量, 0 <= len(slice) <= (array), 其中array是slice引用的数组
make创建切片:
slice := make([]type, len) slice := make([]type, len, cap)
切片初始化:
var slice1 []int = arr[start:end]//包含start到end之间的元素,但不包含end,[start,end) var slice2 []int = arr[0:end]//可以简写为 var slice []int=arr[:end] var slice3 []int = arr[start:len(arr)]//可以简写为 var slice[]int = arr[start:] var slice4 []int = arr[0, len(arr)]//可以简写为 var slice[]int = arr[:] Slice = slice[:len(slice)-1] //最后一个元素去掉
切片的内存布局,类似C++ vector:
用append内置函数操作切片:
slice := make([]int, 10) slice=append(slice, 10)//增添元素,更新 var a = []int{1, 2, 3} var b = []int{4, 5, 6} a = append(a, b...)//增添数组
For range 遍历切片:
for index, val := range slice { }
切片resize:
var a = []int {1,3,4,5} b := a[1:2] b = b[0:3]
切片拷贝:
s1 := []int{1,2,3,4,5} s2 := make([]int, 10) copy(s2, s1)
string与slice:
string底层就是一个byte的数组,因此,也可以进行切片操作
str := “hello world” s1 := str[0:5] fmt.Println(s1) s2 := str[5:] fmt.Println(s2) s := []byte(str)//修改str s[0] = ‘o’ str = string(s)
string的底层布局:
数组和切片的区别:
数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组,指定空间大小
切片:类型 []T 表示一个元素类型为 T 的切片(动态开辟数组),不指定大小
new和make的区别:
var i *int=new(int) *i=10 var num []int = make([]int, 10) num[1]=10
make也是用于内存分配的,但是和new不同,它只用于chan、 map以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
3、map
map是一种特殊的数据结构:一种元素对 (pair) 的无序集合,pair 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key,对应的 value 可以迅速定位。
map是引用类型,可以使用如下声明:
var map1 map[keytype]valuetype var map1 map[string]int
key 可以是任意可以用 ==
或者 !=
操作符比较的类型,比如 string
、int
、float32(64)
。所以数组、切片和结构体不能作为 key ,但是指针和接口类型可以。如果要用结构体作为 key 可以提供Key()
和 Hash()
方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key
value 可以是任意类型的;通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言
map
是 引用类型 的: 内存用 make()
方法来分配
map
的初始化:
var map1 = make(map[keytype]valuetype)
你错误地使用 new()
分配了一个引用对象,你会获得一个空引用的指针
测试 map1
中是否存在 key1
:
if _, ok := map1[key1]; ok { // ... }
从 map1
中删除 key1
:
delete(map1, key1)
注:如果 key1
不存在,该操作不会产生错误
使用 for
循环读取 map
:
for key, value := range map1 { ... }
只想获取 key
:
for key := range map1 { fmt.Printf("key is: %d\n", key) }
注意 map
不是按照 key 的顺序排列的,也不是按照 value 的序排列的
如果你想为 map
排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort
包),然后可以使用切片的 for-range 方法打印出所有的 key 和 value
package main import ( "fmt" "sort" var ( barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23, "delta": 87, "echo": 56, "foxtrot": 12, "golf": 34, "hotel": 16, "indio": 87, "juliet": 65, "kili": 43, "lima": 98} ) func main() { fmt.Println("unsorted:") for k, v := range barVal { fmt.Printf("Key: %v, Value: %v / ", k, v) } keys := make([]string, len(barVal)) i := 0 for k, _ := range barVal { keys[i] = k i++ } sort.Strings(keys) fmt.Println() fmt.Println("sorted:") for _, k := range keys { fmt.Printf("Key: %v, Value: %v / ", k, barVal[k]) } }
五、Go中包及go mod
1、go mod
实际项目开发中我们首先要在我们项目目录中用go mod命令生成一个go.mod文件管理我们项目的依赖
使用go mod命令生成一个go.mod文件
go mod init goProject
生成一个 go.mod 的文件,里面的内容是go版本,以及以后添加的包
module goProject go 1.14
2、包
包(package)是多个Go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包
package 包名
注意事项
- 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
- 包名可以不和文件夹的名字一样,包名不能包含-符号。
- 包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
3、init()初始化函数
在Go 语言程序执行时导入包语句会自动触发包内部init()函数的调用。
需要注意的是:init() 函数没有参数也没有返回值。init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。
包初始化顺序:
依赖
使用go mod命令生成一个go.mod文件
go mod init goProject
生成一个 go.mod 的文件,里面的内容是go版本,以及以后添加的包
module goProject go 1.14
2、包
包(package)是多个Go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包
package 包名
注意事项
- 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
- 包名可以不和文件夹的名字一样,包名不能包含-符号。
- 包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
3、init()初始化函数
在Go 语言程序执行时导入包语句会自动触发包内部init()函数的调用。
需要注意的是:init() 函数没有参数也没有返回值。init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。
包初始化顺序:
[外链图片转存中…(img-jR5UCw7D-1698326264819)]
[外链图片转存中…(img-OPYWhU6s-1698326264819)]