go官方文档   一,函数

求字节长度

import (
	"fmt"
	"unsafe"
)
	var a int
	fmt.Printf("%d", unsafe.Sizeof(a))

1.函数练习

阶乘

package main

import "fmt"

func su(n int) int {
	if n == 1 {
		return 1
	} else {
		return n * su(n-1)
	}
}
func main() {
	var a int
	fmt.Scanln(&a)
	fmt.Println("a!=", su(a))

}

猴子偷桃

第十天只剩一个桃子,每天吃前一天的一半加一,刚开始桃子数量

package main

import "fmt"

func monk(n int) int {
	if n == 10 {
		return 1
	} else {
		return monk(n+1)*2 + 1    //当天的桃子数等于monk(当天+1)*2+1
	}
}

func main() {
	var n int
	fmt.Scanln(&n)
	fmt.Println("桃子数=", monk(n))
}

值交换

package main

import "fmt"

func num(n1 *int, n2 *int) {
	a := *n1
	*n1 = *n2
	*n2 = a
}

func main() {
	a, b := 4, 7
	num(&a, &b)    //传入地址
	fmt.Println(a, b)
}
2.init 函数

每一个源文件都可以包含一个init 函数,该函数会在 main 函数执行前被GO 框架调用,

有全局变量先执行全局变量执行顺序为  全局变量>init函数>main函数

package main

import "fmt"

var age = text()

func text() int {
	fmt.Println("text...")
	return 90
}
func init() {
	fmt.Println("init()...")
}
func main() {
	fmt.Println("main()...age=", age)

}

执行结果

匿名函数

package main

import "fmt"

var (
	//b是全局匿名函数
	b = func(n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {
	//在定义匿名函数是就直接调用,这种方式匿名函数只能调用一次
	//求两个数的和,使用匿名函数完成
	res1 := func(n1 int, n2 int) int {
		return n1 + n2
	}(20, 50)
	fmt.Println("res1=", res1)
	//将匿名函数赋值给 a变量
	//此时a可以反复调用
	a := func(n1 int, n2 int) int {
		return n1 - n2
	}
	res2 := a(10, 30)
	fmt.Println("res2=", res2)
	res3 := a(50, 50)
	fmt.Println(res3)
	res4 := b(20, 30)
	fmt.Println(res4)
}
3.闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

func AddUpper() func(int) int {	//函数返回func(int)和int
	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}
func main() {
	f := AddUpper()
	fmt.Println(f(1)) //11
	fmt.Println(f(2)) //13
}

闭包

闭包的最佳实践

请编写一个程序,具体要求如下

1)编写一个函数makeSuffix(suffix string)可以接收一个文件后缀名(比如.jpg),并返回一个闭包
2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg,如果已经有.jpg后缀,则返回原文件名。
3)要求使用闭包的方式完成
4) strings.HasSuffix      该函数可以判断某个字符串是否有指定的后缀

func makeSuffix(suffix string) func(string) string {
	//func(string)接受一个string
	// string返回一个string
	return func(name string) string { //闭包
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}
func main() {
	//	测试
	f2 := makeSuffix(".jpg")
	fmt.Println("文件名处理后=", f2("winter"))
	fmt.Println("文件名处理后=", f2("hello.jpg"))
}

代码说明

1)返回的匿名函数makeSuffix (suffix string)的 suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量

2)我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如.jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用

defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。

案例:

package main

import "fmt"

func sum(n1 int, n2 int) int {
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	// 当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("n1=", n1)    //   3
	defer fmt.Println("n2=", n2)    //  2

	res := n1 + n2
	fmt.Println("res=", res)    //  1
	return res
}

func main() {
	res := sum(30, 20)
	fmt.Println("res main=", res)    // 4
}

执行结果

 defer的注意细节:

func sum(n1 int, n2 int) int {
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	// 当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("n1=", n1)
	defer fmt.Println("n2=", n2)
	//增加一句话
	n1++
	n2++
	res := n1 + n2
	fmt.Println("res=", res)//   32
	return res
}

func main() {
	res := sum(30, 20)
	fmt.Println("res main=", res)
}

结果

 函数的传递方式

 值类型和引用类型

1)值类型:基本数据类型int系列, float系列, bool, string 、数组和结构体struct

2)引用类型:指针、slice切片、map、管道chan、interface等都是引用类型

字符串

字符串常用函数

中文在go语言中占3个字节

字符串遍历处理有中文的问题:

r  :=[]rune(str)

package main

import "fmt"

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

字符串转整数

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

整数转字符串

// 整数转字符串
	str = strconv.Itoa(12345)
	fmt.Printf("str=%v,str=%T", str, str)

字符串 转[]byte=[]byte("hello go")

// 字符串 转[]byte=[]byte("hello go")
	var bytes = []byte("hello go")
	fmt.Printf("bytes=%v\n", bytes)

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

// []byte 转字符串:str = string([]byte{97,98,99})
	str = string([]byte{97, 98, 99})
	fmt.Printf("str=%v\n", str)
}

 10进制转 2,8,16进制: str =strconv.FormatInt(123,2),返回对应的字符串

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

查找子串是否在指定的字符串中:strings.Contains("seeafood","food")

// 查找子串是否在指定的字符串中:strings.Contains("seeafood","food") //true
	b := strings.Contains("seeafood", "food")
	fmt.Printf("b=%v\n", b)

 统计一个字符串有几个指定的字串:strings.Count("wangssa","a")

// 统计一个字符串有几个指定的字串:strings.Count("wangssa","a") //4
	num := strings.Count("wangssa", "a")
	fmt.Printf("num=%v\n", num)

不区分大小写的字符串 比较(==是区分字母大小写的):fmt.Println(strings.EqualFold)("abc","ABc")

//不区分大小写的字符串 比较(==是区分字母大小写的):fmt.Println(strings.EqualFold)("abc","ABc")
	b = strings.EqualFold("abc", "ABc")
	fmt.Printf("b=%v\n", b)  //true
	fmt.Println("结果", "abc" == "ABc")  //false

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

    //返回字串在字符串第一次出现的index值,如果没有返回-1:
	// strings.Index("NLT_abc","abc") //4
	index := strings.Index("NLT_abc", "abc")
	fmt.Println(index)

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

//返回字串在字符串最后一次出现的index值,如果没有返回-1:
	// strings.LastIndex("go golang","go") //4
	index = strings.LastIndex("go golang", "go")
	fmt.Println(index)
}

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

// 将指定的子串替换成另一个子串:strings.Replace("go go hello","go","北京",n)
	// n可以指定你希望替换几个,如果n=-1表示全部替换
	str2 := "go go hello"
	str = strings.Replace(str2, "go", "北京", -1)
	fmt.Printf("str=%v\nstr2=%v\n", str, str2)

按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:

// 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:
	// strings.Split("hello,wrold,ok",",")
	strarr := strings.Split("hello,wrold,ok", ",")
	fmt.Printf("strarr=%v\n", strarr)

 将字符串的字母进行大小写的转换:

// 将字符串的字母进行大小写的转换:strings.ToLower("Go")//strings.ToUpper("Go")//GO
	str = "goLang Hello"
	str = strings.ToLower(str)
	fmt.Println(str)
	str = strings.ToUpper(str)
	fmt.Println(str)

将字符串左右两边的空格去掉

// 将字符串左右两边的空格去掉:strings.TrimSpace("  tn a gopher ntrn    ")
	str = strings.TrimSpace("  tn a gopher ntrn    ")
	fmt.Printf("str=%q\n", str)

时间和日期相关函数

1)获取时间

import (
	"fmt"
	"time"
)
func main() {
	// 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T", now, now)

}

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

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

 练习

判断text03执行花费时间

func text03() {
	str := ""
	for i := 0; i < 100000; i++ {
		str += "hello" + strconv.Itoa(i)
	}
}
func main() {
	start := time.Now().Unix()
	text03()
	end := time.Now().Unix()
	fmt.Printf("执行text03所用时间%v秒\n", end-start)
}

内置函数

1)new:用来分配内存,主要用来分配值类型,比如int,float32,struct...返回的是指针

num1的值 = 地址 0xc0000160b8 (系统分配)    num1的地址 =地址 0xc00000a028(系统分配)

0xc0000160b8   这个地址存放的就是0  num1指向的值就是0

func main() {
	num1 := new(int)

	fmt.Printf("num1的类型%T,num1的值=%v,num1的地址%v,num1这个指针指向的值=%v", num1, num1, &num1, *num1)

}

内存分析图:

go错误处理机制

使用defer +recover 来捕获和处理异常

func text() {
	defer func() {
		err := recover()  //recover()内置函数,可以捕获到异常
		if err != nil {  //说明捕获到错误
			fmt.Println("err=", err)
			// 这里就可以将错误发送给管理员。。。

		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println(res)
}

func main() {

	text()
	fmt.Println("你好,错误")
}

 自定义错误

使用errors.New和panic内置函数

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

func main() {
	test02()
	fmt.Println("你好,错误")

}
  二,数组与切片

var+数组名+[ ]+类型

go 中的数组遍历

func main() {
	arr := [...]string{"宋江", "大哈", "老师"}

	// for-range
	for i, v := range arr {
		fmt.Printf("i=%v v=%v\n", i, v)
	}
}

随机生成N个值,传入数组中;反转打印

rand .Seed(time.Now().Unix())  如果在同一秒生成的值也是一样

UnixNano更好

随机数 rand.Intn(100)   100以内的随机数

不设置seed 生成的值一直是默认值

package main

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

func main() {
	var arr [5]int
	len := len(arr)
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len; i++ {
		arr[i] = rand.Intn(100)
	}
	fmt.Println(arr)
	temp := 0
	for i := 0; i < len/2; i++ {
		temp = arr[i]
		arr[i] = arr[len-i-1]
		arr[len-i-1] = temp

	}
	fmt.Println(arr)
}
冒泡排序应用
package main

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

var temp int = 0
var sum int = 0

func sore(arr *[10]int, len int) {
	for i := 0; i < len-1; i++ {
		if (*arr)[i] == 55 {
			fmt.Printf("有55,在第%v下标\n", i)
		}
		sum += (*arr)[i]
		for j := 0; j < len-i-1; j++ {
			if (*arr)[j] > (*arr)[j+1] {
				temp = (*arr)[j]
				(*arr)[j] = (*arr)[j+1]
				(*arr)[j+1] = temp
			}
		}
	}
	fmt.Println("sum=", sum)

	fmt.Println("aver=", sum/10)
	fmt.Println("min=", arr[0])
	fmt.Println("max=", arr[9])

}
func main() {
	var arr [10]int
	len := len(arr)
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < len; i++ {
		arr[i] = rand.Intn(100)
	}
	fmt.Println(arr)
	sore(&arr, len)
}
切片

slice切片        引用类型

var+切片名[ ]+类型        [ ]不用写长度

为什么需要切:我们需要一个数组保存学生成绩,但是学生个数是不确定的,怎么办?

---》用切片   类似于动态数组

入门案例:

package main

import "fmt"

func main() {
	// 切片基本使用
	var intArr [5]int = [...]int{1, 2, 3, 4, 5}
	// 声明/定义一个切片
	// slice:=intArr[1:3]
	// 1.slice 就是切片名
	// 2.intArr[1:3]表示slice
	//  引用到intArr这个数组的第2个元素到第3个元素 (左闭右开)
	slice := intArr[1:3]
	fmt.Println(slice)
	fmt.Println(len(slice))
	fmt.Println(len(intArr)) 
	fmt.Println("slice的容量=", cap(slice))	 
	fmt.Println("intArr的容量=", cap(intArr))

}

 

 切片在内存中形式

slice由三个部分构成

1.元素地址

2.长度

3.容量

使用切片的3种方式

1.如上例所示

2.通过make来创建切片: (这种方式可以指定cap,则cap必须大于len)
基本语法:var 切片名[ ]type=make([ ]type,len,[cap])  类型,长度,容量

package main

import "fmt"

func main() {
	// 演示切片使用make
	var slice []float64 = make([]float64, 5, 10)
	slice[0] = 1
	slice[3] = 2
	fmt.Println(slice)
	fmt.Println("len=", len(slice))
	fmt.Println("cap", cap(slice))
}

 3.定义一个切片,直接就指定具体数组,使用原理类似make方式

var strslice []string = []string{"tom", "jacj", "mary"}
	fmt.Println("strslice=", strslice)
	fmt.Println("strslice size=", len(strslice))
	fmt.Println("strslice cap=", cap(strslice))

使用切片的区别分析

                        make创建的切片,他所指向的数组不可见

切片的遍历

与数组遍历方式相同

package main

import (
	"fmt"
)

func main() {
	// 切片常规遍历
	var arr [5]int = [...]int{10, 20, 30, 40, 50}
	slice := arr[1:4]
	for i := 0; i < len(slice); i++ {
		fmt.Println("slice=", slice[i])
	}
// for--range 方式遍历切片 
	for i, v := range slice {
		fmt.Printf("i=%v v=%v\n", i, v)
	}

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

分析append底层原来

切片append操作的本质就是对数组的扩容

go底层会创建一个新的数组newArr(安装扩容后大小)

将slice原来包含的元素拷贝到新的数组newArr

slice重新引用到newArr    newArr是在底层维护的,程序员看不见

var slice3 []int = []int{100, 200, 300}
	fmt.Printf("slice01的地址=%p\n", slice3)
	// 通过append直接给slice3追加具体的元素
	slice3 = append(slice3, 400, 500, 600)
	fmt.Printf("slice02的地址=%p\n", slice3)
	slice3 = append(slice3, slice3...)
	fmt.Printf("slice03的地址=%p\n", slice3)
	fmt.Println("slice3=", slice3)

 

给顺序数组插入元素元素,并按升序打印

package main

import (
	"fmt"
)

func main() {
	// arr为切片
	arr := []int{1, 4, 9, 10, 22}
	len1 := len(arr)
	arr = append(arr, 5, 11, 88, 8, 7)
	temp := 0
	num := 0
	for i := len(arr) - len1; i > 0; i-- {
		for j := 0; j < len(arr); j++ {
			if arr[j] > arr[len(arr)-i] {
				temp = arr[j]
				arr[j] = arr[len(arr)-i]
				arr[len(arr)-i] = temp
				num++
			}
		}
	}
	fmt.Println(arr)
	// 循环次数
	fmt.Println(num)
}

切片的拷贝操作

使用copy内置函数完成拷贝

    var arr []int = []int{1, 2, 3, 4, 5}
	var slice []int = make([]int, 10)
	copy(slice, arr)
	fmt.Println(arr)
	fmt.Println(slice)

arr和slice两个数据空间是独立的,相互不影响,

切片是引用类型,当作为实参传递给形参的时候是 址传递

string和slice关系

1)string底层是一个byte数组,因此string也可以进行切片处理


	str := "hello@xiaownag"

	// 使用切片获取到xiaowang
	slice := str[6:]
	fmt.Println(slice)

2)string是不可变的,也就是说不能通过str[0]='z'方式来修改字符串

3)如果需要修改字符串,可以先将string->[ ]byte/或者[ ]rune->修改->重写转成string

	arr := []byte(str)
	arr[0] = 'z'
	str = string(arr)
	fmt.Println(str)

转成[ ]byte后,可以处理英文和数字,但是不能处理中文,原因是[ ]byte占一个字节,而汉字在go语言中占3个字节。

解决办法:将string转成[ ]rune即可,因为[ ]rune是按字符处理,兼容汉字

arr := []rune(str)
	arr[0] = '北'
	str = string(arr)
	fmt.Println(str)

切片练习

使用切片来存放斐波那契数列

package main

import "fmt"

func fub(n int) []uint64 {

	fub := make([]uint64, n)
	fub[0] = 1
	fub[1] = 1
	for i := 2; i < n; i++ {
		fub[i] = fub[i-1] + fub[i-2]
	}
	return fub
}

func main() {

	slice := fub(10)
	fmt.Println(slice)

}

二分查找

arr数组是一个有序数组,并且是从小到大排序的

func sore(arr *[6]int, left int, right int, findval int) {
	if left > right {
		fmt.Println("找不到")
		return
	}
	middle := (left + right) / 2
	if (*arr)[middle] > findval {
		// 说明我们要查找的数,应该在  left---middle-1
		sore(arr, left, middle-1, findval)
	} else if (*arr)[middle] < findval {
		sore(arr, middle+1, right, findval)
	} else {
		fmt.Printf("找到了,下表为%v\n", middle)
	}

}
func main() {
	arr := [6]int{1, 8, 10, 89, 1000, 1234}
	sore(&arr, 0, len(arr)-1, 1000)

}

二维数组

var arr2 [2][3]int
	arr2[1][1] = 10
	fmt.Println(arr2)

内存分析

数组里面存放两个指针,分别指向两个连续的一维数组

 二维数组 for range 遍历

// for range 遍历二位数组
	for i, v := range arr2 {
		for j, v1 := range v {
			fmt.Printf("arr2[%v][%v]=%v\t", i, j, v1)
		}
		fmt.Println()
	}