二、go语言数组、切片、map、list

go语言提供了哪些集合类型的数据结构?

  • 数组
  • 切片(slice)
  • map
  • list

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

         运行结果:

二、go语言数组、切片、map、list_第1张图片

        数组的遍历:

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

}

        运行结果:

二、go语言数组、切片、map、list_第2张图片

1.2、数组的初始化

package main

import "fmt"

/*
 * 第二周:1.2节 数组的初始化
 */
func main() {
	//数组的初始化
	//var array1 = [3]string{"张三", "李四", "王五"}
    //还可以用下面的简写方式初始化数组
	array1 := [3]string{"张三", "李四", "王五"}
	for _, value := range array1 {
		fmt.Println(value)
	}
}

        运行结果:

二、go语言数组、切片、map、list_第3张图片

        另外,还有一种初始化方式,假如我想将数组的第三个位置的元素初始化为"李四",其他位置的元素保持默认,可以这样初始化:

package main

import "fmt"

/*
 * 第二周:1.2节 数组的初始化
 */
func main() {
	//数组的初始化
	array1 := [3]string{2: "李四"}
	for _, value := range array1 {
		fmt.Println(value)
	}
}

        debug运行结果如下:

二、go语言数组、切片、map、list_第4张图片

 二、go语言数组、切片、map、list_第5张图片

         string类型的默认值是空字符串,可以看到运行结果和我们预期的一致。

运行结果:

二、go语言数组、切片、map、list_第6张图片

        如果数组初始化时我们不想指定数组的长度,如果我们往数组里面放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运行结果如下:

二、go语言数组、切片、map、list_第7张图片

         可以看到数组的长度为3。

        然后,我们再向数组里面放2个元素:

package main

import "fmt"

/*
 * 第二周:1.2节 数组的初始化
 */
func main() {
	//数组的初始化
	//如果数组初始化时我们不想指定数组的长度,如果我们往数组里面放2个元素,数组的长度就为2;如果我们往数组里面放3个元素,数组的长度就为3。那么可以这样初始化数组:
	array1 := [...]string{"张三", "李四"}
	for _, value := range array1 {
		fmt.Println(value)
	}
}

         debug运行结果如下:

二、go语言数组、切片、map、list_第8张图片

          可以看到数组的长度为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")
	}
}

        数组比较的时候,会比较两个数组的每一个元素是否相等。

        运行结果:

二、go语言数组、切片、map、list_第9张图片

        如果将 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")
	}
}

        运行结果:

二、go语言数组、切片、map、list_第10张图片

        如果将 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")
	}
}

        运行结果:

二、go语言数组、切片、map、list_第11张图片

多维数组的定义:

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

        运行结果:

二、go语言数组、切片、map、list_第12张图片

        还有更加简洁的多维数组遍历方式(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()
	}
}

         运行结果:

二、go语言数组、切片、map、list_第13张图片

        还可以打印一行

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

}

         运行结果:

二、go语言数组、切片、map、list_第14张图片

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

        运行结果:

二、go语言数组、切片、map、list_第15张图片

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)

}

        运行结果:

二、go语言数组、切片、map、list_第16张图片

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

        运行结果:

二、go语言数组、切片、map、list_第17张图片

        如果想将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)
}

        运行结果:

二、go语言数组、切片、map、list_第18张图片

        除了循环之外,将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切片中。

        运行结果:
二、go语言数组、切片、map、list_第19张图片

         如果我只想将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)
}

        运行结果:

二、go语言数组、切片、map、list_第20张图片

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

        运行结果:

二、go语言数组、切片、map、list_第21张图片

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

}

        运行结果:

二、go语言数组、切片、map、list_第22张图片

注意:

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)

}

        运行结果:

二、go语言数组、切片、map、list_第23张图片

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

运行结果:

二、go语言数组、切片、map、list_第24张图片

 删除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)
}

运行结果:

二、go语言数组、切片、map、list_第25张图片

        很重要的提示,map不是线程安全的,在go语言中,多个协程时建议使用sync.Map

1.12、list和slice的区别

  1. list是一个链表,slice是动态数组,在slice中不停地添加元素的过程中,slice会不停地扩容,并进行数据的拷贝。
  2. slice要求一定要是连续的存储空间,如果内容剩余空间不足,是无法分配对应大小的slice的。list的空间不连续。
  3. slice查询的时间复杂度是O(1),list查询的时间复杂度是O(n)。
  4. list的插入和删除元素的时间复杂度是O(1),slice的插入和删除元素的时间复杂度是O(n)。

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
}

运行结果:

二、go语言数组、切片、map、list_第26张图片

        定义一个函数,入参类型是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
}

运行结果:

二、go语言数组、切片、map、list_第27张图片

         每次循环调用之后都会加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
	}
}

运行结果:

二、go语言数组、切片、map、list_第28张图片

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
}

运行结果:

二、go语言数组、切片、map、list_第29张图片

         通过代码运行结果可以看出,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
}

        运行结果:

二、go语言数组、切片、map、list_第30张图片

        所以,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")
}

运行结果:

二、go语言数组、切片、map、list_第31张图片

 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("这行不到了")
}

运行结果:

二、go语言数组、切片、map、list_第32张图片

        可以看到打印出错误信息。

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

运行结果:

二、go语言数组、切片、map、list_第33张图片

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
}

运行结果:

二、go语言数组、切片、map、list_第34张图片

自定义类型

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
}

运行结果:

二、go语言数组、切片、map、list_第35张图片

类型判断

package main

import "fmt"

/*
 * type关键字的用法
 */
func main() {

	//类型判断
	var a interface{} = "abc"
	switch a.(type) {
	case string:
		fmt.Println("字符串")
	}
}

运行结果:

二、go语言数组、切片、map、list_第36张图片

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

运行结果:

二、go语言数组、切片、map、list_第37张图片

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
}

运行结果:

4.1、指针的定义和使用

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基础知识入门

下一篇:

你可能感兴趣的:(go语言学习,go语言学习)