golang语言_2

init函数
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也 就是说 init 会在 main 函数前被调用。可以做初始化操作。

func init() {
	fmt.Println(123)
}

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

init函数的注意事项和细节
1)如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init 函数->main 函数
匿名函数
Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考 虑使用匿名函数,匿名函数也可以实现多次调用。
(1)在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。

func main() {
	res1 := func(n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)
	fmt.Println(res1)
}

(2)将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数

func main() {
	a := func(n1 int, n2 int) int {
		return n1 - n2
	}
	res2 := a(10, 30)
	fmt.Println(res2)
}

(3)全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序 有效

var (
	fun1 = func(n1 int, n2 int) int {
		return n1 * n2
	}
)

闭包
基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
1)AddUpper 是一个函数,返回的数据类型是 fun (int) int
2)返回的是一个匿名函数, 但是这个匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一 个整体,构成闭包。
3)大家可以这样理解: 闭包是类, 函数是操作,n 是字段。函数和它使用到 n 构成闭包。
4)当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计。
5)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引 用到的变量共同构成闭包。

func AddUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}
func main() {
	//累加器
	f := AddUpper()
	fmt.Println(f(2))
}

** 函数的 defer**
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完 毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
defer 的注意事项和细节
1)当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈 中, 然后继续执行函数下一个语句。
2)当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),
3)在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈
defer的最佳实战
1)在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是 锁资源), 可以执行 defer file.Close() defer connect.Close()
2)在 defer 后,可以继续使用创建资源.
3)当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
4)这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
两种传递方式
1)值传递
2)引用传递 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的 拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低。
值类型和引用类型
1)值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
2)引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
变量作用域
1)函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用 域在整个程序有效
3)如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
字符串常用的系统函数
1)统计字符串的长度,按字节 len(str)

func main() {
	str := "hello北"
	fmt.Println("str len=", len(str))
}

2)字符串遍历,同时处理有中文的问题 r := []rune(str)

	str2 := "hello北京"
	//字符串遍历,同时处理有中文的问题 r := []rune(str)
	r := []rune(str2)
	for i := 0; i < len(r); i++ {
		fmt.Printf("字符=%c\n", r[i])
	}

3)字符串转整数: n, err := strconv.Atoi(“12”)

	n, err := strconv.Atoi("123")
	if err != nil {
		fmt.Println("转换错误", err)
	}else {
		fmt.Println("转成的结果是", n)
	}

4)整数转字符串 str = strconv.Itoa(12345)

	str = strconv.Itoa(12345)
	fmt.Printf("str=%v, str=%T\n", str, str)

5)字符串 转 []byte: var bytes = []byte(“hello go”)

	var bytes = []byte("hello go")
	fmt.Printf("bytes=%v\n", bytes)

6)[]byte 转 字符串: str = string([]byte{97, 98, 99})

	str = string([]byte{97, 98, 99}) 
	fmt.Printf("str=%v\n", str)

7)10 进制转 2, 8, 16 进制: str = strconv.FormatInt(123, 2) // 2-> 8 , 16

	str = strconv.FormatInt(123, 2)
	fmt.Printf("123对应的二进制是=%v\n", str)
	str = strconv.FormatInt(123, 16)
	fmt.Printf("123对应的16进制是=%v\n", str)

8)查找子串是否在指定的字符串中: strings.Contains(“seafood”, “foo”) //true

b := strings.Contains("seafood", "mary")
	fmt.Printf("b=%v\n", b)

9)统计一个字符串有几个指定的子串 : strings.Count(“ceheese”, “e”) //4

num := strings.Count("ceheese", "e")
	fmt.Printf("num=%v\n", num)

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

b = strings.EqualFold("abc", "Abc")
	fmt.Printf("b=%v\n", b) //true

	fmt.Println("结果","abc" == "Abc") // false //区分字母大小写

11)返回子串在字符串第一次出现的 index 值,如果没有返回-1 : strings.Index(“NLT_abc”, “abc”) // 4

index := strings.Index("NLT_abcabcabc", "abc") // 4
	fmt.Printf("index=%v\n",index)

12)返回子串在字符串最后一次出现的 index,如没有返回-1 : strings.LastIndex(“go golang”, “go”)

index = strings.LastIndex("go golang", "go") //3
	fmt.Printf("index=%v\n",index)

13)将指定的子串替换成 另外一个子串: strings.Replace(“go go hello”, “go”, “go 语言”, n) n 可以指 定你希望替换几个,如果 n=-1 表示全部替换

str2 = "go go hello"
	str = strings.Replace(str2, "go", "北京", -1)
	fmt.Printf("str=%v str2=%v\n", str, str2)

14)按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组 : strings.Split(“hello,wrold,ok”, “,”)

//strings.Split("hello,wrold,ok", ",")
	strArr := strings.Split("hello,wrold,ok", ",")
	for i := 0; i < len(strArr); i++ {
		fmt.Printf("str[%v]=%v\n", i, strArr[i])
	} 
	fmt.Printf("strArr=%v\n", strArr)

15)将字符串的字母进行大小写的转换: strings.ToLower(“Go”) // go strings.ToUpper(“Go”) // GO

str = "goLang Hello"
	str = strings.ToLower(str) 
	str = strings.ToUpper(str) 
	fmt.Printf("str=%v\n", str) //golang hello

16)将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")

str = strings.TrimSpace(" tn a lone gopher ntrn   ")
	fmt.Printf("str=%q\n", str)

17)将字符串左右两边指定的字符去掉 : strings.Trim("! hello! “, " !”) // [“hello”] //将左右两边 ! 和 " "去掉

str = strings.Trim("! he!llo! ", " !")
	fmt.Printf("str=%q\n", str)

18)将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! “, " !”) // [“hello”] //将左边 ! 和 " “去掉
19)将字符串右边指定的字符去掉 :strings.TrimRight(”! hello! “, " !”) // [“hello”] //将右边 ! 和 " "去掉
20)判断字符串是否以指定的字符串开头: strings.HasPrefix(“ftp://192.168.10.1”, “ftp”) // true

b = strings.HasPrefix("ftp://192.168.10.1", "hsp") //true
	fmt.Printf("b=%v\n", b)

21)判断字符串是否以指定的字符串结束: strings.HasSuffix(“NLT_abc.jpg”, “abc”) //false
时间和日期相关函数
1)时间和日期相关函数,需要导入 time 包
2)time.Time 类型,用于表示时间

now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)

3)如何获取到其它的日期信息

//2.通过now可以获取到年月日,时分秒
	fmt.Printf("年=%v\n", now.Year())
	fmt.Printf("月=%v\n", now.Month())
	fmt.Printf("月=%v\n", int(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())

4)格式化日期时间
方式 1: 就是使用 Printf 或者 SPrintf

	fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

	dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
	fmt.Printf("dateStr=%v\n", dateStr)

方式二: 使用 time.Format() 方法完成:

//格式化日期时间的第二种方式
	fmt.Printf(now.Format("2006-01-02 15:04:05"))
	fmt.Println()
	fmt.Printf(now.Format("2006-01-02"))
	fmt.Println()
	fmt.Printf(now.Format("15:04:05"))
	fmt.Println()

	fmt.Printf(now.Format("2006"))
	fmt.Println()

5)时间的常量

const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)

6)结合 Sleep 来使用一下时间常量

i := 0
	for {
		i++
		fmt.Println(i)
		//休眠
		//time.Sleep(time.Second)
		time.Sleep(time.Millisecond * 100)
		if i == 100 {
			break
		}
	}

7)time 的 Unix 和 UnixNano 的方法

fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())

编写一段代码来统计 函数 test03 执行的时间

func test03() {

	str := ""
	for i := 0; i < 100000; i++ {
		str += "hello" + strconv.Itoa(i)
	}
}

func main() {
	//在执行test03前,先获取到当前的unix时间戳
	start := time.Now().Unix()
	test03()
	end := time.Now().Unix()
	fmt.Printf("执行test03()耗费时间为%v秒\n", end-start)
}

内置函数

1)len:用来求长度,比如 string、array、slice、map、channel
2)new:用来分配内存,主要用来分配值类型,比如 int、float32,struct…返回的是指针
3)make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice(后面在写相关笔记)
错误处理
1)在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
2)如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可 以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
3)这里引出错误处理机制
1)Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
2)Go 中引入的处理方式为:defer, panic, recover
3)这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中 通过 recover 捕获这个异常,然后正常处理
使用 defer+recover 来处理错误

func test() {
	//使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover() // recover()内置函数,可以捕获到异常
		if err != nil {  // 说明捕获到错误
			fmt.Println("err=", err)
			//这里就可以将错误信息发送给管理员....
			fmt.Println("发送邮件给admin@sohu.com~")
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}
func main() {

	//测试
	 test()
	 for {
	 	fmt.Println("main()下面的代码...")
	 	time.Sleep(time.Second)
	 }
}

自定义错误的介绍

//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
	if name == "config.ini" {
		//读取...
		return nil
	} else {
		//返回一个自定义错误
		return errors.New("读取文件错误..")
	}
}
func test02() {
	err := readConf("config2.ini")
	if err != nil {
		//如果读取文件发送错误,就输出这个错误,并终止程序
		panic(err)
	}
	fmt.Println("test02()继续执行....")
}
func main() {
	test02()
	fmt.Println("main()下面的代码...")
}

数组的定义和内存布局
var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30 …
数组在内存布局(重要)
在这里插入图片描述
1)数组的地址可以通过数组名来获取 &intArr
2)数组的第一个元素的地址,就是数组的首地址
3)数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4…
数组的使用

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

	var numArr02 = [3]int{5, 6, 7}
	fmt.Println("numArr02=", numArr02)
	//这里的 [...] 是规定的写法
	var numArr03 = [...]int{8, 9, 10}
	fmt.Println("numArr03=", numArr03)

	var numArr04 = [...]int{1: 800, 0: 900, 2:999}
	fmt.Println("numArr04=", numArr04)

	//类型推导
	strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}
	fmt.Println("strArr05=", strArr05)

数组的遍历
for-range 结构遍历

//演示for-range遍历数组
	 heroes  := [...]string{"宋江", "吴用", "卢俊义"}
	 
	for i, v := range heroes {
		fmt.Printf("i=%v v=%v\n", i , v)
		fmt.Printf("heroes[%d]=%v\n", i, heroes[i])
	}

	for _, v := range heroes {
		fmt.Printf("元素的值=%v\n", v)
	}

数组使用的注意事项和细节
1)数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化

func main() {
	//数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化。
	var arr01 [3]int
	arr01[0] = 1
	arr01[1] = 30
	//这里会报错
	arr01[2] = 1.1  
	//其长度是固定的, 不能动态变化,否则报越界
	arr01[3] = 890

	fmt.Println(arr01)
}

2)var arr []int 这时 arr 就是一个 slice 切片,切片后面专门讲解,不急哈.
3)数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
4)数组创建后,如果没有赋值,有默认值(零值)

//数组创建后,如果没有赋值,有默认值(零值)
	//1. 数值(整数系列, 浮点数系列) =>0
	//2. 字符串 ==> ""
	//3. 布尔类型 ==> false

	var arr01 [3]float32
	var arr02 [3]string
	var arr03 [3]bool
	fmt.Printf("arr01=%v arr02=%v arr03=%v\n", arr01, arr02, arr03)

5)使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值(默认零值) 3 使用数组
6)数组的下标是从 0 开始的
7)数组下标必须在指定范围内使用,否则报 panic:数组越界,比如 var arr [5]int 则有效下标为 0-4
8)Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响

func test01(arr [3]int) {
	arr[0] = 88
} 
func main() {
	//Go的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响

	arr := [3]int{11, 22, 33}
	test01(arr)
	fmt.Println("main arr=", arr) //
}

9)如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)

//函数
func test02(arr *[3]int) {
	fmt.Printf("arr指针的地址=%p", &arr)
	(*arr)[0] = 88 //!!
} 

arr := [3]int{11, 22, 33}
	fmt.Printf("arr 的地址=%p", &arr)
	test02(&arr)
	fmt.Println("main arr=", arr)

10)长度是数组类型的一部分,在传递函数参数时 需要考虑数组的长度
长度是数组类型的一部分
数组的应用案例
1)创建一个 byte 类型的 26 个元素的数组,分别 放置’A’-'Z‘。使用 for 循环访问所有元素并打印 出来。提示:字符数据运算 ‘A’+1 -> 'B

//思路
	//1. 声明一个数组 var myChars [26]byte
	//2. 使用for循环,利用 字符可以进行运算的特点来赋值 'A'+1 -> 'B'
	//3. 使用for打印即可
	//代码:
	var myChars [26]byte
	for i := 0; i < 26; i++ {
		myChars[i] = 'A' + byte(i) // 注意需要将 i => byte
	}

	for i := 0; i < 26; i++ {
		fmt.Printf("%c ", myChars[i])
	}

2)请求出一个数组的最大值,并得到对应的下标。

//思路
	//1. 声明一个数组 var intArr[5] = [...]int {1, -1, 9, 90, 11}
	//2. 假定第一个元素就是最大值,下标就0
	//3. 然后从第二个元素开始循环比较,如果发现有更大,则交换
	var intArr [6]int = [...]int {1, -1, 9, 90, 11, 9000}
	maxVal := intArr[0]
	maxValIndex := 0

	for i := 1; i < len(intArr); i++ {
		//然后从第二个元素开始循环比较,如果发现有更大,则交换
		if maxVal < intArr[i] {
			maxVal = intArr[i]
			maxValIndex = i
		}
	}
	fmt.Printf("maxVal=%v maxValIndex=%v\n\n", maxVal, maxValIndex)

3)请求出一个数组的和和平均值。for-range

//思路
	//1. 就是声明一个数组  var intArr[5] = [...]int {1, -1, 9, 90, 11}
	//2. 求出和sum
	//3. 求出平均值
	//代码
	var intArr2 [5]int = [...]int {1, -1, 9, 90, 12}
	sum := 0
	for _, val := range intArr2 {
		//累计求和
		sum += val
	}
	//如何让平均值保留到小数.
	fmt.Printf("sum=%v 平均值=%v \n\n", sum, float64(sum) / float64(len(intArr2)))

4)要求:随机生成五个数,并将其反转打印 , 复杂应用

//思路
	//1. 随机生成五个数 , rand.Intn() 函数
	//2. 当我们得到随机数后,就放到一个数组 int数组
	//3. 反转打印 , 交换的次数是  len / 2, 倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换

	var intArr3 [5]int 
	//为了每次生成的随机数不一样,我们需要给一个seed值
	len := len(intArr3)
	
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len; i++ {
		intArr3[i] = rand.Intn(100) //  0<=n<100
	}

	fmt.Println("交换前~=", intArr3)
	//反转打印 , 交换的次数是  len / 2, 
	//倒数第一个和第一个元素交换, 倒数第2个和第2个元素交换
	temp := 0  //做一个临时变量
	for i := 0; i < len / 2; i++ {
		temp = intArr3[len - 1 - i]  
		intArr3[len - 1 - i] = intArr3[i]
		intArr3[i] = temp
	}

	fmt.Println("交换后~=", intArr3)

切片

切片的基本介绍
1)切片的英文是 slice
2)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
3)切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
4)切片的长度是可以变化的,因此切片是一个可以动态变化数组。
5)切片定义的基本语法: var 切片名 []类型 比如:var a [] int

  1. slice 的确是一个引用类型
  2. slice 从底层来说,其实就是一个数据结构(struct 结构体)
    type slice struct { ptr *[2]int len int cap int }
    切片的使用
    第一种方式:定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例就是这 样的。
var intArr [5]int = [...]int{1, 22, 33, 66, 99}
	//声明/定义一个切片
	slice := intArr[1:3] 
	fmt.Println("intArr=", intArr)
	fmt.Println("slice 的元素是 =", slice) //  22, 33
	fmt.Println("slice 的元素个数 =", len(slice)) // 2
	fmt.Println("slice 的容量 =", cap(slice)) // 切片的容量是可以动态变化  

第二种方式:通过 make 来创建切片. 基本语法:var 切片名 []type = make([]type, len, [cap])
参数说明: type: 就是数据类型 len : 大小 cap :指定切片容量,可选, 如果你分配了 cap,则要 求 cap>=len.

//演示切片的使用 make
	var slice []float64 = make([]float64, 5, 10)
	slice[1] = 10
	slice[3] = 20
	//对于切片,必须make使用.
	fmt.Println(slice)
	fmt.Println("slice的size=", len(slice))
	fmt.Println("slice的cap=", cap(slice))

对上面代码的小结:
1)通过 make 方式创建切片可以指定切片的大小和容量
2)如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]
3)通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去 访问各个元素.
第 3 种方式:定义一个切片,直接就指定具体数组,使用原理类似 make 的方式

	fmt.Println()
	//第3种方式:定义一个切片,直接就指定具体数组,使用原理类似make的方式
	var strSlice []string = []string{"tom", "jack", "mary"}
	fmt.Println("strSlice=", strSlice)
	fmt.Println("strSlice size=", len(strSlice)) //3
	fmt.Println("strSlice cap=", cap(strSlice)) // ?

在这里插入图片描述
切片的遍历
for 循环常规方式遍历

//使用常规的for循环遍历切片
	var arr [5]int = [...]int{10, 20, 30, 40, 50}
	//slice := arr[1:4] // 20, 30, 40
	slice := arr[1:4]
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v]=%v ", i, slice[i])
	}

	fmt.Println()

for-range 结构遍历切片

//使用for--range 方式遍历切片
	for i, v := range slice {
		fmt.Printf("i=%v v=%v \n", i, v)
	}

切片的使用的注意事项和细节讨论
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[:]

3)cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
4)切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一 个空间供切片来使用
5)切片可以继续切片

slice2 := slice[1:2] //  slice [ 20, 30, 40]    [30]
	slice2[0] = 100  // 因为arr , slice 和slice2 指向的数据空间是同一个,因此slice2[0]=100,其它的都变化
	fmt.Println("slice2=", slice2)
	fmt.Println("slice=", slice)
	fmt.Println("arr=", arr)

6)用 append 内置函数,可以对切片进行动态追加

//用append内置函数,可以对切片进行动态追加
	var slice3 []int = []int{100, 200, 300}
	//通过append直接给slice3追加具体的元素
	slice3 = append(slice3, 400, 500, 600)
	fmt.Println("slice3", slice3) //100, 200, 300,400, 500, 600

	//通过append将切片slice3追加给slice3
	slice3 = append(slice3, slice3...) // 100, 200, 300,400, 500, 600 100, 200, 300,400, 500, 600
	fmt.Println("slice3", slice3)

切片 append 操作的底层原理分析: 切片 append 操作的本质就是对数组扩容
go 底层会创建一下新的数组 newArr(安装扩容后大小)
将 slice 原来包含的元素拷贝到新的数组 newArr
slice 重新引用到 newArr
注意 newArr 是在底层来维护的,程序员不可见
7)切片的拷贝操作 切片使用 copy 内置函数完成拷贝

//切片使用copy内置函数完成拷贝,举例说明
	fmt.Println()
	var slice4 []int = []int{1, 2, 3, 4, 5}
	var slice5 = make([]int, 10)
	copy(slice5, slice4)
	fmt.Println("slice4=", slice4)// 1, 2, 3, 4, 5
	fmt.Println("slice5=", slice5) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0

(1)copy(para1, para2) 参数的数据类型是切片
(2)按照上面的代码来看, slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999, slice5[0] 仍然是 1

string 和 slice
1)string 底层是一个 byte 数组,因此 string 也可以进行切片处理 案例演示:

//string底层是一个byte数组,因此string也可以进行切片处理
	str := "hello@atguigu"
	//使用切片获取到 atguigu
	slice := str[6:] 
	fmt.Println("slice=", slice)

2)string 和切片在内存的形式,以 “abcd” 画出内存示意图
3)string 是不可变的,也就说不能通过 str[0] = ‘z’ 方式来修改字符串
4)如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string

	//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
	//"hello@atguigu" =>改成 "zello@atguigu"
	arr1 := []byte(str) 
	arr1[0] = 'z'
	str = string(arr1)
	fmt.Println("str=", str)
	// 细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文
	// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
	// 解决方法是 将  string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字

冒泡排序实现

//冒泡排序
func BubbleSort(arr *[5]int) {
	fmt.Println("排序前arr=", (*arr))
	temp := 0 //临时变量(用于做交换)
	//冒泡排序..一步一步推导出来的
	for i :=0; i < len(*arr) - 1; i++ {
		for j := 0; j < len(*arr) - 1 - i; j++ {
			if (*arr)[j] > (*arr)[j + 1] {
				//交换
				temp = (*arr)[j]
				(*arr)[j] = (*arr)[j + 1]
				(*arr)[j + 1] = temp
			}
		}
	}
	fmt.Println("排序后arr=", (*arr))

}
func main() {
	//定义数组
	arr := [5]int{24,69,80,57,13}
	//将数组传递给一个函数,完成排序
	BubbleSort(&arr)
	fmt.Println("main arr=", arr) //有序? 是有序的
}

查找

//有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王
	//猜数游戏:从键盘中任意输入一个名称,判断数列中是否包含此名称【顺序查找】
	//思路
	//1 定义一个数组, 白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王 字符串数组
	//2.从控制台接收一个名字,依次比较,如果发现有,提示

	//代码
	names := [4]string{"白眉鹰王", "金毛狮王", "紫衫龙王", "青翼蝠王"}
	var heroName = ""
	fmt.Println("请输入要查找的人名...")
	fmt.Scanln(&heroName)
	//顺序查找:第2种方式
	index := -1

	for i := 0; i < len(names); i++ {
		if heroName == names[i] {
			index = i //将找到的值对应的下标赋给 index
			break
		} 
	}
	if index != -1 {
		fmt.Printf("找到%v , 下标%v \n", heroName, index)
	} else {
		fmt.Println("没有找到", heroName)
	}

二分查找

//二分查找的函数
/*
二分查找的思路: 比如我们要查找的数是 findVal
1. arr是一个有序数组,并且是从小到大排序
2.  先找到 中间的下标 middle = (leftIndex + rightIndex) / 2, 然后让 中间下标的值和findVal进行比较
2.1 如果 arr[middle] > findVal ,  就应该向  leftIndex ---- (middle - 1)
2.2 如果 arr[middle] < findVal ,  就应该向  middel+1---- rightIndex
2.3 如果 arr[middle] == findVal , 就找到
2.4 上面的2.1 2.2 2.3 的逻辑会递归执行
3. 想一下,怎么样的情况下,就说明找不到[分析出退出递归的条件!!]
if  leftIndex > rightIndex {
   // 找不到..
   return ..
}
*/
func BinaryFind(arr *[6]int, leftIndex int, rightIndex int, findVal int) {

	//判断leftIndex 是否大于 rightIndex
	if leftIndex > rightIndex {
		fmt.Println("找不到")
		return
	}

	//先找到 中间的下标
	middle := (leftIndex + rightIndex) / 2

	if (*arr)[middle] > findVal {
		//说明我们要查找的数,应该在  leftIndex --- middel-1
		BinaryFind(arr, leftIndex, middle - 1, findVal)
	} else if (*arr)[middle] < findVal {
		//说明我们要查找的数,应该在  middel+1 --- rightIndex
		BinaryFind(arr, middle + 1, rightIndex, findVal)
	} else {
		//找到了
		fmt.Printf("找到了,下标为%v \n", middle)
	}
}

func main() {
	arr := [6]int{1,8, 10, 89, 1000, 1234}
	//测试一把
	BinaryFind(&arr, 0, len(arr) - 1, -6)
}

二维数组
使用方式 1: 先声明/定义,再赋值
语法: var 数组名 [大小][大小]类型
比如: var arr [2][3]int , 再赋值。

var arr2 [2][3]int //以这个为例来分析arr2在内存的布局!!
	arr2[1][1] = 10
	fmt.Println(arr2)
	fmt.Printf("arr2[0]的地址%p\n", &arr2[0])
	fmt.Printf("arr2[1]的地址%p\n", &arr2[1])

	fmt.Printf("arr2[0][0]的地址%p\n", &arr2[0][0])
	fmt.Printf("arr2[1][0]的地址%p\n", &arr2[1][0])

使用方式 2: 直接初始化
声明:var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值…},{初值…}}
赋值(有默认值,比如 int 类型的就是 0)

fmt.Println()
	arr3  := [2][3]int{{1,2,3}, {4,5,6}}
	fmt.Println("arr3=", arr3)

二维数组的遍历
双层 for 循环完成遍历

//演示二维数组的遍历
	var arr3  = [2][3]int{{1,2,3}, {4,5,6}}

	//for循环来遍历
	for i := 0; i < len(arr3); i++ {
		for j := 0; j < len(arr3[i]); j++ {
			fmt.Printf("%v\t", arr3[i][j])
		}
		fmt.Println()
	}

for-range 方式完成遍历

//演示二维数组的遍历
	var arr3  = [2][3]int{{1,2,3}, {4,5,6}}
	//for-range来遍历二维数组
	for i, v := range arr3 {
		for j, v2 := range v {
			fmt.Printf("arr3[%v][%v]=%v \t",i, j, v2)
		}
		fmt.Println()	
	}

map的声明

map 是 key-value 数据结构,又称为字段或者关联数组。类似其它编程语言的集合, 在编程中是经常使用到
基本语法
var map 变量名 map[keytype]valuetype
key 可以是什么类型
golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只 包含前面几个类型的 接口, 结构体, 数组
通常 key 为 int 、string
注意: slice, map 还有 function 不可以,因为这几个没法用 == 来判断
valuetype 可以是什么类型
valuetype 的类型和 key 基本一样,这里我就不再赘述了
通常为: 数字(整数,浮点数),string,map,struct
注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能赋值和使用。 案例演示
map 的使用

//第一种使用方式
	
	var a map[string]string
	//在使用map前,需要先make , make的作用就是给map分配数据空间
	a = make(map[string]string, 10)
	a["no1"] = "宋江" //ok?
	a["no2"] = "吴用" //ok?
	a["no1"] = "武松" //ok?
	a["no3"] = "吴用" //ok?
	fmt.Println(a)
	//第二种方式
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"
	fmt.Println(cities)
//第三种方式
	heroes := map[string]string{
		"hero1" : "宋江",
		"hero2" : "卢俊义",
		"hero3" : "吴用",
	}
	heroes["hero4"] = "林冲"
	fmt.Println("heroes=", heroes)

map 的增删改查操作

//第二种方式
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"
	fmt.Println(cities)

	//因为 no3这个key已经存在,因此下面的这句话就是修改
	cities["no3"] = "上海~" 
	fmt.Println(cities)

	//演示删除
	delete(cities, "no1")
	fmt.Println(cities)
	//当delete指定的key不存在时,删除不会操作,也不会报错
	delete(cities, "no4")
	fmt.Println(cities)

map查找

//演示map的查找
	val, ok := cities["no2"]
	if ok {
		fmt.Printf("有no1 key 值为%v\n", val)
	} else {
		fmt.Printf("没有no1 key\n")
	}

	//如果希望一次性删除所有的key
	//1. 遍历所有的key,如何逐一删除 [遍历]
	//2. 直接make一个新的空间
	cities = make(map[string]string)
	fmt.Println(cities)

map遍历

	//使用for-range遍历map
	//第二种方式
	cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"
	
	for k, v := range cities {
		fmt.Printf("k=%v v=%v\n", k, v)
	}
	//使用for-range遍历一个结构比较复杂的map
	studentMap := make(map[string]map[string]string)
	
	studentMap["stu01"] =  make(map[string]string, 3)
	studentMap["stu01"]["name"] = "tom"
	studentMap["stu01"]["sex"] = "男"
	studentMap["stu01"]["address"] = "北京长安街~"

	studentMap["stu02"] =  make(map[string]string, 3) //这句话不能少!!
	studentMap["stu02"]["name"] = "mary"
	studentMap["stu02"]["sex"] = "女"
	studentMap["stu02"]["address"] = "上海黄浦江~"

	for k1, v1 := range studentMap {
		fmt.Println("k1=", k1)
		for k2, v2 := range v1 {
				fmt.Printf("\t k2=%v v2=%v\n", k2, v2)
		}
		fmt.Println()
	}

map 切片
切片的数据类型如果是 map,则我们称为 slice of map,map 切片,这样使用则 map 个数就可以动 态变化了。

	//演示map切片的使用
	/*
	要求:使用一个map来记录monster的信息 name 和 age, 也就是说一个
	monster对应一个map,并且妖怪的个数可以动态的增加=>map切片
	*/
	//1. 声明一个map切片
	var monsters []map[string]string
	monsters = make([]map[string]string, 2) //准备放入两个妖怪
	//2. 增加第一个妖怪的信息
	if monsters[0] == nil {
		monsters[0] = make(map[string]string, 2)
		monsters[0]["name"] = "牛魔王"
		monsters[0]["age"] = "500"
	}

	if monsters[1] == nil {
		monsters[1] = make(map[string]string, 2)
		monsters[1]["name"] = "玉兔精"
		monsters[1]["age"] = "400"
	}

	// 下面这个写法越界。
	// if monsters[2] == nil {
	// 	monsters[2] = make(map[string]string, 2)
	// 	monsters[2]["name"] = "狐狸精"
	// 	monsters[2]["age"] = "300"
	// }

	//这里我们需要使用到切片的append函数,可以动态的增加monster
	//1. 先定义个monster信息
	newMonster := map[string]string{
		"name" : "新的妖怪~火云邪神",
		"age" : "200",
	}
	monsters = append(monsters, newMonster)

	fmt.Println(monsters)

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

//map的排序
	map1 := make(map[int]int, 10)
	map1[10] = 100
	map1[1] = 13
	map1[4] = 56
	map1[8] = 90

	fmt.Println(map1)

	//如果按照map的key的顺序进行排序输出
	//1. 先将map的key 放入到 切片中
	//2. 对切片排序 
	//3. 遍历切片,然后按照key来输出map的值

	var keys []int
	for k, _ := range map1 {
		keys = append(keys, k)
	}
	//排序
	sort.Ints(keys)
	fmt.Println(keys)

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

1)map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来 的 map
2)map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动 态的增长 键值对(key-value)
3)map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),比如 value 为 Student 结构体

type Stu struct {
	Name string
	Age int
	Address string
}

//map的value 也经常使用struct 类型,
	//更适合管理复杂的数据(比前面value是一个map更好),
	//比如value为 Student结构体 【案例演示,因为还没有学结构体,体验一下即可】
	//1.map 的 key 为 学生的学号,是唯一的
	//2.map 的 value为结构体,包含学生的 名字,年龄, 地址

	students := make(map[string]Stu, 10)
	//创建2个学生
	stu1 := Stu{"tom", 18, "北京"}
	stu2 := Stu{"mary", 28, "上海"}
	students["no1"] = stu1
	students["no2"] = stu2

	fmt.Println(students)

	//遍历各个学生信息
	for k, v := range students {
		fmt.Printf("学生的编号是%v \n", k)
		fmt.Printf("学生的名字是%v \n", v.Name)
		fmt.Printf("学生的年龄是%v \n", v.Age)
		fmt.Printf("学生的地址是%v \n", v.Address)
		fmt.Println()
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值