go语言提供了哪些集合类型的数据结构?
1、数组、切片和map
1.1、数组的基本用法
package main
import "fmt"
/*
* 数组 定义: var name [count]type
*/
func main() {
var array1 [3]string //array1类型,数组 只有3个元素的数组
var array2 [4]string //array2类型,数组 只有4个元素的数组
// 注意:array1 和 array2 是两种不同类型的数组,因为数组元素个数不同。
// 另外,还要注意:[]string 和 [3]string 这是两种不同的类型,[]string 是切片,[3]string是 数组。
fmt.Printf("%T\r\n", array1)
fmt.Printf("%T", array2)
}
运行结果:
从运行结果可以看出,array1 和 array2 的类型不同。
定义数组,并往里面存值。
package main
import "fmt"
/*
* 数组 定义: var name [count]type
*/
func main() {
var array1 [3]string //array类型,数组 只有3个元素的数组
array1[0] = "张三"
array1[1] = "李四"
array1[2] = "王五"
fmt.Println(array1)
}
运行结果:
数组的遍历:
package main
import "fmt"
/*
* 数组 定义: var name [count]type
*/
func main() {
var array1 [3]string
array1[0] = "张三"
array1[1] = "李四"
array1[2] = "王五"
for _, value := range array1 {
fmt.Println(value)
}
}
运行结果:
1.2、数组的初始化
package main
import "fmt"
/*
* 第二周:1.2节 数组的初始化
*/
func main() {
//数组的初始化
//var array1 = [3]string{"张三", "李四", "王五"}
//还可以用下面的简写方式初始化数组
array1 := [3]string{"张三", "李四", "王五"}
for _, value := range array1 {
fmt.Println(value)
}
}
运行结果:
另外,还有一种初始化方式,假如我想将数组的第三个位置的元素初始化为"李四",其他位置的元素保持默认,可以这样初始化:
package main
import "fmt"
/*
* 第二周:1.2节 数组的初始化
*/
func main() {
//数组的初始化
array1 := [3]string{2: "李四"}
for _, value := range array1 {
fmt.Println(value)
}
}
debug运行结果如下:
string类型的默认值是空字符串,可以看到运行结果和我们预期的一致。
运行结果:
如果数组初始化时我们不想指定数组的长度,如果我们往数组里面放2个元素,数组的长度就为2;如果我们往数组里面放3个元素,数组的长度就为3。那么可以这样初始化数组(使用...替换 数字):
package main
import "fmt"
/*
* 第二周:1.2节 数组的初始化
*/
func main() {
//数组的初始化
//如果数组初始化时我们不想指定数组的长度,如果我们往数组里面放2个元素,数组的长度就为2;如果我们往数组里面放3个元素,数组的长度就为3。那么可以这样初始化数组:
array1 := [...]string{"张三", "李四", "王五"}
for _, value := range array1 {
fmt.Println(value)
}
}
debug运行结果如下:
可以看到数组的长度为3。
然后,我们再向数组里面放2个元素:
package main
import "fmt"
/*
* 第二周:1.2节 数组的初始化
*/
func main() {
//数组的初始化
//如果数组初始化时我们不想指定数组的长度,如果我们往数组里面放2个元素,数组的长度就为2;如果我们往数组里面放3个元素,数组的长度就为3。那么可以这样初始化数组:
array1 := [...]string{"张三", "李四"}
for _, value := range array1 {
fmt.Println(value)
}
}
debug运行结果如下:
可以看到数组的长度为2。
数组的另外一种遍历方式:
package main
import "fmt"
/*
* 第二周:1.2节 数组的初始化
*/
func main() {
//数组的初始化
array1 := [...]string{"张三", "李四"}
//数组的另外一种遍历方式:
for i := 0; i < len(array1); i++ {
fmt.Println(array1[i])
}
}
运行结果:
1.3、多维数组
我们先来说一下数组的比较。
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//数组的比较
array1 := [...]string{"张三", "李四"}
array2 := [...]string{"张三", "王五"}
if array1 == array2 {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
数组比较的时候,会比较两个数组的每一个元素是否相等。
运行结果:
如果将 array1 和 array2 的元素都修改为一样的:
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//数组的比较
array1 := [...]string{"张三", "李四"}
array2 := [...]string{"张三", "李四"}
if array1 == array2 {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
运行结果:
如果将 array1 和 array2 的元素是一样的,但是元素的顺序不一样,比较相等的结果也是不相等的:
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//数组的比较
array1 := [...]string{"张三", "李四"}
array2 := [...]string{ "李四", "张三"}
if array1 == array2 {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
运行结果:
多维数组的定义:
package main
/*
* 第二周:1.3节 多维数组
*/
func main() {
//数组的比较
//array1 := [...]string{"张三", "李四"}
//array2 := [...]string{"张三", "李四"}
//if array1 == array2 {
// fmt.Println("equal")
//} else {
// fmt.Println("not equal")
//}
//多维数组
var userInfo [3][4]string
//定义多维数组 userInfo 的第0行的元素
userInfo[0] = [4]string{"张三", "18", "13723456876", "山东省青岛市"}
//也可以单独得一个一个元素定义
userInfo[0][0] = "张三"
userInfo[0][1] = "18"
userInfo[0][2] = "13723456876"
userInfo[0][3] = "山东省青岛市"
}
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//多维数组
var userInfo [3][4]string
//定义多维数组 userInfo 的第0行的元素
userInfo[0] = [4]string{"张三", "18", "13723456876", "山东省青岛市"}
userInfo[1] = [4]string{"李四", "18", "13723456877", "山东省青岛市李沧区"}
userInfo[2] = [4]string{"王五", "18", "13723456878", "山东省青岛市崂山区"}
//多维数组的遍历
for i := 0; i < len(userInfo); i++ {
for j := 0; j < len(userInfo[i]); j++ {
fmt.Print(userInfo[i][j] + " ")
}
fmt.Println()
}
}
运行结果:
还有更加简洁的多维数组遍历方式(for range方式)
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//多维数组
var userInfo [3][4]string
//定义多维数组 userInfo 的第0行的元素
userInfo[0] = [4]string{"张三", "18", "13723456876", "山东省青岛市"}
userInfo[1] = [4]string{"李四", "18", "13723456877", "山东省青岛市李沧区"}
userInfo[2] = [4]string{"王五", "18", "13723456878", "山东省青岛市崂山区"}
//多维数组的遍历
for _, row := range userInfo {
for _, column := range row {
fmt.Print(column + " ")
}
fmt.Println()
}
}
运行结果:
还可以打印一行
package main
import "fmt"
/*
* 第二周:1.3节 多维数组
*/
func main() {
//多维数组
var userInfo [3][4]string
//定义多维数组 userInfo 的第0行的元素
userInfo[0] = [4]string{"张三", "18", "13723456876", "山东省青岛市"}
userInfo[1] = [4]string{"李四", "18", "13723456877", "山东省青岛市李沧区"}
userInfo[2] = [4]string{"王五", "18", "13723456878", "山东省青岛市崂山区"}
// 还可以打印一行
for _, row := range userInfo {
fmt.Println(row)
}
}
运行结果:
1.4、切片的定义和赋值
package main
import "fmt"
/*
* 第二周:1.4节 切片的定义和赋值
*/
func main() {
//切片定义 var sliceName []sliceType
//注意:这里[]里面没有数组,这和数组的定义不一样。切片相当于动态长度的数组。
var slice []string
//用的时候和数组也很相似。
//使用append函数向切片里面存值
slice = append(slice, "张三")
slice = append(slice, "李四")
fmt.Println(slice)
fmt.Println(slice[1])
}
运行结果:
1.5、切片的多种初始化方式
切片的初始化方式 4种:
1、从数组直接创建
2、使用string{}
3、make函数
4、var sliceName []sliceType
1、从数组直接创建:
package main
import "fmt"
/*
* 第二周:1.5节 切片的多种初始化方式
*/
func main() {
// 切片的初始化方式 4种:1、从数组直接创建 2:使用string{} 3:make函数 4:var sliceName []sliceType
//1、从数组直接创建
array := [6]string{"张三", "李四", "王五", "赵六", "牛七", "马八"}
//现在将数组中的 张三 和 李四 取出来,变成一个切片 slice
slice := array[0:2] //左闭右开 [)
fmt.Println(slice)
//将数组中的全部元素取出,然后转化为切片
//allSlice := array[0:] //左闭右开 [)
//fmt.Println(allSlice)
}
2、使用string{}
package main
import "fmt"
/*
* 第二周:1.5节 切片的多种初始化方式
*/
func main() {
// 切片的初始化方式 4种:1、从数组直接创建 2:使用string{} 3:make函数 4:var sliceName []sliceType
//2:使用string{}
sliceTwo := []string{"张三", "李四", "王五", "赵六", "牛七", "马八"}
fmt.Println(sliceTwo)
}
3、make函数
package main
import "fmt"
/*
* 第二周:1.5节 切片的多种初始化方式
*/
func main() {
// 切片的初始化方式 4种:1、从数组直接创建 2:使用string{} 3:make函数 4:var sliceName []sliceType
//3:make函数
sliceThree := make([]string, 6)
sliceThree[0] = "张三"
sliceThree[1] = "李四"
sliceThree[2] = "王五"
sliceThree[3] = "赵六"
sliceThree[4] = "牛七"
sliceThree[5] = "马八"
//使用这种方式时,设置超出切片长度时会报错
//sliceThree[6] = "1213"
fmt.Println(sliceThree)
}
运行结果:
4、var sliceName []sliceType
package main
import "fmt"
/*
* 第二周:1.5节 切片的多种初始化方式
*/
func main() {
//4、多数情况下使用下面这种不设置slice大小的初始化方式
var sliceFour []string
sliceFour = append(sliceFour, "a")
fmt.Println(sliceFour)
}
1.6、切片的数据访问
访问切片的元素
package main
import "fmt"
/*
* 第二周:1.6节 切片的数据访问
*/
func main() {
var slice []string
slice = append(slice, "a")
slice = append(slice, "b")
slice = append(slice, "c")
slice = append(slice, "d")
slice = append(slice, "e")
//访问切片的单个元素
fmt.Println(slice[1])
}
package main
import "fmt"
/*
* 第二周:1.6节 切片的数据访问
*/
func main() {
var slice []string
slice = append(slice, "a")
slice = append(slice, "b")
slice = append(slice, "c")
slice = append(slice, "d")
slice = append(slice, "e")
//访问切片的多个元素
/*
* [startIndex, endIndex]
* 1、如果只有startIndex,没有endIndex,就表示访问从startIndex开始到结尾的所有元素
* 2、如果没有startIndex,有endIndex,就表示从0开始到endIndex之前的所有元素
* 3、如果没有startIndex,没有endIndex,就表示访问所有元素
* 4、如果有startIndex,有endIndex,就表示从startIndex开始到endIndex结尾的所有元素
*/
fmt.Println(slice[1:]) // 打印结果:[b c d e]
fmt.Println(slice[:4]) // 取值范围:左闭右开 [0,3) 打印结果:[a b c d]
fmt.Println(slice[:]) // 打印结果:[a b c d e]
fmt.Println(slice[0:4]) //取值范围:左闭右开 [0,3) 打印结果:[a b c d]
}
1.7、通过省略号添加多个元素到切片
func append(slice []Type, elems ...Type) []Type
elems ...Type表示动态参数,可以向切片中同时添加多个元素,用法如下:
package main
import "fmt"
/*
* 第二周:1.7节 通过省略号添加多个元素到切片
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
fmt.Println(slice)
}
运行结果:
如果想将2个切片合并到一起,应该怎么办?
首先可以想到的是for range循环的方式:
package main
import "fmt"
/*
* 第二周:1.7节 通过省略号添加多个元素到切片
*/
func main() {
var slice []string
var slice2 []string
slice = append(slice, "张三", "李四", "王五")
slice2 = append(slice2, "赵六", "牛七", "马八")
//fmt.Println(slice)
for _, value := range slice2 {
slice = append(slice, value)
}
fmt.Println(slice)
}
运行结果:
除了循环之外,将2个切片合并到一起,还有更简单的实现方式:
package main
import "fmt"
/*
* 第二周:1.7节 通过省略号添加多个元素到切片
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//将2个切片合并到一起
var slice2 []string
slice2 = append(slice2, "赵六", "牛七", "马八")
slice = append(slice, slice2...)
fmt.Println(slice)
}
其中append函数的第二个参数slice2...表示将slice2切片拆散为多个string元素,然后再通过append函数添加到slice切片中。
如果我只想将slice2里面的牛七和马八放入到slice里面,代码可以这样修改:
package main
import "fmt"
/*
* 第二周:1.7节 通过省略号添加多个元素到切片
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//将2个切片合并到一起
var slice2 []string
slice2 = append(slice2, "赵六", "牛七", "马八")
slice = append(slice, slice2[1:]...)
fmt.Println(slice)
}
运行结果:
1.8、切片元素的删除和拷贝
切片元素的删除:
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//如果现在我想将slice切片中的李四删除掉应该怎么办?
//这时稍微有点麻烦,需要将 李四 左边的元素 张三 取出来,再把 李四 右边的元素 王五 取出来,然后append到一起。
deletedSlice := append(slice[:1], slice[2:]...)
fmt.Println(deletedSlice)
}
如果想要删除slice切片中的王五,只保留前两个元素,应该这样操作:
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//如果想删除slice里面的王五
newSlice := slice[:2]
fmt.Println(newSlice)
}
这时,取slice切片中的前两个元素,放入到一个新的newSlice切片中。
还可以不新建切片,也可以完成删除最后一个元素的操作:
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
slice = slice[:2]
fmt.Println(slice)
}
也是取slice切片中的前两个元素,然后赋值给原来的slice切片。
切片的复制:
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//切片的复制
sliceCopy := slice
//或者也可以这样复制
sliceCopy2 := slice[:]
fmt.Println(sliceCopy)
fmt.Println(sliceCopy2)
}
运行结果:
使用copy函数进行切片的拷贝
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//使用copy函数进行切片的拷贝
var sliceThree = make([]string, len(slice))
copy(sliceThree, slice)
fmt.Println(sliceThree)
}
运行结果:
注意:
package main
import "fmt"
/*
* 第二周:1.8节 切片元素的删除和拷贝
*/
func main() {
var slice []string
slice = append(slice, "张三", "李四", "王五")
//切片的复制
//sliceCopy := slice
//或者也可以这样复制
sliceCopy2 := slice[:]
//fmt.Println(sliceCopy)
fmt.Println(sliceCopy2)
//使用copy函数进行切片的拷贝
var sliceThree = make([]string, len(slice))
copy(sliceThree, slice)
fmt.Println(sliceThree)
//使用sliceCopy := slice 和 sliceCopy2 := slice[:] 的方式拷贝切片,当拷贝后修改被拷贝的切片数据,拷贝出来的切片数据也会受到影响。
//使用copy() 函数的方式拷贝切片,当拷贝后修改被拷贝的切片数据,拷贝出来的切片数据不会会受到影响。
fmt.Println("-----------------------------------")
slice[0] = "赵四"
fmt.Println(sliceCopy2)
fmt.Println(sliceThree)
}
运行结果:
1.9、map的初始化和赋值
map是一个key(索引)和value(值)的无序集合,主要是查询方便。查询的时间复杂度是O(1)
map定义:var mapName map[keyType]valueType
map初始化:
package main
import "fmt"
/*
* map的初始化和赋值
*/
func main() {
//map是一个key(索引)和value(值)的无序集合,主要是查询方便。查询的时间复杂度是O(1)
//map定义:var mapName map[keyType]valueType
//var userInfoMap map[string]string
//map初始化:var mapName map[keyType]valueType
var userInfoMap = map[string]string{
"张三": "13",
"李四": "14",
"王五": "15",
"牛七": "16",
}
fmt.Println(userInfoMap)
}
//map取值
fmt.Println(userInfoMap["张三"])
//map放值
userInfoMap["马八"] = "17"
fmt.Println(userInfoMap)
//如果只定义了map,定义方式:var userInfoMap map[string]string
//但是没有初始化赋值时,直接向map中放值时会报错。
var userInfoMap map[string]string
userInfoMap["马八"] = "17"
fmt.Println(userInfoMap)
所以,map类型想要设置值必须要先初始化。初始化方式:
var userInfoMap map[string]string{}
make是内置函数,主要用于初始化slice、map、channel。
var userInfoMap = make(map[string]string, 3)
实际开发中,使用make函数初始化的方式更常用。
但是slice可以不初始化。
1.10、map进行for循环遍历
package main
import "fmt"
/*
* map进行for range循环遍历
*/
func main() {
var userInfoMap = map[string]string{
"张三": "13",
"李四": "14",
"王五": "15",
"牛七": "16",
}
//遍历key 和 value
//for key, value := range userInfoMap {
// fmt.Println(key, value)
//}
//遍历value
//for _, value := range userInfoMap {
// fmt.Println(value)
//}
//遍历key
for key := range userInfoMap {
fmt.Println(key, userInfoMap[key])
}
//map的遍历是无序的,而且不能保证每次打印都是相同的顺序。
}
1.11、判断map中是否存在元素和删除元素
package main
import "fmt"
/*
* 判断map中是否存在元素和删除元素
*/
func main() {
var userInfoMap = map[string]string{
"张三": "13",
"李四": "14",
"王五": "15",
"牛七": "16",
}
//判断张三是不是在userInfoMap中
_, exist := userInfoMap["张三"]
if exist {
fmt.Println("find")
} else {
fmt.Println("not in")
}
}
运行结果:
删除map中的元素
package main
import "fmt"
/*
* 判断map中是否存在元素和删除元素
*/
func main() {
var userInfoMap = map[string]string{
"张三": "13",
"李四": "14",
"王五": "15",
"牛七": "16",
}
//删除map中的元素
delete(userInfoMap, "张三")
//删除map中不存在的元素是不会报错的
delete(userInfoMap, "马八")
fmt.Println(userInfoMap)
}
运行结果:
很重要的提示,map不是线程安全的,在go语言中,多个协程时建议使用sync.Map
1.12、list和slice的区别
1.13、list的基本用法
package main
import (
"container/list"
"fmt"
)
/*
* list的基本用法
*/
func main() {
//list的初始化方式一
//var myList list.List
//list的初始化方式二
myList := list.New()
//向list的尾部添加元素
myList.PushBack("张三")
myList.PushBack("李四")
myList.PushBack("王五")
//向list的头部添加元素
myList.PushFront("王小二")
//在list中某个元素前插入元素 例如在张三前插入赵四,插入之后的数据正序排列是这样的:王小二 赵四 张三 李四 王五
i := myList.Front()
for ; i != nil; i = i.Next() {
if i.Value.(string) == "张三" {
break
}
}
myList.InsertBefore("赵四", i)
//删除list中的王五
j := myList.Front()
for ; j != nil; j = j.Next() {
if j.Value.(string) == "王五" {
break
}
}
myList.Remove(j)
//遍历打印list的值 正序遍历
for i := myList.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
fmt.Println("----------------------------------")
//遍历打印list的值 反向遍历
for i := myList.Back(); i != nil; i = i.Prev() {
fmt.Println(i.Value)
}
}
2、函数
2.1、函数的定义
package main
import "fmt"
/*
* 函数的定义
*/
func main() {
//go函数支持普通函数、匿名函数、闭包
//go中的函数是"一等公民":1、函数本身可以当做变量 2、函数包括 普通函数、匿名函数、闭包 3、函数可以满足接口
a := 1
b := 2
sum, _ := add(a, b)
fmt.Println(sum)
}
// 函数格式:
// func关键字 函数名称(多个入参 入参类型) (多个返回参数类型) {}
func add(a, b int) (int, error) {
a = 3
return a + b, nil
}
2.2、函数的可变参数
package main
import (
"fmt"
"time"
)
/*
* 函数的可变参数
*/
func main() {
//sum, _ := sum(1, 2)
//fmt.Println(sum)
sum, _ := sumInt("必传的描述入参", 1, 2, 3, 4, 5)
fmt.Println(sum)
}
// 函数可以返回单个类型值、多个类型值或不返回任何值
func runForever() {
for {
time.Sleep(time.Second)
fmt.Println("doing...")
}
}
// 函数在返回里面定义变量值
func sum(a, b int) (sum int, err error) {
sum = a + b
return sum, err
}
// 定义可变个数的int入参的函数,计算所有int入参的和
func sumInt(desc string, items ...int) (sum int, err error) {
for _, value := range items {
sum += value
}
return sum, err
}
2.3、函数一等公民
package main
import "fmt"
/*
* 函数一等公民特性
*/
func main() {
//go函数支持普通函数、匿名函数、闭包
//go中的函数是"一等公民":1、函数本身可以当做变量 2、函数包括 普通函数、匿名函数、闭包 3、函数可以满足接口
//将定义的sumChangeableInt函数传给某一个变量。注意:右边直接写函数名就行,不能写括号,写括号代表调用函数。
funcVar := sumChangeableInt
//调用函数
a := 1
b := 2
sum, _ := funcVar(a, b, 3, 4)
fmt.Println(sum)
}
// 定义可变个数的int入参的函数,计算所有int入参的和
func sumChangeableInt(items ...int) (sum int, err error) {
for _, value := range items {
sum += value
}
return sum, err
}
运行结果:
定义一个函数,入参类型是string,返回类型是func的函数类型
package main
import "fmt"
/*
* 函数一等公民特性
*/
func main() {
//go函数支持普通函数、匿名函数、闭包
//go中的函数是"一等公民":1、函数本身可以当做变量 2、函数包括 普通函数、匿名函数、闭包 3、函数可以满足接口
//调用calculate函数
calculate("*")()
}
// 定义一个函数,入参类型是string,返回类型是func的函数类型
func calculate(operation string) func() {
switch operation {
case "+":
return func() {
fmt.Println("这是加法")
}
case "-":
return func() {
fmt.Println("这是减法")
}
case "*":
return func() {
fmt.Println("这是乘法")
}
case "/":
return func() {
fmt.Println("这是除法")
}
default:
return func() {
fmt.Println("未知的运算符")
}
}
}
定义一个函数,func函数入参,int类型返回值 (一个函数的参数也是函数,函数内部的代码是由我传递进去的。这样的写法在实际开发中也很常用。)
package main
import "fmt"
/*
* 函数一等公民特性
*/
func main() {
//go函数支持普通函数、匿名函数、闭包
//go中的函数是"一等公民":1、函数本身可以当做变量 2、函数包括 普通函数、匿名函数、闭包 3、函数可以满足接口
//调用calculateTwo函数
sum := calculateTwo(func(items ...int) int {
sum := 0
for _, value := range items {
sum += value
}
return sum
})
fmt.Println(sum)
}
// 定义一个函数,func函数入参,int类型返回值 (一个函数的参数也是函数,函数内部的代码是由我传递进去的。这样的写法在实际开发中也很常用。)
func calculateTwo(myFunction func(items ...int) int) int {
return myFunction()
}
package main
import "fmt"
/*
* 函数一等公民特性
*/
func main() {
//go函数支持普通函数、匿名函数、闭包
//go中的函数是"一等公民":1、函数本身可以当做变量 2、函数包括 普通函数、匿名函数、闭包 3、函数可以满足接口
//调用calculateTwo函数,func(items ...int)是匿名函数。
//sum := calculateTwo(func(items ...int) int {
// sum := 0
// for _, value := range items {
// sum += value
// }
// return sum
//
//})
//或者将匿名变量赋值给一个变量,然后通过变量调用函数。
localFunction := func(items ...int) int {
sum := 0
for _, value := range items {
sum += value
}
return sum
}
sum := calculateTwo(localFunction)
fmt.Println(sum)
}
// 定义一个函数,func函数入参,int类型返回值 (一个函数的参数也是函数,函数内部的代码是由我传递进去的。这样的写法在实际开发中也很常用。)
func calculateTwo(myFunction func(items ...int) int) int {
return myFunction(1, 2, 3)
}
2.4、go函数的闭包特性
现在有个需求:希望有个函数,每次调用一次返回的结果都是增加一次之后的值。
我们首先想到的是用普通的函数定义一个全局变量
package main
import "fmt"
/*
* go函数的闭包特性
*/
func main() {
//现在有个需求:希望有个函数,每次调用一次返回的结果都是增加一次之后的值。
//我们首先想到的是用普通的函数定义一个全局变量
for i := 0; i < 5; i++ {
fmt.Println(autoIncrement())
}
}
var global int
func autoIncrement() int {
global += 1
return global
}
运行结果:
每次循环调用之后都会加1,这样好像会解决我们的问题。
但是autoIncrement()这个函数为了实现这个需求被迫的声明了一个全局变量,而且调用之后我想再次从0开始,就做不到了,因为全局变量已经被我们改了,只能继续往上加,不能再次置0。
有没有什么办法,不用多声明1个全局变量,同时还能每次调用函数自动加1,而且还能置0呢?这就要用到闭包的特性了。
package main
import "fmt"
/*
* go函数的闭包特性
*/
func main() {
//现在有个需求:希望有个函数,每次调用一次返回的结果都是增加一次之后的值。
//我们首先想到的是用普通的函数定义一个全局变量
nextFunc := closure()
for i := 0; i < 5; i++ {
fmt.Println(nextFunc())
}
fmt.Println("------------再次访问闭包函数变量重新置0-----------------")
nextFunc2 := closure()
for i := 0; i < 10; i++ {
fmt.Println(nextFunc2())
}
}
// 闭包(函数里面定义匿名函数)
func closure() func() int {
local := 0
return func() int {
//在闭包的匿名函数中,可以访问另一个函数的局部变量。注意:除了闭包的情况,一个函数中是不能访问另一个函数中的局部变量。
local += 1
return local
}
}
运行结果:
2.5、defer的应用场景
defer类似于java和python中的finally,实际开发中,我们经常连接一些资源(连接数据库、打开文件、开始锁),我们希望这些资源无论程序是否正常运行到最后,都要关闭这些资源(关闭数据库、关闭文件、解锁)。
package main
import "fmt"
/*
* defer的应用场景
*/
func main() {
/*
* defer类似于java和python中的finally,实际开发中,我们经常连接一些资源(连接数据库、打开文件、开始锁),我们希望这些资源
* 无论程序是否正常运行到最后,都要关闭这些资源(关闭数据库、关闭文件、解锁)。
*
*/
var lock sync.Mutex
lock.Lock()
defer lock.Unlock()
//defer后面的代码是会放在函数return之前执行的。
//go语言的defer相比于java的try-finally设计的更巧妙一些。
//因为try-finally需要括号的嵌套,而且假如在try里面的第10行代码开启的资源,
//但是try里面写了500行代码,最后可能在finally里面就忘了关闭第10行开启的资源了。
return
}
在代码中如果有多个defer,那么执行顺序是什么样的?
package main
import "fmt"
/*
* defer的应用场景
*/
func main() {
/*
* defer类似于java和python中的finally,实际开发中,我们经常连接一些资源(连接数据库、打开文件、开始锁),我们希望这些资源
* 无论程序是否正常运行到最后,都要关闭这些资源(关闭数据库、关闭文件、解锁)。
*
*/
//在代码中如果有多个defer,那么执行顺序是什么样的?
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("main")
return
}
运行结果:
通过代码运行结果可以看出,mian最先出输出,三个defer中,先声明的defer会进入栈底,导致后输出。所以最终的打印顺序就是:
main
3
2
1
假如我们定义了1个函数:
func deferReturn() (ret int) {
defer func() {
ret++
}()
return 10
}
package main
import "fmt"
/*
* defer的应用场景
*/
func main() {
ret := deferReturn()
fmt.Printf("ret = %d\r\n", ret)
return
}
func deferReturn() (ret int) {
defer func() {
ret++
}()
return 10
}
运行结果:
所以,defer函数是可以修改变量的返回值的。这个需要注意。
2.6、go的error设计理念
go语言错误处理的理念:一个函数可能出错,在其他语言中,使用try catch 去包住这个函数。
go语言认为没有必要去设计一个try catch,如果函数出错了,会返回一个error类型的值。然后通过error值是否等于nil来判断函数执行是否成功。go设计者要求我们必须要处理这个error,代码中大量出现 if error != nil。这属于防御性编程,虽然写代码的人觉得很啰嗦,但是代码的健壮性很好。
代码演示:
package main
import (
"errors"
"fmt"
)
/*
* go的error设计理念
*/
func main() {
//error(值) panic(函数) recovery(函数)
//go语言错误处理的理念:一个函数可能出错,在其他语言中,使用try catch 去包住这个函数。
//go语言认为没有必要去设计一个try catch,如果函数出错了,会返回一个error类型的值。然后通过error值是否等于nil来判断函数执行是否成功。
//go设计者要求我们必须要处理这个error,代码中大量出现 if error != nil。这属于防御性编程,虽然写代码的人觉得很啰嗦,但是代码的健壮性很好。
_, error := returnError()
if error != nil {
fmt.Println(error)
}
}
// 定义返回error的函数
func returnError() (int, error) {
return 0, errors.New("this is an error")
}
运行结果:
2.7、如何正确使用recover和panic
package main
import "fmt"
/*
* 如何正确使用recover和panic
*/
func main() {
//panic这个函数会导致你的程序退出,有点像java中的抛异常。但是在go语言中不推荐随便使用panic,使用的时候要非常小心。
//但是在特定情况下是可以使用panic函数的(比如服务启动过程中,必须有些依赖服务准备好,比如:日志文件存在、mysql能连接通、配置文件没问题,
//这个时候服务才能启动。如果启动检查中发现任何一个不满足就主动调用panic函数使程序退出。)
panic("this is a panic")
fmt.Println("这行不到了")
}
运行结果:
可以看到打印出错误信息。
recover这个函数能捕获到panic。
package main
import (
"errors"
"fmt"
)
/*
* 如何正确使用recover和panic
*/
func main() {
//recover这个函数能捕获到panic
recoverFunction()
}
// 定义返回error的函数
func recoverFunction() (int, error) {
//如果map定义之后没有初始化就直接使用,会panic出来异常信息。这时如果不想出现panic使程序结束,就要用到recover()函数了。
defer func() {
if r := recover(); r != nil {
fmt.Println("recoverd if A:", r)
}
}()
var names map[string]string
names["go"] = "go语言程序设计"
return 0, errors.New("this is an error")
}
运行结果:
3、结构体
3.1、type关键字的用法
type关键字常用功能:
1、定义类型别名,别名是为了更好的使用代码。
2、自定义类型。基于已有的类型自定义一个类型
3、类型判断
4、定义结构体
5、定义接口
类型别名
package main
import "fmt"
/*
* type关键字的用法
*/
func main() {
//type关键字
/*
* type常用功能:
* 1、定义类型别名,别名是为了更好的使用代码。
* 2、定义接口
* 3、定义结构体
* 4、
*
*/
//类型别名
type MyInt = int //类型别名
var i MyInt = 12
var j MyInt = 8
fmt.Println(i + j) //在编译的时候MyInt类型别名会被替换为int
fmt.Printf("%T\r\n", i) // i的类型是int
}
运行结果:
自定义类型
package main
import (
"fmt"
"strconv"
)
/*
* type关键字的用法
*/
type MyDefinedInt int
// 在int的基础上,为MyDefinedInt添加数字转换为字符串的toString()函数
func (m MyDefinedInt) toString() string {
return strconv.Itoa(int(m))
}
func main() {
//自定义类型
var m MyDefinedInt = 12
fmt.Println(m.toString())
var n int = 8
fmt.Println(int(m) + n) //自定义的 MyDefinedInt 类型值和 int 类型值相加时,要将 MyDefinedInt 转换为 int 类型才可以。
fmt.Printf("%T\r\n", m) // m的类型是MyDefinedInt
}
运行结果:
类型判断
package main
import "fmt"
/*
* type关键字的用法
*/
func main() {
//类型判断
var a interface{} = "abc"
switch a.(type) {
case string:
fmt.Println("字符串")
}
}
运行结果:
type定义结构体在3.2节中继续说明。
3.2、结构体定义和初始化
package main
/*
* 结构体定义和初始化
*/
func main() {
//如何初始化结构体的2种方式
//方式一:
person1 := Person{
"张三",
18,
"山东省",
1.80,
}
person2 := Person{
name: "李四",
age: 19,
address: "山东省",
height: 1.90,
}
var persons []Person
persons = append(persons, person1)
persons = append(persons, person2)
//还可以这样向persons切片里面放数据
persons = append(persons, Person{
"王五",
20,
"山东省",
1.85,
})
//甚至还可以这样向persons切片里面放数据
persons2 := []Person{
{
"张三",
18,
"山东省",
1.80,
},
{
name: "赵四",
},
{
age: 19,
},
}
persons2 = append(persons2, Person{
"张三",
18,
"山东省",
1.80,
})
}
// 结构体定义
type Person struct {
name string
age int
address string
height float32
}
3.3、匿名结构体
有的时候只是希望临时的定义一个结构体,并不是希望在全局里面使用这个结构体。这时就可以定义匿名结构体。
package main
import "fmt"
/*
* 匿名结构体
*/
func main() {
//匿名结构体(有的时候只是希望临时的定义一个结构体,并不是希望在全局里面使用这个结构体。这时就可以定义匿名结构体。)
address := struct {
province string
city string
address string
}{
"山东省",
"青岛市",
"李沧区",
}
fmt.Println(address.city)
}
运行结果:
3.4、结构体嵌套
package main
import "fmt"
/*
* 结构体嵌套
*/
func main() {
//嵌套结构体赋值
s := student{
Person{
"张三",
18,
"山东省",
1.80,
},
95.80,
}
//嵌套结构体取值
fmt.Println(s.person.age)
fmt.Println(s.score)
//嵌套结构体赋值方式2
s2 := student{}
s2.person.name = "赵四"
fmt.Println(s2.person.name)
//但是这样嵌套结构体时,取值和赋值并不方便。可以使用匿名嵌套来优化嵌套结构体的取值和赋值。
s3 := anonymityStudent{
Person{
"张三",
18,
"山东省",
1.80,
},
95.80,
"赵四",
}
//匿名嵌套结构体赋值和取值
//赋值
s3.age = 20
//取值
//匿名嵌套结构体anonymityStudent中,嵌套的Person里面定义了name,而且anonymityStudent里面也定了name。取值时anonymityStudent的name优先级更高。
fmt.Println(s3.name)//打印 赵四
}
// 结构体定义
type Person struct {
name string
age int
address string
height float32
}
// 结构体student定义,里面嵌套Person结构体。这样student结构体里面就不用重复定义Person里面的name,age,address,height属性了。
type student struct {
person Person
score float32
}
// 匿名嵌套结构体定义,嵌套的Person里面定义了name,而且anonymityStudent里面也定了name。取值时anonymityStudent的name优先级更高。
type anonymityStudent struct {
Person
score float32
name string
}
3.5、结构体定义方法
package main
import "fmt"
/*
* 结构体定义方法
* 语法:func (s StructType) funcName(param1 param1Type, ...) (returnValue1 returnValue1Type, ...) {}
*
*/
func main() {
//调用结构体的方法
p := Person3{
"赵四",
18,
}
p.print()
}
// 定义Person3结构体的打印方法
func (p Person3) print() {
fmt.Printf("name:%s, age:%d", p.name, p.age)
}
// 结构体定义
type Person3 struct {
name string
age int
}
运行结果:
package main
import "fmt"
/*
* 第2周:容器,go编程思想
* 第4章 指针 4-1 指针的定义和使用
*/
type Person struct {
name string
}
func changeName(p Person) {
p.name = "小强"
}
func changeNameUsePointer(p *Person) {
p.name = "小强"
}
func main() {
//指针,提一个需求,我希望结构体传值的时候 我在函数中修改的值可以反应到变量中
p := Person{
name: "旺财",
}
changeName(p)
fmt.Println(p.name) // 这块打印的是 旺财
changeNameUsePointer(&p)
fmt.Println(p.name) // 这块打印的是 小强
var pi *Person = &p
fmt.Printf("%p\r\n", pi)
//指针的定义
//var po *int
//var po *float32
var po *Person
//指针赋值
po = &p
fmt.Println(po)
//指针定义的时候同时进行初始化
person := &Person{
name: "旺财",
}
fmt.Println(person)
fmt.Println(person.name)
}
跳转链接:
上一篇:一、Go基础知识入门
下一篇: