Golang项目的组织管理

1、说明

Go Module模式

2、VSCode配置Go项目

GOPROXY

在控制终端输入下面命令即可配置

go env -w GOPROXY=https://goproxy.cn,direct
//默认是GOPROXY=https://proxy.golang.org,direct,但是国内访问不到
go module模式project1go mod init project1go.modmain.go
project2go mod init project2先在project1模块的go.mod中配置

Golang中的函数、包和错误处理

1、函数

基本语法
package main

import (
	"fmt"
)

// 参数列表:不能有var关键字。此外,go的函数没有默认参数,也就是不能在参数列表中给参数赋值
/*
 返回值列表: 如果没有返回就不写返回值类型,也不用加括号;
			如果有一个返回值可以加括号也可以不加;
			如果有多个返回值,就必须要加括号
*/
//接受返回值的时候,如果有不需要的返回值可以使用 _ 表示忽略这个返回值。
func cal(n1 float64, n2 float64) (float64, float64) { 
	fmt.Println(n1, n2)
	return n2, n1
}

func main() {

	fmt.Println(cal(1.0, 3.0))
	
}
注意事项
可以是值类型和引用类型首字母大写该函数可以被本包文件和其它包文件使用基本数据类型和数组默认都是值传递的可以传入变量的地址&,函数内以指针的方式操作变量
package main

import (
	"fmt"
)

func getSum(v1 int, v2 int) int {

	return v1 + v2
}

// 为了简化数据类型定义,Go 支持自定义数据类型,通过关键字type来实现
// 比如 type myInt int; 这样就可以使用myInt来替代int
// 对于函数类型也是一样,一般是在类型比较冗长的情况下简化数据类型定义
// 函数类型在使用type的时候,只需要fanc关键字、参数列表、返回值列表即可,不能写函数名
// 重新定义的是一种函数类型,通过查看函数类型%T就可以理解为什么不加函数名
type funSum func(int, int) int

// 这里的第一个参数就可以直接使用funSum类型
func mySum(fun funSum, v1 int, v2 int) int {

	return fun(v1, v2)

}

func main() {

	fmt.Println(mySum(getSum, 1, 2))  // 3
}

package main

import (
	"fmt"
)

// 计算两数相加和相减
/*
	函数支持返回值命名,可以在函数中直接使用返回值名称
	如果有返回值名称,可以使用也可以不使用,如果不使用,在return的时候就必须返回结果
*/
func getSumSub(v1 int, v2 int) (sum int, sub int) {

	// 直接用返回值变量名来接收返回结果
	sum = v1 + v2
	sub = v1 - v2

	// var sum string = ""  错误,不能重名

	return		//这里必须要写return
}

func main() {

	sum, sub := getSumSub(12, 16)
	fmt.Println("sum = ", sum, "  sub = ", sub) // 28  -4
}

package main

import (
	"fmt"
)

/*
函数支持可变参数,可变参数是一个切片,可以通过索引得到每一个元素
要求可变参数在参数列表的最后,否则会出错,所以只能有一个可变参数
*/
func getSum(n1 float32, args ...int) int {

	sum := int(n1)
	for i := 0; i < len(args); i++ {
		sum += args[i]
	}

	return sum
}

func main() {

	sum := getSum(10, 11, 12, 12)
	fmt.Println("sum = ", sum)
}

2、init函数

基本介绍每一个源文件都可以包含一个 init 函数init 会在 main 函数前被调用
注意事项和细节
全局变量定义->init函数->main 函数
package main

import (
	"fmt"
)

var glo = test()

func test() int {
	fmt.Println("test...")
	return 90
}

func init() {

	fmt.Println("init... ", glo)
}

func main() {

	fmt.Println("main...", glo)
}

在这里插入图片描述
2. 当导入了其他包,里面的源文件也有init函数和全局变量,他们的执行顺序如下:
在这里插入图片描述
在这里插入图片描述

3、匿名函数

基本介绍匿名函数就是没有名字的函数
两种使用方式
package main

import (
	"fmt"
)

func main() {

	// 匿名函数

	// 1、在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
	result := func(val int, vals ...int) int {
		sum := val
		for i := 0; i < len(vals); i++ {
			sum += vals[i]
		}
		return sum
	}(10, 20)

	fmt.Println(result)  // 30

	// 2、将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
	funvar := func(val int, vals ...int) int {
		sum := val
		for i := 0; i < len(vals); i++ {
			sum += vals[i]
		}
		return sum
	}
	result = funvar(1, 2, 3, 4, 5, 6, 7, 8, 9)

	fmt.Println(result)  // 45
}
全局匿名函数将匿名函数赋给一个全局变量
package main

import (
	"fmt"
)

var (
	// 将匿名函数赋值给全局变量
	funvar = func(val int, vals ...int) int {

		sum := val
		for i := 0; i < len(vals); i++ {
			sum += vals[i]
		}
		return sum
	}
)

func main() {

	// 通过全局变量调用匿名函数
	result := funvar(1, 2, 3)
	fmt.Println(result)
}

4、闭包

基本介绍一个函数和与其相关的引用环境组合
匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一个整体分析出返回的函数它使用(引用)到哪些变量最佳实践
package main

import (
	"fmt"
	"strings"
)

func makeSuffix(suffix string) func(string) string {

	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {

	f := makeSuffix(".jpg")

	fmt.Println(f("weater"))
	fmt.Println(f("spring.png"))
	fmt.Println(f("author.jpg"))

}

在这里插入图片描述
说明:

  1. 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用到 suffix 这个变量
  2. 如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。

5、函数的defer(延迟机制)

基本介绍注意事项和细节
最佳实践最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源

6、函数参数的传递方式

基本介绍值传递和引用传递值传递的是值的拷贝,引用传递的是地址的拷贝地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低

*** 值类型和引用类型
在这里插入图片描述

使用特点

7、变量的作用域

  1. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部。
  2. 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。
  3. 如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块。
    在这里插入图片描述

8、字符串常用函数

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {

	// 1、len(str) 统计字符串的长度,单位是子节数
	// 英文数字一个子节,中文三个字节
	var str = "Hello,世界"
	fmt.Println(len(str)) // 12个子节

	// 2、字符串遍历,如果有中文,不能直接遍历字符串,需要先切片 r := []rune(str)
	str2 := "HELLO北京"
	r := []rune(str2)
	for i := 0; i < len(r); i++ {
		fmt.Printf("%c", r[i]) // 格式化输出字符
	}
	fmt.Println()

	// 3、字符串转整数: n, err := strconv.Atoi("12")
	n, err := strconv.Atoi("12")
	if err != nil {
		fmt.Println("转换整数错误 ", err)
	} else {
		fmt.Println("转换成整数的结果 ", n)
	}

	// 4、整数转换成字符串: str = strconv.Itoa(n)
	str3 := strconv.Itoa(123)
	fmt.Printf("%T, %v\n", str3, str3)

	// 5、字符串转 []byte: var bytes = []byte("hello")
	var bytes = []byte("hello北京")
	fmt.Printf("bytes = %v\n", bytes)

	// 6、[]byte 转 字符串: str = string([]byte{97, 98, 99})
	str4 := string([]byte{97, 98, 99})
	fmt.Println(str4)

	// 7、10进制 转2、8、16进制,并且返回字符串: str = strconv.FormatInt(123, 2)
	str5 := strconv.FormatInt(123, 2)
	fmt.Printf("123对应的二进制是 = %v\n", str5)
	str5 = strconv.FormatInt(123, 16)
	fmt.Printf("123对应的十六进制是 = %v\n", str5)

	// 8、查找子串是否在指定的字符串中: strings.Contains("main string", "small string")
	b := strings.Contains("HELLO, WORLD", "world")
	fmt.Println("是否包含在主串中: ", b) // false
	b = strings.Contains("HELLO, WORLD", "HELLO")
	fmt.Println("是否包含在主串中: ", b) // true

	// 9、统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
	counts := strings.Count("HELLO", "L")
	fmt.Printf("num = %v\n", counts) // 2

	// 10、不区分大小写的字符串比较(== 是区分字母大小写的 ): fmt.Println(strings.EqualFold("abc","Abc")) // true
	b = strings.EqualFold("abc", "ABC")       // 不区分大小写比较字符串
	fmt.Println("abc == ABC ", b)             // true
	fmt.Println("abc == ABC", "abc" == "ABC") // false

	// 11、返回子串在字符串第一次出现的index值,如果没有返回-1:  strings.Index("NLT_abc", "abc") // 4
	index := strings.Index("NLP_世界", "界") // 按照子节来取索引
	fmt.Println(index)                    // 7

	// 12、返回子串在字符串最后一次出现的 index,如没有返回-1 : strings.LastIndex("go golang", "go")
	index = strings.LastIndex("世界你好呀世界", "世界")
	fmt.Println("index = ", index) // 15

	// 13、将指定的子串替换成 另外一个子串: strings.Replace("go go hello", "go", "go 语言", n) n 可以指定你希望替换几个,如果 n=-1 表示全部替换
	str6 := "golang clang java python clang java golang golang"
	str7 := strings.Replace(str6, "golang", "2009go", 2) //不会影响到str6字符串
	fmt.Println(str6)                                    // golang clang java python clang java golang golang
	fmt.Println(str7)                                    // 2009go clang java python clang java 2009go golang

	// 14、按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组
	strArr := strings.Split("Hello,world,kiko,yoyo", ",")
	for i := 0; i < len(strArr); i++ {
		// str[0] = Hello  str[1] = world  str[2] = kiko   str[3] = yoyo
		fmt.Printf("str[%v] = %v\t", i, strArr[i])
	}
	fmt.Printf("\nstrArr Type = %T\n", strArr) // strArr Type = []string

	// 15、将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
	str8 := "golang Hello"
	str9 := strings.ToLower(str8)
	str10 := strings.ToUpper(str8)
	fmt.Println(str9)  // golang hello
	fmt.Println(str10) // GOLANG HELLO

	// 16、将字符串左右两边的空格去掉:strings.TrimSpace(" tn a lone gopher ntrn ")
	str11 := "  tn a lone gopher ntrn  "
	fmt.Println(str11)
	str12 := strings.TrimSpace(str11)
	fmt.Println(str12)

	// 17、将字符串左右两边指定的字符去掉: strings.Trim("! hello! ", " !h") // ["hello"] //将左右两边 !和 " "去掉
	str13 := strings.Trim("! he!llo! ", "h! ") // "h! "表示三个字符,和顺序没有关系
	fmt.Println(str13)                         // e!llo

	// 18、将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !") // ["hello"] //将左边 ! 和 " "去掉
	// 19、将字符串右边指定的字符去掉 :strings.TrimRight("! hello! ", " !") // ["hello"] //将右边 ! 和 " "去掉

	// 20、判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp") // true
	// 21、判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT_abc.jpg", "abc") //false
}

在这里插入图片描述

9、内置函数(常用)

  1. len:用来求长度,比如 string、array、slice、map、channel
  2. new:用来分配内存,主要用来分配值类型,比如 int、float32,struct…返回的是指针。
package main

import (
	"fmt"
)

func main() {

	num1 := 100
	// num1的类型 int, num1的值 = 100, num1的地址 = 0xc000016078
	fmt.Printf("num1的类型 %T, num1的值 = %v, num1的地址 = %v\n", num1, num1, &num1)

	num2 := new(int)
	*num2 = 100
	// num2的类型 *int, num2的值 = 0xc0000160b0, num2的地址 = 0xc00000a030, num2指针指向的值 = 100
	fmt.Printf("num2的类型 %T, num2的值 = %v, num2的地址 = %v, num2指针指向的值 = %v\n", num2, num2, &num2, *num2)
}
  1. make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice。

在这里插入图片描述

10、包(配合首章节)

包的引出
go 是以包的形式来管理文件和项目目录结构的
三大作用
  1. 区分相同名字的函数、变量等标识符
  2. 当程序文件很多时,可以很好的管理项目
  3. 控制函数、变量等访问范围,即作用域
打包和导入
打包基本语法
package 包名

引入包的基本语法
import "包的路径"

*** 包的使用参考前一节的项目组织管理

注意细节和注意事项
文件的包名通常和文件所在的文件夹名一致小写字母函数名的首字母需要大写包名.函数名可以给包名取别名
导入模块中的包使用步骤

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

11、错误处理

错误处理引出
  1. 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
  2. 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可
    以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
  3. 这里引出我们要将的错误处理机制
基本介绍
defer, panic, recoverGo 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
package main

import (
	"fmt"
	"time"
)

func test() {
	// 使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover() // recover是内置函数,可以捕获到异常
		if err != nil {
			fmt.Println("err = ", err)
		}
	}()
	num1 := 10
	num2 := 0
	result := num1 / num2
	fmt.Println("result = ", result)
}

func main() {

	//测试
	test()
	for {
		fmt.Println("test函数之后的代码...")
		time.Sleep(time.Second)
	}

}
错误处理的好处
自定义错误
支持自定义错误errors.New 和 panic 内置函数
  1. errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
  2. panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序。
package main

import (
	"errors"
	"fmt"
)

// 函数去读取配置文件init.conf的信息
// 如果文件名传入不正确,就返回一个自定义错误
func readConf(name string) (err error) {
	if name == "config.ini" {
		return nil
	} else {
		// 返回一个自定义错误
		return errors.New("name is not a valid configuration")
	}
}

func test() {

	defer func() {
		err := recover() // recover是内置函数,可以捕获到异常
		if err != nil {
			fmt.Println("err = ", err)
		}
	}()

	err := readConf("config111.ini")
	if err != nil {
		//如果读取文件错误,就输出错误信息,并终止程序(如果没有上面的defer处理,就会终止程序)
		panic(err)
	}
	fmt.Println("test继续执行")
}

func main() {

	//测试
	test()
	fmt.Println("MAIN...")
}

在这里插入图片描述

Golang中的数组

1、基本介绍

数组类型是值类型

2、数组定义和内存布局

*** 定义

var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30 ...
内存布局

3、数组的使用

数组名[下标索引]
package main

import (
	"fmt"
)

func main() {

	// 从终端输入5个成绩并且输出
	var grades [5]float32
	for i := 0; i < 5; i++ {
		fmt.Printf("请输入第%d个成绩: ", i+1)
		fmt.Scanln(&grades[i])
	}

	// 输出成绩
	for i := 0; i < 5; i++ {
		fmt.Println(grades[i])
	}
}
初始化数组的方式
package main

import (
	"fmt"
)

func main() {

	// 几种初始化数组的方式
	var numArr1 [3]int = [3]int{1, 2, 3}
	fmt.Println("numArr1 ", numArr1)

	var numArr2 = [3]int{1, 2, 3} // 自动类型推导
	fmt.Println("numArr2 ", numArr2)

	// 使用[...]固定写法,可以不指定数组的个数,它的长度按照初始化数值的个数确定或者是使用索引赋值的最大索引确定
	var numArr3 = [...]int{1, 2, 3, 4} //
	fmt.Println("numArr3 ", numArr3, "  len(numArr3) ", len(numArr3))   // 4个元素

	var numArr4 = [...]int{1: 100, 2: 200, 5: 500} // 将指定的索引位置的数值赋值,未赋值的默认值
	fmt.Println("numArr4 ", numArr4, "  len(numArr4) ", len(numArr4))  // 6个元素

	numArr5 := [...]string{1: "kiko", 2: "yoyo"}
	fmt.Println("numArr5 ", numArr5)

	numArr6 := [3]string{"nike", "jack"}
	fmt.Println("numArr6 ", numArr6)
}

在这里插入图片描述

4、数组的遍历方式

for循环和len(arr)配合使用
for-range 结构
package main

import (
	"fmt"
)

func main() {

	// for-range
	heroes := [...]string{"李白", "韩信", "嬴政"}

	for index, value := range heroes {
		fmt.Printf("index = %v, value = %v\t", index, value)
		fmt.Printf("heroes[%d] = %v\n", index, heroes[index])
	}

	for _, value := range heroes {
		fmt.Printf("%v\t", value)
	}
}

在这里插入图片描述

5、数组使用的注意事项和细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。

  2. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。

  3. 数组创建后,如果没有赋值,有默认值(零值)。
    数值类型数组:默认值为 0
    字符串数组: 默认值为 “”
    bool 数组: 默认值为 false

  4. 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组

  5. 数组的下标是从 0 开始的

  6. 数组下标必须在指定范围内使用,否则报 panic:数组越界,比如var arr [5]int 则有效下标为 0-4

  7. Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响,比如通过函数参数传递,然后这个函数体内对数组进行了修改,这不会影响函数外的数组实体。

  8. 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
    在这里插入图片描述

  9. 长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
    在这里插入图片描述

Golang中的切片

1、引入

如果需要一个数组用于保存学生的成绩,但是学生的个数是不确定的,此时数组无法满足需求,切片就是这个解决方案。

2、基本介绍

切片是数组的一个引用切片是引用类型遵守引用传递的机制遍历切片、访问切片的元素和求切片长度 len(slice)都一样可以动态变化数组

3、快速入门

package main

import (
	"fmt"
)

func main() {

	// 演示切片的基本使用

	// 声明初始化一个数组
	var intArr [5]int = [...]int{1, 2, 3, 4, 5}

	// 声明一个切片
	slice := intArr[1:3]
	/*
		intArr[1:3] 表示slice切片引用到intArr这个数组中的索引1到3这个部分
		[1:3] 包左不包右,不能引用数组中不存在的索引
	*/
	fmt.Println("intArr = ", intArr)         // [1 2 3 4 5]
	fmt.Println("slice = ", slice)           // [2 3]
	fmt.Println("slice的元素个数 = ", len(slice)) // [2 3]
	fmt.Println("slice的容量 = ", cap(slice))   // 4

	fmt.Printf("intArr的地址 = %p\n", &intArr)           // 0xc0000126c0
	fmt.Printf("slice的地址 = %p\n", &slice)             //0xc000008078
	fmt.Printf("slice的第一个元素的地址 = %p\n", &slice[0])    //0xc0000126c8
	fmt.Printf("intArr索引为1的元素的地址 = %p\n", &intArr[1]) //0xc0000126c8

	slice[0] = 999
	fmt.Println("slice after change = ", slice) // [999 3]
	fmt.Println("intArr = ", intArr)            // [1 999 3 4 5]
}

4、切片在内存中的形式

以上面的例子为例
在这里插入图片描述分析:

  1. slice是一个引用类型
  2. slice从底层来说,是一个数据结构(struct结构体)
//大致有三个成员组成,一个是指向数组的指针,所以切片是引用类型,它内部是得到了引用数组的指针,通过指针对引用的数组进行操作
//第二个是切片当前的元素的个数
//第三个是容量,表示当前切片能存放的元素的个数,可以继续扩展,有它自己的扩容机制。
type slice struct {
	ptr * [2]int
	len int
	cap int
}

5、切片的使用

方式1
第一种方式是定义一个切片,然后让切片去引用一个已经初始化好了的数组,比如上面的代码。

makevar 切片名 [ ]type = make( []type, len, [cap]) // cap可选cap>=len

在这里插入图片描述对上面代码的小结:

  1. 通过 make 方式创建切片可以指定切片的大小和容量
  2. 如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool =>false]
  3. 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素。所以切片不管使用何种方式创建,底层都是维护着一个数组。

方式3
定义一个切片,直接就指定具体的数组,使用原理类似make的方式。
在这里插入图片描述

区别

6、切片的遍历

和数组一样两种方式都可以。

7、切片的使用的注意事项和细节讨论

  1. 切片初始化时 var slice = arr[startIndex:endIndex]
    说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
  2. 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长.
var slice = arr[0:end]			 可以简写 var slice = arr[:end]
var slice = arr[start:len(arr)]  可以简写: var slice = arr[start:]
var slice = arr[0:len(arr)] 	 可以简写: var slice = arr[:]
cap 是一个内置函数,用于统计切片的容量append 内置函数本质就是对数组扩容创建一下新的数组
两个切片自身的修改不会影响到对方
copy 内置函数完成拷贝拷贝和被拷贝的切片 的数据空间是独立

8、string和slice

string 底层是一个 byte 数组string 是不可变的string --> []byte 或者 []rune --> 修改 --> 重写转成 string

9、二维数组

var 数组名 [大小][大小]类型var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}
var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值..},{初值..}}
var 数组名 [大小][大小]类型 = [...][大小]类型{{初值..},{初值..}}
var 数组名 = [大小][大小]类型{{初值..},{初值..}}
var 数组名 = [...][大小]类型{{初值..},{初值..}}
  1. 二维数组的遍历

在这里插入图片描述

Golang中的map

1、基本介绍

key-value 数据结构

2、声明

基本语法
var map变量名 map[keytype]valuetype
通常 key 为 int 、string类型
通常为: 数字(整数,浮点数),string,map,struct
map声明需要 make分配内存后才能赋值和使用
package main

import (
	"fmt"
)

func main() {

	//声明一个map
	var mmap map[int]string

	//在使用map前,需要先make,make的作用就是给map分配数据空间
	mmap = make(map[int]string, 2)
	mmap[0] = "kiko"
	mmap[1] = "yoyo"
	mmap[2] = "jack"
	mmap[1] = "bing"

	fmt.Println(mmap)

	fmt.Println("-------------------------")
	var mp map[string]string
	mp = make(map[string]string, 2)
	mp["kiko"] = "kiko"
	mp["yoyo"] = "yoyo"
	mp["jerry"] = "jerry"
	mp["jack"] = "jack"

	fmt.Println(mp)
}

在这里插入图片描述

  1. map 在使用前一定要 make
  2. map 的 key 是不能重复,如果重复了,则以最后这个 key-value 为准
  3. map 的 value 是可以相同的.
    在这里插入图片描述

3、初始化使用方式

package main

import (
	"fmt"
)

func main() {

	// 1、第一种使用方式
	// 语法建议将声明和make写在同一行
	var mmp1 map[string]string = make(map[string]string, 2)
	mmp1["no1"] = "李白"
	mmp1["no2"] = "韩信"
	mmp1["no3"] = "诸葛亮"
	mmp1["no1"] = "孙悟空"
	fmt.Println(mmp1)

	// 2、第二种使用方式,和第一种方式一样,主要是使用变量的方式不同
	cities := make(map[string]string)
	cities["n1"] = "北京"
	cities["n2"] = "上海"
	cities["n3"] = "深圳"
	fmt.Println(cities)

	// 3、第三种使用方式
	languages := map[string]string{
		"no1": "Java",
		"no2": "Python",
		"no3": "JavaScript",
		"no4": "Golang", //最后这里也要加上,
	}
	languages["no3"] = "Js"

	fmt.Println(languages)

}

在这里插入图片描述

4、map套map练习理解

package main

import (
	"fmt"
)

func main() {

	// make生成对象, 创建map对象的时候,第二个参数可以不设置
	studentMap := make(map[string]map[string]string)

	// 内部的map也要make生成
	studentMap["stu01"] = make(map[string]string, 3)
	studentMap["stu01"]["name"] = "kiko"
	studentMap["stu01"]["sex"] = "男"
	studentMap["stu01"]["address"] = "qq.com"

	studentMap["stu02"] = make(map[string]string)
	studentMap["stu02"]["name"] = "yoyo"
	studentMap["stu02"]["sex"] = "女"
	studentMap["stu02"]["address"] = "tube.com"

	fmt.Println(studentMap)
	fmt.Println(studentMap["stu02"])
	fmt.Println(studentMap["stu02"]["address"])
}

在这里插入图片描述

5、map 的增删改查操作

在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述

6、map的遍历

map元素个数
在这里插入图片描述

*** 遍历map套map

package main

import (
	"fmt"
)

func main() {

	// make生成对象, 创建map对象的时候,第二个参数可以不设置
	studentMap := make(map[string]map[string]string)

	// 内部的map也要make生成
	studentMap["stu01"] = make(map[string]string, 3)
	studentMap["stu01"]["name"] = "kiko"
	studentMap["stu01"]["sex"] = "男"
	studentMap["stu01"]["address"] = "qq.com"

	studentMap["stu02"] = make(map[string]string)
	studentMap["stu02"]["name"] = "yoyo"
	studentMap["stu02"]["sex"] = "女"
	studentMap["stu02"]["address"] = "tube.com"

	for key1, value1 := range studentMap {
		fmt.Println(key1)
		for key2, value2 := range value1 {
			fmt.Println("\t", key2, " = ", value2)
		}
	}
}

在这里插入图片描述

7、map的切片

map 个数就可以动态变化
package main

import (
	"fmt"
)

func main() {

	// 声明一个map切片
	var heroes []map[string]string
	// 给map切片创建空间
	heroes = make([]map[string]string, 2)

	// 增加信息
	if heroes[0] == nil {
		heroes[0] = make(map[string]string, 2)
		heroes[0]["name"] = "韩信"
		heroes[0]["age"] = "100"
	}

	if heroes[1] == nil {
		heroes[1] = make(map[string]string, 2)
		heroes[1]["name"] = "李白"
		heroes[1]["age"] = "120"
		heroes[1]["status"] = "active"
	}

	// 非法,超出了切片的索引范围
	// heroes[2] = make(map[string]string)
	// if heroes[2] == nil {
	// 	heroes[0] = make(map[string]string, 2)
	// 	heroes[0]["name"] = "诸葛亮"
	// 	heroes[0]["age"] = "100"
	// }

	// 可以通过append添加新的map对象到切片中
	newHero := map[string]string{
		"name": "鲁班",
		"age":  "130",
	}

	heroes = append(heroes, newHero)

	fmt.Println(heroes)
}

在这里插入图片描述

8、map排序

基本介绍
  1. golang 中没有一个专门的方法针对 map 的 key 进行排序。
  2. golang 中的 map 默认是无序的(新版本是字典序),注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样.。
  3. golang 中 map 的排序,是先将 key 进行排序,然后根据 key 值遍历输出即可。
package main

import (
	"fmt"
	"sort"
)

func main() {

	map1 := make(map[int]int)

	map1[10] = 100
	map1[1] = 90
	map1[3] = 110
	map1[7] = 80
	map1[5] = 30

	fmt.Println(map1)

	//如果按照map的key的顺序进行排序输出
	//1.先将map的key放入到切片中
	//2. 对切片排序
	//3. 遍历切片,然后按照key来输出map的值
	// 排序方式是使用sort包中的函数,但是它是对切片进行排序,所以要先将key转成切片

	var keys []int = make([]int, 1)
	for key, _ := range map1 {
		keys = append(keys, key)
	}

	// 排序
	sort.Ints(keys) // 升序
	fmt.Println(keys)

	// 遍历
	for _, k := range keys {
		fmt.Printf("map1[%v] = %v \n", k, map1[k])
	}

}

在这里插入图片描述

9、map 使用细节

map 是引用类型,遵守引用类型传递的机制

在这里插入图片描述

map 的 value 也经常使用 struct 类型更适合管理复杂的数据(比前面 value 是一个 map 更好)