一 运算符

1 运算符基础

1 算法: 解决问题的过程,运算符和表达式来串联数据和指令。


算数运算符 赋值运算符 比较运算符 逻辑运算符 位运算符 其他相关运算符

2 算数运算符

1 简介

算术运算符是对数值类型的变量进行运算的,如加减乘除,在Go语言中使用较多

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余
++ 自增
-- 自减

2 重点讲解

除(/)和取模(%)

针对除(/): 默认的当除号两边都是整数时,其值为整数,其会直接削减掉对应的小数部分,当一个为小数时,则会继承小数的属性

针对取模(%) a%b=a-a/b*b 及 -10%3=-10-(-10/3)*3=-10-(-9)=-1


自增和自减 ++ --

Go语言中自增自减只能当成独立语言使用,不能是b:=a++ 或 b:=a--

Go 语言中的++ 和 -- 只能在变量后面,不能在变量前面,及没有++a和--a

Go 语言中自增,自减不是运算符,只能作为独立语句,不能用于表达式

3 关系运算符

1 简介

关系运算符的结果都是bool类型,也就是要么是true,要么是false 关系表达式经常用在if结构条件判断中或循环结构中使用

运算符 描述
== 检查两个值是否相等,如果相等则返回True,否则返回False
!= 检查两个值是否不相等,如果不相等返回True,否则返回False
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False

2 细节说明

1 关系运算符的结果都是bool类型,也就是要么是true,要么是false

2 关系运算符组成的表达式,称为关系表达式

3 比较运算符的== 不能写成=

4 逻辑运算符

1 简介

用于链接多个条件,最终结果是一个bool

运算符 描述
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False
逻辑或 运算符。 有一个是True,则条件 True,否则为 False
! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True

2 注意事项

&& 也叫短路与,a && b ,及若a为false,则结果必为false || 也叫短路或,a || b, 若a为true,则结果必为true

5 赋值运算符

1 介绍

赋值运算符就是将某个运算运行后赋值给指定的变量

运算符 描述
= 简单的赋值运算符,将一个表达式的值赋给一个左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
<<= 左移后赋值
<<= 右移后赋值
&= 按位与后赋值
^= 按位异或后赋值
= 按位或后赋值

通过相关的赋值语句完成对应的操作

2 赋值运算特点

1 运算顺序从右向左 2 赋值运算符的左边只能是变量,右边可以是变量,常量,表达式 3 复合赋值运算等价于下面结果

如 a+=3等价于 a=a+3

6 位运算符

1 源码,反码,补码

二进制是逢2进位的进位制,0,1 是基本算符

现代的电子计算机技术全部采用的是二进制,因为它只使用0,1两个数字符号,非常方便,易使用电子方式实现,计算机内部处理的信息,都是采用二进制数表示的,二进制数用0或者1可表示任何数,进位规则是"逢2进1",数字1在不同的位上表示不用的值,按从右至左的次序,这个值是以二倍递增

在计算机内部,运算各种运算,就是以二进制的方式运行的


源码: 及原来数据的二进制表示 反码:符号位不变,其他位相反 补码:整体加1

正数的源码,反码和补码都一样

负数的反码=原码的符号位不变,其他位取反

负数的补码==他的反码+1

0的反码,补码都是0

在计算机运算的时候,都是以补码的方式来进行运算的


如下

-9 二进制源码表示为: 1000 1001 则二进制反码为: 1111 0110 则二进制补码为: 1111 0111

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	fmt.Printf("输出结果为%d      %d", -5>>2, -15>>2)
	/*
		分析过程
		-5>>2
		结果分析如下

		源码
		1000  0110
		反码
		1111  1001
		补码
		1111  1010

		右移两位
		补码
		1111  1110
		反码
		1111   1101
		源码
		1000   0010
		及 -2

		-14 >>2
		源码1000 1110
		反码 1111 0001
		补码 1111 0010
		右移两位
		补码  1111  1100
		反码  1111  1011
		源码  1000  0100  及  -4
	*/
}

结果如下

2 位运算符

运算符 描述
& 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与
按位或运算符" "是双目运算符。 其功能是参与运算的两数各对应的二进位相或
^ 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1
<< 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0
<< 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。

7 其他运算符

& 返回变量存储地址 * 指针变量,返回其指针对应的变量的值 Go 语言不支持三元运算符

8 进制介绍

1 简介

对于整数有4中表达方式 1 二进制, 0,1 满2进1 2 十进制 0-9,满 10 进1 3 八进制 0-7 满八进一 以数字 0开头表示 4 十六进制 0-9 及 A-F,满16 进1 ,以0x或0X开头,此处A-F不区分大小写

2 进制转换

1 其他进制转十进制

二进制转换为十进制 规则: 从最低位开始(右边的),将每个为上的数字提取出来,乘以2的(位数-1)次方 然后求和


八进制转10进制 规则:从最低位(右边的),将每个为上的数字提取,乘以8的(位数-1)次方


十六进制转十进制 规则:从最低位开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和

2 十进制转其他进制

十进制转2进制 规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制


十进制转八进制 将该数不断除以8,直到商为0即可,然后将每步得到的余数倒过来,就是对应的八进制


十进制转16进制 规则: 将该数不断除以16,直到商为0,然后将每步得到的余数倒过来,就是对饮的十六进制

3 二进制转其他进制

二进制转八进制 规则:三位一组进行处理和转换


二进制转十六进制 规则: 将二进制数每四位一组,组合成对应的十六进制数即可

4 其他进制转二进制

八进制转二进制 规则: 将八进制数每1位,转成对应的三位的二进制数即可


十六进制转二进制 规则: 将十六进制的每一位数,转换成4位的二进制数即可

9 运算符优先级

一元算法运算符优先级最高,二元则分成五个级别,从高往低分别是

优先级 运算符
5 * / % << >> & &^
4 + - ^
3 == != < <= > >=
2 &&
1

相同优先级的二元运算符,从左往右依次计算

10 键盘输入语句

1 单次输入

在编程中,需要接受用户输入的数据,可使用键盘输入语句来进行获取相关参数的操作

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	var name string
	fmt.Println("请输入")
	fmt.Scanln(&name)
	fmt.Println("name=", name)
}

结果如下

2 同时输入多个

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	var name string
	var age int
	fmt.Println("请输入")
	fmt.Scanf("%s  %d", &name, &age)
	fmt.Printf("name=%s,age=%d", name, age)

}

结果如下

二 控制语句

1 简介

在程序中,程序运行的流程控制决定程序如何执行,是必须掌握的,主要有三大流控语句


1 顺序控制 2 分支控制 3 循环控制

2 顺序控制

程序从上到下逐步执行,中间没有任何判断和跳转

3 分支控制

1 if 控制

1 单分支

单分支 基本语法

if 条件表达式 { 执行语句块 }

当表达式为true,会执行其中的语句块的内容 细节说明: go 的if有一个强大的就是条件判断里面允许声明一个变量,这个变量的作用域只能在该条件的逻辑块内,在其他地方不起作用


示例如下

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	if i := 20; i > 19 {
		fmt.Println(i)
	}
	fmt.Println(i)
}

结果如下

2 双分支

判断流程: 先判断条件表达式是否成立,若为真,则执行代码块1 若为假,则执行代码块2

3 多分支

判断流程: 先判断条件表达式1是否成立,若不成立,则执行下一个,若成立,则执行, 若都不成立,则执行else

4 嵌套分支

在一个分支结构中又完成的嵌套了另一个完成的分支结构,里面的分支的结构称为内层分支,外部的分支称为外层分支 建议嵌套分支不宜太多,建议控制在3层

2 switch

1 简介

switch 语句用于基于不同条件执行不同动作的情况,每个case分支都是唯一的,从上到下逐一匹配,直到匹配为止

switch 表达式 {
			case  表达式1,表达式2,...:  //表达式之间是或的关系
							语句块
			case  表达式3,表达式4...:
							语句块
			// 此处有多个表达式 
			default:
							语句块
	}
2 switch 细节

1 case 后面是一个表达式 2 case 后各个表达式的值的数据类型必须和switch的表达式数据类型一致 3 case 后面可以带多个表达式,使用逗号隔开,比如case 表达式1,表达式2 4 case 后面的表达式如果是常量值(字面值),则要求不能重复 5 case 后面不需要break,程序匹配到一个case就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default 6 default 不是必须的 7 switch后面也可以不带表达式,类似if-else分支来使用 8 switch后面也可以直接声明/定义一个变量,分号结束,不推荐 9 switch穿透fallthrough,如果在case语句块增加了fallthrough,则会继续执行下一个case,也叫switch穿透 10 type switch: switch语句还可以被用于type-switch来判断一个interface变量中实际指向的变量

3 实战
1 判断输入数据类型
package main

import "fmt"

func Test(x interface{}) { // 此处使用interface类型,其若为空,则表示可是任何类型,后面可说明
	switch x.(type) { // 此处用于判断输入值的类型
	case nil:
		fmt.Printf("this  type is  %T\n", x)
	case float64:
		fmt.Printf("this  type is  %T\n", x)
	case string:
		fmt.Printf("this  type is  %T\n", x)
	case int:
		fmt.Printf("this type is %T\n", x)
	default:
		fmt.Println("未知数据类型")
	}
}
func main() { //定义main函数,一般的文件执行的入口函数都是main
	x := 10
	y := 20.1
	z := "abcd"
	Test(x)
	Test(y)
	Test(z)
}
 

结果如下

2 穿透相关
package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	switch { //switch默认是true
	case false:
		fmt.Println("The integer was <= 4")
		fallthrough
	case true:
		fmt.Println("The integer was <= 5")
		fallthrough
	case false: //此处不会进行判断,直接进入此处
		fmt.Println("The integer was <= 6")
		fallthrough
	case true:
		fmt.Println("The integer was <= 7")
		fallthrough
	case false:
		fmt.Println("The integer was <= 8")
	default:
		fmt.Println("default case")
	}
}

结果如下

4 循环控制

1 简介

1 基本语法
for  循环变量初始化;循环条件;循环变量迭代{
				循环操作(语句)
}

语法格式说明

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	for i := 1; i < 10; i++ {
		fmt.Println(i)
	}
}

结果如下

2 for 循环执行步骤

循环变量初始化 循环条件 循环操作 循环变量迭代

3 注意事项和细节说明

循环条件是返回一个布尔表达式

4 for 循环的第二种使用方式
for  循环条件 {
			循环执行语句
}

将变量初始化和变量迭代写到其他位置

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	i := 1
	for i < 10 {
		fmt.Println(i)
		i++;
	}
}

结果如下

5 for 循环的第三种方式
for   {
						// 循环执行语句
		}
package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	i := 1
	for {
		if i == 10 {
			break
		} else {
			fmt.Println(i)
		}
		i++;
	}
}

结果如下

2 for 循环遍历

1 for...range 遍历
package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	var str string
	fmt.Scanf("%s", &str)
	for k, v := range str {
		fmt.Printf("index=%d ,value=%c\n", k, v)
	}
}

结果如下

注意: for-range 在遍历字符串时,是按照字符来遍历的,而不是for循环默认的使用字节进行遍历

3 多重循环控制

1 介绍

1 将一个循环放在另一个循环中,就形成了嵌套循环,在外层的被称为外层循环,在内部称为内层循环 2 一般情况下,当内层循环的判断条件为false时,才会跳出内层循环 3 设外层循环次数为m,内层循环次数为n,则内层循环一般要执行m*n此

2 基本实例,打印乘法口诀

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	for i := 1; i < 10; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%d*%d=%d    ", j, i, i*j)
		}
		fmt.Println()
	}
}

结果如下

4 for 循环的break 和 continue

break 跳出本循环体,continue 跳过本次循环

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	for j := 1; j <= 2; j++ {
		for i := 1; i < 5; i++ {
			fmt.Println("内层", i)
			if i == 2 {
				break //跳出此轮循环,而不是直接跳出所有循环,只是某一个for
			}
		}
		fmt.Println("外层", j)
	}
	fmt.Println("continue----------------")
	for i := 1; i < 5; i++ {
		if i == 2 {
			continue // 跳出此次循环
		}
		fmt.Println(i)
	}
}

结果如下

5 label 使用

用于跳转到指定位置

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
lable2:
	for j := 1; j <= 2; j++ {
		for i := 1; i < 5; i++ {
			fmt.Println("内层", i)
			if i == 2 {
				break lable2 //加上标签,表示跳出至标签位置
			}
		}
		fmt.Println("外层", j)
	}
}

结果如下

6 跳转到指定代码标签(goto)

1 简介

goto语句通过标签进行代码间的无条件跳转,goto语句可以在快速跳出循环、避免重复退出上有一定的帮助,Go语言使用goto语句能简化一些代码的实现过程。

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	var breakAgain bool
	//外层循环
	for x := 0; x < 10; x++ {
		fmt.Println("外层循环", x)
		//内层循环
		for y := 0; y < 10; y++ {
			//满足条件则退出
			fmt.Println("内层循环", y)
			if y == 2 {
				breakAgain = true
			}
		}
		if breakAgain { //外层循环处理退出机制
			break
		}
	}
	fmt.Println("done") // 退出最终机制
}

结果如下

2 goto 处理问题

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	//外层循环
	for x := 0; x < 10; x++ {
		fmt.Println("外层循环", x)
		//内层循环
		for y := 0; y < 10; y++ {
			//满足条件则退出
			fmt.Println("内层循环", y)
			if y == 2 {
				//跳转到标签。直接退出
				goto breakHere
			}
		}
	}
	//return
breakHere:
	fmt.Println("done")
}

结果如下

7 return

使用在方法或函数中,表示跳出所在的方法或函数

说明

1 如果return 在普通函数中,表示跳出该函数,则不在执行return后面的代码,也可以理解为终止该函数 2 若return在main函数,表示终止main函数,也就是终止程序

三 函数和包

1 函数

1 概述

函数是组织好的,可重复使用的,用来实现单一或相关功能的代码段,其可以提高应用的模块性和代码的重复利用率,其是完成某一功能的程序指令的集合,其分为自定义函数和系统函数

2 特点

1 减少代码冗余 2 提升代码可维护性

3 声明函数

普通函数需要先声明才能调用,一个函数的声明包括参数和函数名等,编译通过了才能了解函数应该怎样调用代码和函数体之间传递参数和返回函数。

1 普通函数的声明形式

Go 语言的函数声明以func标识,后面紧跟着函数名、参数列表、返回参数列表及函数体,具体形式如下

func  函数名(形参列表)   (返回值列表)  {
    执行语句
		return  返回值列表 
}

函数名:由字母,数字,下划线组成,其中,函数名的第一个字母不能为数字,在同一个包内,函数名称不能重名。


形参列表: 表示函数的输入 ,其由参数变量和参数类型组成,其中,参数列表中的变量作为函数的局部变量而存在。


执行语句:表示为了实现某一功能的代码块。


返回值:其可以有返回值,也可以没有,其有返回值时,必须在函数体中使用return语句提供返回值列表。 函数体:能够被重复调用的代码片段。

2 参数类型简写
func  add(a,b int) int  {
	return  a+b
}

若有多个参数变量,以逗号进行分割,若相邻变量是同类型,则可将写成一个

3 函数的返回值

Go 语言支持多返回值,多返回值能方便获取函数执行后的多个返回参数,Go语言经常使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误

1 纯类型的返回值
func Test(a, b int) (int, int) {
	return a + b, a * b
}

2 带有变量名的返回值类型

Go 支持对返回值类型进行命名,这样返回值就和参数一样拥有变量名和类型

package main

import "fmt"

func Test(a, b int) (x, y int) {  //此处通常用于标识返回值情况
	return a + b, a * b
}

func main() {
	m, n := Test(10, 20)
	fmt.Println(m, n)
}

结果如下

4 函数调用机制

1 基本语法

Go 支持函数返回多个值 1 若返回多个值,在接受时,希望忽略某个返回值,则可以使用_符号表示占位符 2 如果返回值只有一个,则返回值类型列表可以不写括号 3 为了让其他包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其他语言的公共参数,这样才能挎包访问 4 在访问其他函数时,其语法是 包名.函数名 5 如果包名称比较长,则Go支持使用别名的方式进行处理 6 在同一个包下,不能有相同的函数名,否则重复定义 7 如果要编译成一个可执行程序文件,就需要这个包声明为main,及package main,这就是一个语法规范,如果是写一个库,则包名可以自定义。

package main

import "fmt"

func Test(i int) int { //函数定义,当返回是为一个时,其不用写括号
	i += 1
	return i
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	i := 10
	j := Test(i) // 函数调用
	fmt.Println(j)
	fmt.Println(i) //打印原值。因为函数调用会形成新的空间,其会进行值拷贝,因此其不会影响原来main函数中对应的值
}

结果如下

5 函数的注意事项和细节讨论

1 函数的形参列表可以是多个,返回值也可以是多个 2 形参列表和返回值的数据类型可以是值类型和引用类型 3 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包和其他包文件引入调用,,首字母小写,只能被本包使用,其他包文件不能使用 4 函数中变量是局部变量,函数外不生效 5 基本数据类型和数组默认都是值传递,进行值拷贝,在函数内修改,不会影响到函数外原来的值 6 如果希望函数内的变量能够改变函数外的变量,可以传递变量的地址进行处理

package main

import "fmt"

func Test(i *int) { //函数定义,当返回是为一个时,其不用写括号
	*i += 1
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	i := 10
	fmt.Println(i)
	Test(&i)       // 函数调用
	fmt.Println(i) //打印原值。因为函数调用会形成新的空间,其会进行值拷贝,因此其不会影响原来main函数中对应的值
}

结果如下

7 函数不支持重载 8 在Go中,函数也是一种数据类型,可以赋值给一个变量,该变量就是一个函数类型的变量,通过该变量可以对函数进行调用 9 函数是一种数据类型,自然可以作为形参,进行相关的调用操作

package main

import "fmt"

func getSum(n1, n2 int) int {
	return n1 + n2
}

func MyFun(funvar func(int, int) int, num1 int, num2 int) int { //此处传入进来一个函数,后面的num1 和 num2 是对应的方法
	return funvar(num1, num2)
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	a := getSum
	fmt.Printf("a 的类型为%T,getSum 的类型为%T\n", a, getSum)
	res := a(10, 20)
	fmt.Println("RES=", res)
	res1 := MyFun(getSum, 40, 50)
	fmt.Println("RES1=", res1)
}

结果如下

10 为了简化数据类型定义,go支持自定义数据类型

基本语法

type 自定义数据类型 数据类型 // 理解: 相当于一个别名 类型别名和类型定义见上一篇 https://blog.51cto.com/11233559/2509084

12 使用_ 标识符,忽略返回值

13 go 支持可变参数

package main

import "fmt"

func getSum(args ...int) int {  //使用xxx... 来表示多个参数,及可变参数
	sum := 0
	for _, value := range args {  // 此处用于求和
		sum += value
	}
	return sum

}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	res := getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
	fmt.Println(res)
}

结果如下

6 递归调用

1 基本介绍

函数----递归调用

一个函数在函数体又调用了本身,称为递归调用

package main

import (
	"fmt"
)

func getSum(num int) {
	if num > 2 {  //此处num 大于2时,其会不停调用此函数,直至其等于2
		num--
		getSum(num)
	}
	fmt.Println("num=", num)
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	getSum(10)
}

结果如下

此处存在压栈问题 当i=2将不成立,此处输出i=2 当i=3时,由于执行了i--,则此处将输出i的值为2 一个是直接不成立输出的结果,另一个是相减后输出的结果

2 基本实例
package main

import (
	"fmt"
)

var sum int = 1 //此处定义一个全局变量

func getSum(num int) int {
	if num == 1 { //此处进行一个退栈操作,及弹出最上面的
		return sum //
	} else {
		sum = getSum(num-1) * num //此处进行递归调用
	}
	return sum
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	res := getSum(10)
	fmt.Println(res)
}

结果如下

3 递归调用遵守的重要原则

1 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈) 2 函数的局部变量是独立的,不会影响 3 递归必须向退出递归的条件逼近,否则就无限递归了 4 当一个函数执行完毕后,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁

7 init 函数

通常在初始化时被调用

package main

import "fmt"

func getSum() int {
	fmt.Println("变量初始化")
	return 100
}

var Test = getSum() //此处是首先执行的

func init() { //此处是在变量初始化后执行的
	fmt.Println("init 函数初始化")
}
func main() { //定义main函数,一般的文件执行的入口函数都是main
	fmt.Println("main 函数处理过程") //此处是最后执行的

}

结果如下

如果一个Go文件同时包含全局变量定义,init 函数和main函数,则执行流程为全局变量定义-----> init 函数------> main 函数

8 匿名函数

1 介绍

Go 支持匿名函数,如果我们某个函数只希望使用一次,则可考虑匿名函数,匿名函数可实现多次调用

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	func() int {
		fmt.Println("变量初始化")
		return 100
	}() //定义并进行调用

}

结果如下

2 带参数的调用
package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	a := func(n1, n2 int) int {
		return n1 + n2
	}(10, 20)
	fmt.Println(a)
}

结果如下

3 全局匿名函数

如果将一个匿名函数赋值给一个全局便来给你,那么就会称为一个全局匿名函数,其可在程序中有效

package main

import "fmt"

var A = func(n1, n2 int) int {
	return n1 + n2
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	a := A(10, 20)
	fmt.Println(a)

}

结果如下

9 闭包

1 基本介绍

闭包就是一个函数和其他相关的引用环境组合的一个整体,其是引用了自由变量的函数,被引用的自由变量和函数一同存在,及时已经离开了自由变量也不会被释放或者删除,在闭包中仍可继续使用这个自由变量及 函数+引用环境=闭包

package main

import "fmt"

func AddUpper() func(int) int { //此处定义返回一个函数
	var n int = 10
	var str = "hello"
	return func(x int) int {  //此处返回一个函数,但该函数需要使用外部资源,此时该函数就和该外部资源形成一个整体,被称为闭包
		n = n + x             //此函数调用外部使用的n和str,并有自己传递的x
		str = str + string(n) // 此处进行拼接
		fmt.Println(str)
		return n // 内层函数返回值
	}
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	add := AddUpper() // 此处调用返回内层函数
	num := add(2)     //此处调用内层函数,并返回和打印对应值
	fmt.Println(num)
	fmt.Println("第二种调用--------------")
	num1 := AddUpper()(2)
	fmt.Println(num1)
}

结果如下

10 defer 函数

1 应用场景

在函数中,程序员需要经常创建资源,为了在函数执行完毕后,及时释放资源,go的设计者提供defer(延时机制),用于当函数执行完毕时,可以及时释放资源

业务用途:通常用于关闭文件和数据库链接

2 基本使用

defer 会被压到独立的栈defer栈中,当函数执行完毕后,会从defer中从先进后出的方式进行出栈执行操作

package main

import "fmt"

func Close() {
	fmt.Println("执行出栈操作")
}
func AddUpper() func(int) int { //此处定义返回一个函数\
	var n int = 10
	var str = "hello"
	return func(x int) int {
		n = n + x             //此函数调用外部使用的n和str,并有自己传递的x
		str = str + string(n) // 此处进行拼接
		fmt.Println(str)
		return n // 内层函数返回值
	}
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
	defer Close()
	add := AddUpper() // 此处调用返回内层函数
	num := add(2)     //此处调用内层函数,并返回和打印对应值
	fmt.Println(num)
	fmt.Println("第二种调用--------------")
	num1 := AddUpper()(2)
	fmt.Println(num1)
}

结果如下

11 函数参数的传递方式

1 基本介绍

值类型参数默认就是值传递,引用类型参数默认传递方式就是引用传递

其实,不管是值传递函数引用传递,传递给函数的都是变量的副本,不同的是,值传递传递的是拷贝,引用传递传递的是引用地址拷贝,一般来说,地址拷贝效率更高,因为数据量小,而值拷贝的数据大,数据越大,其拷贝效率越低


值类型默认是值传递:变量直接存储值,内存通常在栈中分配

引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才是真正存储数据的值,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就会成为一个垃圾,由GC来进行垃圾回收。

如果希望函数内的变量能够修改函数外的变量,可以传入变量的地址,函数内以指针的方式操作,从效果上类似于引用


值类型: 基本数据类型,如int ,float,bool,string,数组和结构体

引用类型: 指针,slice切片,map,管道,interface等都是引用数据类型

12 变量作用域

1 简介

1 函数内部声明/定义的变量叫做局部变量,作用仅限于函数内部 2 函数外部声明的变量叫全局变量,作用域在整个包都有效,如果首字母大写,则作用域在整个程序中都有效 3 如果变量是在一个代码块,则该变量的作用域就是该代码块

2 时间和日期相关函数
package main

import (
	"fmt"
	"time"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
	now := time.Now() // 获取当前时间
	fmt.Printf("年 %v\n", now.Year())
	fmt.Printf("月 %v\n", now.Month())
	fmt.Printf("日 %v\n", now.Day())
	fmt.Printf("时 %v\n", now.Hour())
	fmt.Printf("分 %v\n", now.Minute())
	fmt.Printf("秒 %v\n", now.Second())
	//格式化时间
	fmt.Printf("%v\n", now.Format("2006/01/02"))
	fmt.Printf("%v\n", now.Format("2006/01/02  15:04:06")) //此处格式化的时间是不能修改的,必须是2006/01/02  15:04:05 ,否则结果是错误的
	fmt.Printf("%v\n", now.Format("2016/01/02  00:00:00")) //此处格式化的时间是不能修改的,必须是2006/01/02  15:04:05 ,否则结果是错误的

}

结果如下

3 时间常量

Nanosecond Duration=1 //纳秒 Microscond =1000 *Nanosecond //微秒 Millisecond = 1000 * Microscond // 毫秒 Second 秒 Minute 分钟 Hour 小时

package main

import "time"

func main() { //定义main函数,一般的文件执行的入口函数都是main
	time.Sleep(time.Nanosecond)  //纳秒
	time.Sleep(time.Microsecond) // 微秒
	time.Sleep(time.Second)      // 秒
	time.Sleep(time.Minute)      // 分钟
	time.Sleep(time.Hour)        //小时
}

2 包

1 介绍

Go语言的源码复用建立在包(package)基础之上,Go语言的入口main() 函数所在的包(package)叫main,main包想要引用别的代码,必须同样以包的方式进行引用


Go 语言的包与文件夹一一对应,所有和包相关的操作,都必须依赖于工作目录(GOPATH)

2 工作目录 GOPATH

GOPATH 是Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录,工作目录是一个工程开发的相对参考目录。


查看windows使用 echo %GOPATH%

linux 查看 echo $GOPATH


在GOPATH指定的工作目录下,代码总会保存在GOPATH/src目录下。在工程经过go build ,go install 或 go get 等指令后,会将产生的二进制可执行文件放置在$GOPATH/bin目录下,生成的中间缓存文件被保存在$GOPATH/pkg下

3 多项目工程中使用GOPATH

GOPATH 一般分为全局GOPATH 和项目GOPATH

Global GOPATH 代表全局GOPATH,一般来源于系统环境变量中,Project GOPATH 代表项目所使用的GOPATH,该设置会被保存在工作目录.idea目录下,建议开发时只写项目GOPATH,每个项目尽量只设置一个GOPATH

4 创建包 package

包 package是多个Go源码的集合,是有种高级的代码复用方案,Go默认为我们提供了很多包,如fmt,os,io包等,开发者可以根据自己的需求创建自己的包


包的三大作用 1 区分相同名字的函数、变量等标识符 2 当程序文件很多时,可以很好的管理项目 3 控制函数,变量等访问范围,及作用域


包的特性 1 一个目录下同级文件归属于一个包 2 包可以与其目录不同名 3 包名为main的包围应用程序的入口包,编译代码没有main包时,将无法编译出可执行文件

5 导出标识符

在Go语言中,若想在一个包里引用另一个包里的标识符时,必须首先将被引用的标识符导出,将要导出的标识符的首字母大写就可以让引用者访问这些标识符

package main

import "fmt"


func Test() {  //首字母大写可被引用 
	fmt.Println("Test 函数")
}

type Struct struct {
}

6 导入包(import)

要引用其他包的标识符,可使用import关键字,导入的包使用双引号包围,包名从GOPATH开始计算路径。使用"/"进行分割

结果如下

7 注意事项

1 在给一个文件打包时,该包下对应一个文件夹,文件的包名通常要和文件所在的文件夹保持一致,一般为小写字母,因为后面的import和调用对应的函数看起来比较方便和协调 2 当一个文件要使用其他包或者函数或变量时,需要先引入对应的包

8 细节说明

1 引入方式 import "包名" 2 引入方式 import ( "包名1" "包名2" "包名3" )

3 package 指令在文件第一行,然后是import指令 4 在import包时,路径从$GOPATH的src开始,不需要带src

9 导入包后自定义

package main

import (
	myTest "gocode/project01/test"
)

//"D:\go\project\src\gocode\project01\main"
func main() {
	myTest.Test()
}

结果如下

10 匿名导包,不使用

package main

import (
	"fmt"
	_ "gocode/project01/test"
)

//"D:\go\project\src\gocode\project01\main"
func main() {
	fmt.Println("匿名导包测试")
}

结果如下

四 错误处理

1 简介

1 Go 语言追求简单优雅,所以不支持传统的try...expoty..finally 处理方式

2 Go 中引入的处理方式:defer ,panic ,recover

3 这几个异常的使用场景可简单描述为: panic 抛出异常,然后defer中通过recover捕获异常进行处理 错误处理的好处

进行错误处理后,程序不会轻易挂掉,如果加入预警代码,可以让程序更加健壮

2 panic 宕机

宕机可能造成体验停止、服务中断

手动触发panic

package main

import "fmt"

func main() {
	fmt.Println("程序执行中")
	panic("crash")  // 其参数可以是任何类型的
	fmt.Println("程序执行完成")

}

结果如下

自动panic

在程序运行过程中,可能由于某些错误导致自动panic

3 recover

无论是代码运行错误由Runtime层抛出的panic崩溃,还是主动触发的panic崩溃,都可以配合defer和recover 实现错误捕捉和恢复,让代码在发生崩溃后允许继续运行


Go 没有异常系统,其使用panic触发宕机类似于其他语言的抛出异常,那么recover的宕机恢复机制就对应try/catch机制

package main

import (
	"fmt"
)

func Test() {   
	defer func() {  
		err := recover() // 捕获异常
		if err != nil {
			fmt.Println("error=", err)
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2  // 此处是分母为0,所以会产生异常
	fmt.Printf("%d", res)
}
func main() {
	fmt.Println("函数执行前操作")
	Test()
	fmt.Println("函数执行后的操作")
}

结果如下

4 error.New

Go 语言支持自定义错误,使用error.New 和 panic内置函数完成

1 error.New("错误说明"),会返回一个error类型的值,表示一个错误 2 panic内置函数,接受一个interface{} 类型的值作为参数,可以接受error类型的变量,输出错误信息,并退出程序

package main

import (
	"errors"
	"fmt"
)

func ReadConf(name string) error {
	if name == "test.txt" {
		fmt.Println("结果正确")
		return nil
	} else {
		return errors.New("读取文件错误,文件不存在.......")
	}
}

func main() {
	err := ReadConf("golang.txt")
	if err != nil {
		fmt.Println("执行结果为:", err)
	}
}

结果如下

5 panic 和defer 及recover

package main

import "fmt"

func main() {
	defer func() {
		err := recover() // 捕捉异常
		if err != nil {
			fmt.Println("其crash 结果为:", err)
			fmt.Println("继续执行下面的语句")
		}
	}()

	panic("程序发生crash")

}

执行结果为

五 容器

1 简介

变量在一定程度上能满足函数及代码的要求,但若要编写一些复杂的算法,结构和逻辑,就需要更复杂的类型来实现,这些复杂类型一般情况下具有各种形式的存储和处理数据的功能,将它们称为"容器"。

2 数组

1 简介

用于存储多个相同类型的空间不可变的数据,其是一段固定长度的连续内存区域

数组可以存放多个同一类型的数据,数组也是一种数据类型,在Go中,数组是值类型

2 数组的使用步骤

1 声明数组 2 初始化数组 3 使用数组

3 数组的定义方式

package main

import (
	"fmt"
)

func main() {
	var arr [5]int = [5]int{} //数组必须指定长度和类型,且长度和类型指定后不可改变 ,类型为Int,长度为5

	fmt.Println(len(arr))              //此时,虽然数组未赋值,则默认为该数组元素对应的默认值
	var arr1 = [5]int{1, 2, 3}         //赋值前3个元素为1,23,
	var arr2 = [4]int{2: 15, 3: 20}    //指定index赋值
	var arr3 = [...]int{1, 2, 3, 4, 5} //通过数组中的元素推测长度
	var arr4 = [...]struct {
		name string
		age  int
	}{{"张三", 40}, {"李四", 50}}
	fmt.Println(arr, arr1, arr2, arr3, arr4)
}

结果如下

image.png

二维数组初始化

package main

import (
	"fmt"
)

func main() {
	var arr = [5][3]int{{1}, {2, 3}} //5行三列,且前2行的所有列还没赋值完成
	fmt.Println(arr)
	var arr1 = [...][2]int{{1}, {2, 3}} // 推测多少行列
	fmt.Println(arr1)

}

image.png

4 数组的访问和内存

数组名[下标] 首元素 arr[0] 末元素 arr[len(arr)-1] 访问二维数组中的元素 arr[2][3] //第三行第四列的元素

package main

import "fmt"

func main() {
	var l1 [10] int //数组定义,  10 为大小,int 为数组存储类型 ,其默认会进行初始化,其初始化值为该类型对应的初始值
	l1[0] = 1
	l1[1] = 2
	for i := 0; i < len(l1); i++ {
		fmt.Println(l1[i])
	}

	//下面表明,数组的首元素内存地址就是数组的地址
	fmt.Printf("数组内存地址为%p\n", &l1)
	fmt.Printf("数组首元素内存地址为%p\n", &l1[0])
	fmt.Printf("数组第二个元素地址为%p\n", &l1[1])
}

结果如下

结论 1 数组的地址可通过数组名来获取 2 数组的第一个元素的地址就是数组的首地址 3 数组各个元素的地址间隔是依据数组的类型决定,如Int64->8字节,int32->4 字节

5 数组遍历

1 常规遍历

通过基本for循环进行遍历操作

2 通过for-range 进行处理
package main

import "fmt"

func main() {
	var x = [4]string{1: "abcd", 2: "goland", 3: "linux", 0: "docker"} //定义数组,其下标从0开始
	for i := 0; i < len(x); i++ {
		fmt.Println(x[i])
	}
	fmt.Println("---------------------")
	for _, value := range x {
		fmt.Println(value)
	}
}

结果如下

cap和len cap 代表capacity 容量 len 代表length长度 len 表示目前数组里面有几个元素,cap 代表给数组分配的内存空间可以容纳多少元素 由于数组初始化之后长度不会改变,因此不需要给其预留内存空间,所以对于数组 len(arr)=cap(arr)

数组传参 数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合

go没有按引用传参,全都是按值传参,即传递数组实际是数组的拷贝,当数组的长度很大时,仅传参开销都很大 如果想修改函数外部的数组,就把他的指针传递进来

6 注意事项

1 数组是将多个相同类型的数据进行组合的概念,一旦数组声明/定义了,其长度是固定的,不可修改的 2 var arr []int 是一个切片 3 数组中的元素可以是任意类型,包括值类型和引用类型,但不能混合 4 数组创建后,若没有赋值,则有默认值 数值类型的数组:默认为0 字符类型数组:默认为空 bool 类型的数组,默认为false


6 数组的下标是从0开始的 7 数组下标必须在指定范围内,否则会报错panic,数组越界 8 Go的数组属于值类型,在默认情况下是值传递,因此会被进行值拷贝,数组间不会相互影响 9 如果想在其他函数中修改数组,则可使用值传递方式进行 10 长度是数组类型的一部分,在传递函数参数时,需要考虑数组的长度

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano()) //用于生成随机数字
	var l1 [5] int
	for i := 0; i < len(l1); i++ {
		num := rand.Intn(100) //生成100以内的数字
		l1[i] = num           //生成数组
	}
	fmt.Println("打印数组的值:", l1)
	var l2 [5]int
	for i := 0; i < len(l2); i++ {
		l2[len(l2)-i-1] = l1[i]
	}
	fmt.Println("翻转结果为:", l2)
}

结果如下

7 使用案例

1 创建一个类型为byte的26 个元素的数组,分别放置A-Z,并使用for循环访问所有元素并打印
package main

import "fmt"

func main() {
	var l1 [26]byte
	a := 0
	for i := 'A'; i <= 'Z'; i++ {
		l1[a] = byte(i)
		a += 1
	}
	for _, value := range l1 {
		fmt.Printf("%c", value)
	}
}

结果如下

2 求一个数组的最大值,并得到对应的下标
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	var l1 [10]int
	// 获取随机数种子
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len(l1); i++ {
		num := rand.Intn(100)
		l1[i] = num
	}
	fmt.Println("获取到数组为:", l1)
	maxindex := 0
	max := l1[0]
	for index, value := range l1 {
		if max < value {
			max = value
			maxindex = index
		}
	}
	fmt.Printf("索引为:%d,对应值为%d", maxindex, max)
}

结果如下

3 求一个数组的平均值
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	var l1 [10]int
	// 获取随机数种子
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len(l1); i++ {
		num := rand.Intn(100)
		l1[i] = num
	}
	fmt.Println("获取到数组为:", l1)
	sum := 0
	for _, value := range l1 {
		sum += value
	}
	fmt.Printf("平均值为:%d", sum/len(l1))
}

结果如下

2 数组切片

1 简介

Go语言切片的内部结构包括地址、大小和容量,切片一般用于快速操作一块数集合。

切片struct 内容 type slice struct { array unsafe.Pointer len int cap int
}


基本介绍 1 切片的英文是slice 2 切片是数组的一个引用,因此切片是引用类型,在进行值传递时,遵循引用传递的规则机制 3 切片的使用和数组相似,遍历切片,访问切片元素和求切片长度都是一样的 4 切片的长度可以变化的,因此切片是一个动态变化的数组 5 切片的定义基本语法 var 变量名 [] 变量类型

2 切片声明

1 定义一个切片,然后让切片引用一个已经创建好的数组

2 通过make来创建切片

var xxx []type = make([],len,[cap]) type 表示类型 len 表示长度大小 cap 表示指定切片的容量

package main

import "fmt"

func main() {
	// 定义方式一,直接使用截取的数组进行处理
	var l1 = [5]int{1, 2, 3, 4, 5}
	l2 := l1[1:4] // 获取切片并赋值
	fmt.Printf("类型为:%T,值为:%d\n", l2, l2)

	//定义方式二,通过标准方式进行定义和执行
	var l3 []int
	l3 = make([]int, 4, 10)  //其容量可省略
	l3[0] = 10
	l3[1] = 20
	fmt.Printf("类型为:%T,值为%d\n", l3, l3)

	//定义方式三,直接定义,并指定其值
	var l4 []int = []int{1, 2, 3, 4, 5}
	fmt.Printf("类型为:%T,值为%d\n", l4, l4)
}

package main

import (
	"fmt"
)

func main() {
	var s []int          //声明切片,len=cap=0,默认声明不会给空间
	s = []int{}          //初始化切片 len=cap=0
	s1 := make([]int, 3) //初始化 len=cap=3
	s2 := make([]int, 3, 5)
	s3 := []int{1, 2, 3, 4, 5} //初始化len=cap=5
	fmt.Println(s, s1, s2, s3)
}

结果如下

3 切片在内存中的形式

1 数组在内存中的形式是连续的,切片的底层数据结构是一个结构体,slice是一个引用类型,其是通过指针,相关长度和容量组成的

通过make 方式创建切片可以指定切片的大小和容量,如果没有给切片的各个元素赋值,则其会处理成对应的默认值

通过make创建的切片对应的数组是由make底层维护的,对外不可见,只能通过访问各个元素获取

4 切片的注意事项

1 切片初始化时 var slice=arr[startindex:endindex],左闭右开
2 切片初始化时,仍然不能越界,范围在[0-len(arr)]之间,但是可以动态增长 3 cap 是一个内置函数,用于统计切片的容量,及最大可以存放多少个元素 4 切片定义完成后,还不能使用,需要引用一个数组或make一个空间后才能进行相关的使用 5 切片可继续切片 6 切片的遍历和数组相同,也是通过for循环及for range 进行处理的

5 append

使用append内置函数,可对切片进行动态增加 切片相对于数组最大的特点就是可以追加元素,可以自动扩容 追加的元素放置到预留的内存中,同时len 加1
如果预留空间已用完,则会重新申请一块更大的内存空间,capacity大约会变成之前的2倍(cap<1024)或1.25倍(cap>1024),把原内存空间的数据拷贝过来,在新的内存空间上执行append 1 切片append操作的底层原理分析

1 切片的append操作的本质是对数组的扩容 2 Go 底层会创建一个新的数组 3 将原来的数据包含的元素全部拷贝到新的数组上 4 将原来数组的引用拷贝到新的数组上即可,新数组是底层维护,不可见

package main

import (
	"fmt"
)

func main() {
	var s []int
	var s2 []int             //声明切片 ,len=cap=0
	s = []int{}              //初始化 ,len=cap=0
	s = make([]int, 3)       //初始化,len=cap=3
	s2 = make([]int, 3, 5)   //初始化,len =3  cap=5
	s = []int{1, 2, 3, 4, 5} //初始化 len=cap=5
	s2d := [][]int{
		{1, 2}, {1, 2, 3}, // 二维切片,每一行的列数可以不一致
	}
	fmt.Println(s2d)
	fmt.Println(s)
	fmt.Println(s)
	procap := cap(s2)
	for i := 0; i < 1500; i++ {
		s2 = append(s2, i)
		statuscap := cap(s2)
		if procap < statuscap {
			fmt.Println(procap, statuscap)
			fmt.Printf("首元素地址:%p\n", &s2[0])
			procap = statuscap
		}
	}

}

image.png

package main

import (
	"fmt"
)

func main() {
	arr := make([]int, 3, 5)
	brr := append(arr, 10)
	fmt.Println(&arr[0], &brr[0], len(arr), len(brr))
}

image.png

截取子切片

package main

import (
	"fmt"
)

func main() {
	arr := make([]int, 3, 5) //len=3,cap=5
	sub_slice := arr[1:3]    //  len=2,cap=4
	fmt.Println(&arr[1], &sub_slice[0], arr)
	sub_slice = append(sub_slice, 4)
	fmt.Println(&arr[1], &sub_slice[0], arr)
	sub_slice = append(sub_slice, 5)
	fmt.Println(&arr[1], &sub_slice[0], arr)
	sub_slice = append(sub_slice, 6) // 当sub 增加超过cap 长度时,其会发生分离
	fmt.Println(&arr[1], &sub_slice[0], arr)
	sub_slice = append(sub_slice, 7)
	fmt.Println(&arr[1], &sub_slice[0], arr)
}

image.png 结论: 刚开始,子切片和母切片共享底层的内存空间,修改子切片会反映到母切片上,在子切片上执行append会将新元素放入母切片预留的内存空间上,当子切片不断执行append,耗完了母切片预留的内存空间,子切片跟母切片就会发生分离,此后两个切片就再没任何关系

切片传参

go语言函数传参,传递的都是值,及切片会将切片的{arrPointer,len,cap }这三个字段拷贝一份传递进来,由于传递的是底层数组的指针,因此可以直接修改底层数据中的元素

package main

import (
	"fmt"
)

func slice_test(arr []int) {
	fmt.Println(arr)
	arr[0] = 100
}

func slice_test1(arr *[]int) {
	fmt.Println(*arr)
	(*arr)[0] = 300
}
func main() {
	arr := []int{1, 2, 3, 4, 5}
	fmt.Println(arr)
	slice_test(arr)
	fmt.Println(arr)
	slice_test1(&arr)
	fmt.Println(arr)
}

image.png

6 切片的copy

package main

import "fmt"

func main() {
	var l1 = []int{1, 2, 3, 4, 5, 6}
	l1 = append(l1, 7)
	l1 = append(l1, 8)
	fmt.Println(l1)
	var l2 [] int
	l2 = make([]int, 10, 20)
	l2[0] = 1
	l2[1] = 2
	fmt.Printf("长度为:%d,容量为:%d,实际值为:%d\n", len(l2), cap(l2), l2)
	copy(l2, l1) //后面会覆盖前面的
	fmt.Println(l2)
}

结果如下

7 切片删除元素

切片未提供专门的语法或接口,需要使用切片本身的特性来删除元素

package main

import "fmt"

func main() {
	var x = []int{1, 2, 3, 4, 5, 6, 7}  
	x = append(x[:2], x[3:]...) //切片删除,删除标号为2 的数字 ,此处的append可使用多参数传值进行处理
	fmt.Println(x)
}

结果如下

8 切片练习

1 生成斐波那契数列,并放置到切片中
package main

import "fmt"

func Test(n int) []int {
	l1 := make([]int, n)
	a, b := 0, 1
	for i := 0; i < n; i++ {
		l1[i] = b
		a, b = b, a+b

	}
	return l1
}

func main() {
	l2 := Test(10)
	fmt.Println(l2)
}

结果如下

3 map

1 简介

Go语言提供的映射关系容器称为map,map使用散列表(hash)实现

大多数语言中映射关系容器使用两种算法:散列表和平衡树


散列表:可简单描述为一个数组,数组中的每一个元素都是一个列表,根据散列函数获得每一个元素的特征值,将这些特征值作为映射的键,如果特征值重复,就表示元素发生碰撞,碰撞的元素将被放在同一个特征值的列表中进行保存,散列表查找复杂度O(1),和数组一致,最坏的情况是O(n),n为元素总数,散列需要尽量避免元素碰撞以提高查找效率,这样就需要对桶进行扩容,每次扩容,元素都需要被重新放入桶中,较为耗时。


平衡树:类似于有父子关系的一颗数据树,每个元素在放入树时,都需要与一些节点进行比较,平衡树的查找复杂度始终为O(log n)

2 map 使用的三种方式

package main

import "fmt"

func main() {
	//定义方式一
	var d map[string]string
	d = make(map[string]string)
	d["a"] = "1234"
	d["b"] = "5678"
	fmt.Println(d)
	//定义方式二
	d1 := make(map[string]string)
	d1["a"] = "12345678"
	fmt.Println(d1)
	//定义方式三
	var d2 map[string]string = map[string]string{
		"a": "golang",
		"b": "mysql",
	}
	fmt.Println(d2)
}

结果为

2 多级map

package main

import "fmt"

func main() {
	var d map[string]map[string]string
	d = make(map[string]map[string]string) //初始化外部map
	d["a"] = make(map[string]string)       //初始化内部map
	d["a"]["a"] = "mysql"
	d["a"]["b"] = "linux"
	d["b"] = make(map[string]string) //初始化内部map
	d["b"]["a"] = "mysql"
	d["b"]["b"] = "linux"
	fmt.Println(d)
}

结果如下

3 map 操作

增删改查


清空map:Go 语言中并没有为map提供任何清空所有元素的函数或方法,清空map唯一的办法就是重新make一个新的map。

package main

import "fmt"

func main() {
	var d map[string]map[string]string
	d = make(map[string]map[string]string) //初始化外部map
	d["a"] = make(map[string]string)       //初始化内部map
	//增加
	d["a"]["a"] = "mysql"
	d["a"]["b"] = "linux"
	d["a"]["c"] = "golang"
	fmt.Println(d["a"]["c"]) //查询
	delete(d, "a")           //删除,并删除其类型

	d["b"] = make(map[string]string) //初始化内部map
	//增加
	d["b"]["a"] = "mysql"
	d["b"]["b"] = "linux"
	d["b"]["c"] = "golang"
	for _, value := range d {
		fmt.Println(value)
	}
	d["b"]["a"] = "111"
	d["b"]["b"] = "222"
	d["b"]["c"] = "333"
	for _, value := range d {
		fmt.Println(value)
	}

}

结果如下

4 map 使用细节

1 map是引用类型,遵循值传递过程,在一个函数接受一个map,修改后,直接修改原来的map 2 map 的容量到达后,想在增加元素,会自动扩容,并不会发生panic,也就是说map能动态的增证键值对 3 map 的value经常使struct 类型,更加适合管理复杂数据

5 map 排序

基本介绍 1 golang 没有专门针对map的key 进行排序的方式 2 golang中的map默认是无序的,其也不是按顺序添加的,每次遍历,其结果都可能不同 3 golang中的map排序,是先将key进行排序,然后根据key取出value并进行排序

4 map 切片

1 基本介绍

切片数据类型若是map。则map可进行动态变化了

package main

import "fmt"

func main() {
	var d []map[string]string //此处相当于切片套map
	d = make([]map[string]string, 2)
	if d[0] == nil {
		d[0] = make(map[string]string)
		d[0]["a"] = "linux"
		d[0]["b"] = "windows"
	}
	fmt.Println(d)
}

结果如下