Map与函数


1 Map

  map是一种key:value键值对的数据结构容器。map内部实现的是哈希表(hash)。

  map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

  map是引用类型的。

1.1 语法

  可使用内建函数make或map关键字定义map

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable = make(map[key_data_type]value_data_type)

参数说明:
   1.map_variable:map变量名称
   2.key_data_type:key的数据类型
   3.value_data_type:值得数据类型

func Map() {
	var Map map[string]string // 类型的声明
	Map = make(map[string]string)
	fmt.Printf("strMap: %v\n", Map)
	fmt.Printf("strMap: %T\n", Map)
}

func strMap() {
	var strMap1 = map[string]string{
		"Name":  "小罗",
		"Age":   "30",
		"Email": "luovipyu@163.com",
		"Site":  "https://linuxtech.top/",
	}
	fmt.Printf("strMap1: %v\n", strMap1)

	strMap2 := make(map[string]string)
	strMap2["Name"] = "小罗"
	strMap2["Age"] = "30"
	strMap2["Email"] = "luovipyu@163.com"
	strMap2["Site"] = "https://linuxtech.top/"
	fmt.Printf("strMap2: %v\n", strMap2)
}

1.2 访问map

  通过下标key获得其值。

func value_map() {
	// 通过下标key获得其值
	var valueMap = map[string]string{
		"Name":  "小罗",
		"Age":   "30",
		"Email": "luovipyu@163.com",
		"Site":  "https://linuxtech.top/",
	}
	var key = "Age"
	var value = valueMap[key]
	fmt.Printf("value: %v\n", value)

}

1.3 判断键是否存在

  Go语言中有判断map中键是否存在的特殊写法,格式如下:

value, ok := map[key]
// 如果ok为`true`,存在;否则,不存在。
func key_map() {
	// 判断键是否存在
	keyMap := make(map[string]string)
	keyMap["Name"] = "小罗"
	keyMap["Age"] = "30"
	keyMap["Email"] = "luovipyu@163.com"
	keyMap["Site"] = "https://linuxtech.top/"

	v, ok := keyMap["address"]
	if ok {
		fmt.Println("键存在")
		fmt.Println(v)

	} else {
		fmt.Println("键不存在")
	}
}

1.4 遍历map

  可使用for range循环进行map遍历,得到key和value值。

1.4.1 遍历key

func iterate_key_map() {
	// 遍历map---key
	iterateKey := make(map[string]string)
	iterateKey["name"] = "小明"
	iterateKey["age"] = "23"
	iterateKey["email"] = "xiaoming@163.com"

	for key := range iterateKey {
		fmt.Println(key)
	}
}

1.4.2 遍历key和value

func iterate_key_value_map() {
	// 遍历map---key和value
	var iterateKeyValue = map[string]string{
		"name":   "小红",
		"age":    "21",
		"school": "清华大学",
	}
	for key, value := range iterateKeyValue {
		fmt.Println(key + ":" + value)

	}
}

2 函数

2.1 函数简介

  函数是Go语言中的一级公民,所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名(signature)。

2.2 函数特性

  1.Go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上的函数)。

  2.Go语言中不允许函数重载(overload),也就是说不允许函数同名。

  3.Go语言中的函数不能嵌套函数,但可以嵌套匿名函数。

  4.函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。

  5.函数可以作为参数传递给另一个函数。

  6.函数的返回值可以是一个函数。

  7.函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。

  8.函数参数可以没有名称。

2.3 函数的定义和调用

  函数在使用之前必须先定义,可以调用函数来完成某个任务。

  函数可以重复调用,从而达到代码重用。

2.3.1 函数定义

func function_name( [parameter list] ) [return_types]
{
   函数体
}

语法解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • [parameter list]:参数列表,参数就像一个占位符,当函数被调用时,可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。
package main

func sum(a int, b int) (reSum int) { // 形式参数--形参
	reSum = a + b
	return reSum // 这里也可只写return
}

func conpare(m int, n int) (max int) {
	if m > n {
		max = m
	} else {
		max = n
	}
	return max // 这里也可只写return
}

2.3.2 函数调用

  当完成某个任务时,可以调用函数来完成。调用函数要传递参数,如何有返回值可以获得返回值。

func main() {
	res := sum(3, 90) // 调用的时候是实参
	println("计算结果:", res)

	maxResult := conpare(-1, 7)
	println("这两个数中比较大的数是:", maxResult)
}

2.4 函数返回值

函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过return关键字来指定。

return可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。Go中的函数可以有多个返回值。

  1.return关键字中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称。

  2.当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值。

  3.即使返回值命名了,return中也可以强制指定其它返回值的名称,也就是说return的优先级更高。

  4.命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复定义。

  5.return中可以有表达式,但不能出现赋值表达式,这和其它语言可能有所不同。例如return a+b是正确的,但return c=a+b是错误的。

func test1() {
	// 既没参数,也没返回值
	fmt.Println("我既没参数,也没返回值...")
}

func Sum(a int, b int) (ret int) {
	// 有一个返回值
	ret = a + b
	return ret
}

func Sum2(m int, n int) int {
	// 有一个返回值
	// RET := m + n
	return m + n

}

func test3() (name string, age int) {
	// 多个返回值,且在return中指定返回的内容
	name = "小罗"
	age = 31
	return name, age
}

func test4() (name string, age int) {
	// 多个返回值,返回值名臣没有被使用
	name = "小罗"
	age = 31
	return //等价于 return name, age
}

func test5() (name string, age int) {
	// return覆盖命名返回值,返回值名称没有被使用
	n := "小罗"
	a := 31
	return n, a
}

func test5() (string, int) {
	// return覆盖命名返回值,返回值名称没有被使用
	n := "小罗"
	a := 31
	return n, a
}

  Go中经常使用其中一个返回值作为函数是否执行成功、是否有错误信息的判断条件。例如return value,existsreturn value,okreturn value,err等。

  当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如,同类型的返回值可以放进slice中,不同类型的返回值可以放进map中。

  函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线_丢弃这些返回值。例如下面的f1函数两个返回值,调用该函数时,丢弃了第二个返回值b,只保留了第一个返回值a赋值给了变量a

func f1() (int, int) {
	return 1, 2
}
func main() {
	_, x := f1()
	fmt.Printf("x: %v\\n", x)
}

2.5 函数参数

  Go语言函数可以有0或多个参数,参数需要指定数据类型

  声明函数时的参数列表叫做形参,调用时传递的参数叫做实参。

  Go语言是通过传值的方式传参的,意味着传递给函数的是拷贝后的副本,所以函数内部访问、修改的也是这个副本。

  Go语言可以使用变长参数,有时候并不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分使用ARGS...TYPE的方式。这时会将...代表的参数全部保存到一个名为ARGS的slice中,注意这些参数的数据类型都是TYPE。

// 形参列表
func f1(a int, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func f2(a int) {
	a = 200
	fmt.Printf("a: %v\n", a)
}

func main() {
	// 实参列表
	r := f1(6, 0)
	fmt.Printf("r: %v\n", r)

	// 演示参数传递,按值传递
	x := 100
	f2(x)
	fmt.Printf("x: %v\n", x)
}

// 从运行结果可以看到,调用函数f2后,x的值并没有被改变,说明参数传递是拷贝了一个副本,也就是拷贝了一份新的内容进行运算。

  mapsliceinterfacechannel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。

func f3(s []int) {
	s[0] = 900
}

func main() {
	s := []int{1, 3, 4}
	f3(s)
	fmt.Printf("a: %v\n", s)
}

// 运行结果:s: [900 3 4]

  从运行结果发现,调用函数后,slice内容被改变了。

// 可变参数---变长参数
func f4(args ...int) {
	for _, v := range args {
		fmt.Printf("v: %v\n", v)
	}
}

func f5(name string, ok bool, args ...int) {
	fmt.Printf("name: %v\n", name)
	fmt.Printf("ok: %v\n", ok)
	for _, v := range args {
		fmt.Printf("v: %v\n", v)
	}
}

func main() {
	f4(3, 7, 8, 9, 0)
	f5("小罗", true, 31, 173, 87)
}

2.6 函数类型与函数变量

  可使用type关键字来定义一个函数类型,语法格式如下:

// 定义一个`fun`函数类型,它是一种函数类型,这种函数接收两个`int`类型的参数,并且返回一个`int`类型的返回值。
type fun func(int, int) int

  定义一个fun函数类型,把summax赋值给它。

func Sum(a int, b int) int {
	return a + b
}

func Max(m int, n int) int {
	if m > n {
		return m
	} else {
		return n
	}
}

func main() {
	type fun func(int, int) int

	var funType fun
	
	funType = Sum
	NumberSum := funType(45, 5)
	fmt.Printf("NumberSum: %v\n", NumberSum)

	funType = Max
	NumberMax := funType(45, 67)
	fmt.Printf("NumberMax: %v\n", NumberMax)
}

2.7 高阶函数

  Go语言的函数,可以作为函数的参数,传递给另外一个函数,作为另外一个函数的返回值返回。

2.7.1 函数作为参数

package main // 可执行程序,不是库
import "fmt" // 导入格式化输入输出包,用来打印内容

// 函数作为参数
// 定义一个普通函数sayHello,接收1个string类型参数name,用来打印Hello,+传入的名字
func sayHello(name string) {
	fmt.Printf("Hello,%s\n", name)
}

func sayBye(name string) {
	fmt.Printf("拜拜,%s", name)
}

// f1是高阶函数   两个参数:1.普通字符串name    2.一个函数,函数名叫f,这个函数要求:接收1个string参数,没有返回值
func f1(name string, f func(string)) {
	f(name) // 函数体:调用传进来的函数f,并且把name传给它
}

// 调用函数
func main() {
	f1("小罗", sayHello)
	f1("小罗", sayBye)
}

2.7.2 函数作为返回值

// 声明当前包为主包,可独立运行
package main

// 导入格式化输出标准库
import "fmt"

// 定义加法函数:接收两个整型,返回相加结果
func numAdd(a int, b int) int {
	addResult := a + b // 计算两数之和并赋值
	return addResult   // 返回计算结果
}

// 定义减法函数:接收两个整型,返回相减结果
func numSub(a int, b int) int {
	subResult := a - b // 计算两数之差并赋值
	return subResult   // 返回计算结果
}

// 高阶函数:接收运算符字符串,返回【两个入参、一个返回值】的函数
func cal(operator string) func(int, int) int {
	// 根据运算符匹配对应逻辑
	switch operator {
	case "+":
		return numAdd // 匹配加号,返回加法函数
	case "-":
		return numSub // 匹配减号,返回减法函数
	default:
		return nil // 未知运算符,返回空
	}
}

// 程序入口函数
func main() {
	// 短变量声明:调用cal获取加法函数,赋值给funResult
	funResult := cal("+")
    
	// 调用获取到的加法函数,计算结果并声明变量存储
	calResult := funResult(1, 2)
    
	// 打印计算结果
	fmt.Printf("calResult: %v\n", calResult)

	// 变量重新赋值:funResult已声明,直接用=覆盖,获取减法函数
	funResult = cal("-")
    
	// 调用减法函数,重新给已有变量赋值
	calResult = funResult(1, 2)
    
	// 打印计算结果
	fmt.Printf("calResult: %v\n", calResult)
}

2.8 匿名函数

  Go语言函数不能嵌套,但是在函数内部可以定义匿名函数。

  匿名函数就是没有名称的函数。

  语法格式如下:

func (参数列表)(返回值)

匿名函数既可以没有参数,也可以没有返回值

/* func main() {
   // 匿名函数
	max := func(a int, b int) int {
		if a > b {
			return a
		} else {
			return b
		}
	}

	i := max(1, 2)
	fmt.Printf("i: %v\n", i)

} */

func test() {
	name := "小罗 "
	age := "31"

	// 匿名函数在函数内部做一些运算
	fun1 := func() string {
		return name + age

	}
	msg := fun1()
	fmt.Printf("msg: %v\n", msg)
}

func main() {
	// 自己执行
	func(a int, b int) {
		max := 0
		if a > b {
			max = a
		} else {
			max = b
		}
		fmt.Printf("max: %v\n", max)

	}(1, 2)
	test()
}

3 闭包

  闭包可以理解成定义在一个函数内部的函数

  在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。

  闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

// 返回一个函数
func add() func(y int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	var f = add()
	r := f(10)
	fmt.Printf("r: %v\n", r)    // fmt.Println(f(10))

	r = f(20)
	fmt.Printf("r: %v\n", r)

	r = f(30)
	fmt.Printf("r: %v\n", r)

	fmt.Println("------------")

	f1 := add()
	r = f1(100)
	fmt.Printf("r: %v\n", r)
}

  变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。

  闭包进阶示例1

func add(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = add(10)
	fmt.Println(f(10))
	fmt.Println(f(20))
	fmt.Println(f(30))

	fmt.Println("----------")

	f1 := add(20)
	fmt.Println(f1(40))
	fmt.Println(f1(50))
}

  闭包进阶示例2

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

func main() {
	jpgFunc := makeSuffixFunc(".jpg")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc("test")) 
	fmt.Println(txtFunc("test")) 
}

  闭包进阶示例3

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	add, sub := calc(100)
    r := add(100)
	fmt.Printf("r: %v\n", r)
	r = sub(50)
	fmt.Printf("r: %v\n", r)    // fmt.Println(f1(5), f2(6))
}

闭包其实并不复杂,只要牢记闭包=函数+引用环境

4 递归

  函数内部调用函数自身的函数称为递归函数。

  使用递归函数最重要的三点:

   1.递归就是自己调用自己。

   2.必须先定义函数的退出条件,没有退出条件,递归将成为死循环。

   3.Go语言递归函数很可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出问题。

// 阶乘
func f1() {
	var s int = 1
	// for循环实现阶乘
	for i := 1; i <= 5; i++ { // 5的阶乘 5!=5x4x3x2x1
		s *= i
	}
	fmt.Printf("s: %v\n", s)
}

func f2(a int) int {
	if a == 1 {
		// 1.退出条件
		return 1
	} else {
		// 2.自己调用自己
		return a * f2(a-1)
	}
}

func main() {
	a := 5
	r := f2(a) // r := f2(5)
	fmt.Printf("r: %v\n", r)
	f1()
}

  斐波那契数列:

// 斐波那契数列
// 计算公式:f(n)=f(n-1)+f(n-2)且f(2)=f(1)=1

func f3(n int) int {
	// 退出点判断
	if n == 1 || n == 2 {
		return 1
	}
	// 递归表达式
	return f3(n-1) + f3(n-2)
}

func main() {
	result := f3(5)
	fmt.Printf("result: %v\n", result)
}

5 defer语句

  Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

5.1 defer特性

  1.关键字 defer 用于注册延迟调用。

  2.这些调用直到 return 前才被执。因此,可以用来做资源清理。

  3.多个defer语句,按先进后出的方式执行。

  4.defer语句中的变量,在defer声明时就决定了。

5.2 defer用途

  1.关闭文件句柄

  2.锁资源释放

  3.数据库连接释放

5.3 defer语句实例

func fun_defer() {
	fmt.Println("start...")
	defer fmt.Println("step1...")
	defer fmt.Println("step2...")
	defer fmt.Println("step3...")
	fmt.Println("end...")
}

func main() {
	fun_defer()
}

// 运行结果: start...   end...   step3...   step2...    step1...

6 init函数

  Golang有一个特殊的函数,它是init函数,先于main函数执行,实现包级别的一些初始化操作。

6.1 init函数主要特点

  • init函数先于main函数自动执行,不能被其他函数调用;
  • init函数没有输入参数、返回值;
  • 每个包可以有多个init函数;
  • 包的每个源文件也可以有多个init函数
  • 同一个包的init执行顺序,Golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
  • 不同包的init函数按照包导入的依赖关系决定执行顺序。

6.2 Golang 初始化顺序

初始化顺序:变量初始化 –> init() –> main()

var a int = initVar()

// 快捷键---finit
func init() {
	fmt.Println("init...")
}

func initVar() int {
	fmt.Println("init var...")
	return 100
}

func main() {
	fmt.Println("main...")
}

// 运行结果:init var...    init...     main...

文章作者: 罗宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗宇 !
 上一篇
数组与切片 数组与切片
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
下一篇 
指针与结构体 指针与结构体
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
  目录