求字节长度
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()
}