六、函数、包和错误处理 - Golang

Golang
354
0
0
2022-04-09
标签   Golang基础

1-函数的基本语法

var f1 float64 = 12.34var f2 float64 = 12.00var oper byte = '-'
   result := cal(f1, f2, oper)
   fmt.Println(result)
}

func cal(n1 float64, n2 float64, operator byte) float64 {var res float64switch operator {case '+':
      res = n1 + n2
   case '-':
      res = n1 - n2
   case '*':
      res = n1 * n2
   case '/':
      res = n1 / n2
   default:
      fmt.Println("操作符号错误")}return res
}

2-包

go的每一个文件都属于一个包,也就是说go是以包的形式来管理文件和项目目录结构的

1-包的作用

  1. 区分相同名字的函数、变量等标识符
  2. 管理项目
  3. 控制函数、变量等访问范围,即作用域

2-包的相关说明

Package 包名

Import 包的路径

  1. 文件包名通常和文件名称一致,都为小写
  2. 第一行package、import引入
  3. 在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src开始引入
  4. 访问其他包的函数或者变量,包名.函数名或者包名.变量名
  5. 如果包名太长,可以取别名,但是原来的包名就不能使用了
  6. 同一个包里不能有两个相同的函数名、全局变量
  7. 如果要编译成一个可执行程序文件,需要将这个包声明为main,即package main,这个就是一个语法规范,如果是写一个库,包名可以自定义
  8. 编译时需要编译main包所在的文件夹[root@localhost learn]# go build -o bin\my.exe go_codee\fundemo01\main

3-函数

1-函数的调用机制

上图说明:

  1. 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他的栈的空间区分开来
  2. 在每个函数对应的栈中,数据空间是独立的,不会混淆
  3. 当一个函数调用(执行)完毕后,程序会销毁这个函数对于的栈空间

2-return语句

  1. 如果返回多个值,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略
  2. 如果返回值只有一个,返回值类型列表可以不写()

3-函数的递归调用

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

分析图

案例

递归调用的总结

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

练习题

  1. 斐波那契数 1,1,2,3,5,8,13,给出一个整数n,返回它的斐波那契数
func fbn(n int) int {if n == 1 || n == 2 {return 1} else {return fbn(n-1) + fbn(n-2)

   }
}
  1. 求函数值

f(1)=3

f(n)=2*f(n-1)+1

func fbn(n int) int {if n == 1 {return 3} else {return f(n)=2*f(n-1)+1

   }
}
  1. 猴子吃桃子的问题

有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后 再多吃一个。当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?

思路分析:

  1. 第 10 天只有一个桃子
  2. 第 9 天有几个桃子 = (第 10 天桃子数量 + 1) * 2
  3. 规律: 第 n 天的桃子数据 peach(n) = (peach(n+1) + 1) * 2 代码:
func peach(n int) int {if n > 10 || n < 1 {
      fmt.Println("天数不对")return 0}if n == 10 {return 1} else {return (peach(n+1) + 1) * 2}
}

4-函数使用的注意事项和细节讨论

  1. 函数的形参列表可以是多个,返回值列表也可以是多个。
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大小写问题
  4. 函数中的变量是局部的,函数外不生效
  5. 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
  6. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用
  7. Go 函数不支持函数重载(两个相同的函数名,参数个数不同,认为是同一个函数)
  8. 函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了,通过该变量可以对函数调用
  9. 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
  10. 使用 _ 标识符,忽略返回值
  11. 支持对函数返回值命名
  12. 为了简化数据类型定义,Go 支持自定义数据类型

【基本语法:type 自定义数据类型名 数据类型】

  1. Go 支持可变参数

  1. 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。
13-----Go 支持可变参数
func main(){
    res4 := sum(10, 0, -1, 90, 10)//109
   fmt.Println(res4)
}

func sum(n1 int, args ...int) int {
   sum := n1
   for i := 0; i < len(args); i++ {
      sum += args[i]}return sum
}

12------Go 支持自定义数据类型
相当于别名
但是与原来的int是两种类型,需要转换
type myInt int
var num1 myInt
var num2 int
num1 = 48
num2 = int(num1)
fmt.Println("num1=", num1, "num2=", num2)

函数别名
func main(){
    res3 := myFun2(getSum, 56, 60) 
    fmt.Println(res3) 
} 

type myFunType func(int, int) int 

func myFun2(funvar myFunType, num1 int, num2 int) int { 
        return funvar(num1, num2) 
} 

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

11-----支持对函数返回值命名
 func main(){
   a, b := getSumAndSub(1, 2)
   fmt.Println(a, b)
}

func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
   sub = n1 - n2
   sum = n1 + n2
   return
}

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

--------------------------------------------
num := 14
test1(num)
fmt.Println(num) //14

func test1(n int) {
   n = n + 10
   fmt.Println(n)//24//return 0
}
---------------------------------------------
//传入地址修改值
func main(){
    num := 14//test1(num)test3(&num)
   fmt.Println("main num=", num)//24

}

//n就是 *int类型
func test3(n *int) {*n = *n + 10
   fmt.Println("test03 n=", *n)//24
}
//*取值
//&取地址
---------------------------------------------
函数也是一种类型
func main(){
        a := getSum 
        fmt.Printf("a的类型是%T,getSum类型是%T\n", a, getSum) 

        res := a(10, 40) 
        fmt.Println(res) 
} 

func getSum(n1 int, n2 int) int { 
        return n1 + n2 
}
-----------------------------------------------
函数既然是一种类型,也可以作为参数
func main(){
   res2 := myFun(getSum, 50, 60)
   fmt.Println(res2)
}

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

func myFun(funvar func(int, int) int, num1 int, num2 int) int {return funvar(num1, num2)
}

案例

  1. 如果第一个参数和第二个参数类型一致,第一个后面可以不用带参数

交换两个值的位置

 func main(){
   a = 10
   b = 20swap(&a, &b)
   fmt.Printf("a=%v,b=%v", a, b)
}

func swap(n3 *int, n4 *int) {
   t := *n3
   *n3 = *n4
   *n4 = t
}

4-init函数

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

就是说 init 会在 main 函数前被调用。

package main

import "fmt"

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

func main() {
   fmt.Println("main...")
}
//GOROOT=D:\go #gosetup 
init... 
main... 

1-inti 函数的注意事项和细节

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

函数->main 函数

var age = test()

func test() int {
   fmt.Println("test()")return 90
}
func init() {
   fmt.Println("init...")
}

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

//
test() 
init... 
main... 90
  1. init 函数最主要的作用,就是完成一些初始化的工作(即使是引入的包)

5-匿名函数

Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考

虑使用匿名函数,匿名函数也可以实现多次调用。

5-1 匿名函数使用方式 1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

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

5-2匿名函数使用方式 2

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

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

   res3 := a(12, 6)
   fmt.Println(res3)
}

5-3 全局匿名函数

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

有效。

var (//fun1就是全局匿名函数
   Func1 = func(n1, n2 int) int {return n1 + n2
   }
)

6-闭包

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

案例

func AddUpper() func(int) int {var n int = 10return func(x int) int {
      n = n + x
      return n
   }
}

func main() {
   f := AddUpper()
   fmt.Println(f(1))
   fmt.Println(f(2))
   fmt.Println(f(3))
}

//
11 
13 
16

----这部分属于闭包----var n int = 10return func(x int) int {
      n = n + x
      return n
   }
--------------------
返回的是一个匿名函数, 但是这个匿名函数引用到函数外的 n ,因此这个匿名函数就和 n 形成一
个整体,构成闭包
  1. AddUpper 是一个函数,返回的数据类型是 fun (int) int
  2. 闭包是类, 函数是操作,n 是字段。函数和它使用到 n 构成闭包
  3. 当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计
  4. 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包
  5. 对上面代码的一个修改,加深对闭包的理解
func AddUpper() func(int) int {var n int = 10var str = "hello"return func(x int) int {
      n = n + x
      str += string(36) //36=>$
      fmt.Println("str=", str)return n
   }
}

func main() {
   f := AddUpper()
   fmt.Println(f(1))
   fmt.Println(f(2))
   fmt.Println(f(3))
}
//结果
str= hello$ 
11 
str= hello$$ 
13 
str= hello$$$ 
16

案例:

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

f1 := makeSuffix(".jpg")
fmt.Println("文件名处理后=", f1("winter"))
fmt.Println("文件名处理后=", f1("winter.jpg"))
fmt.Println("文件名处理后=", f1("winter.avi"))

//
文件名处理后= winter.jpg 
文件名处理后= winter.jpg 
文件名处理后= winter.avi.jpg
  1. 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量组合成一个闭包,因为返回的函数引用到 suffix 这个变量
  2. 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把

7-defer

为什么需要 defer

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

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

func sum(n1 int, n2 int) int {

   //当执行到defer时,暂时不执行,会将defer后面的语句亚茹到独立的栈(defer栈)//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行defer fmt.Println("sum3 n1=", n1)defer fmt.Println("sum2 n2=", n2)
   res := n1 + n2
   fmt.Println("sum1 res=", res)return res
}

//
sum1 res= 30 
sum2 n2= 20 
sum3 n1= 10 
main4 res= 30

总结:

  1. 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈 中[我为了讲课方便,暂时称该栈为 defer 栈], 然后继续执行函数下一个语句
  2. 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制),所以同学们看到前面案例输出的顺序
  3. 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈
func main() {
   res := sum(10, 20)
   fmt.Println("main4 res=", res)
}

func sum(n1 int, n2 int) int {

   //当执行到defer时,暂时不执行,会将defer后面的语句亚茹到独立的栈(defer栈)//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行defer fmt.Println("sum3 n1=", n1)defer fmt.Println("sum2 n2=", n2)//增加
   n1++
   n2++
   defer fmt.Println("sum3 n1=", n1)
   defer fmt.Println("sum2 n2=", n2)
   res := n1 + n2
   fmt.Println("sum1 res=", res)return res
}

//

sum1 res= 32 
sum2 n2= 21 
sum3 n1= 11 
sum2 n2= 20 
sum3 n1= 10 
main4 res= 32

defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。

  1. 在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行 defer file.Close() defer connect.Close()
  2. 在 defer 后,可以继续使用创建资源.
  3. 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
  4. 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。

8-函数参数传递方式

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

两种传递方式

  1. 值传递
  2. 引用传递

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

2-值类型和引用类型

  1. 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
  2. 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型

2-值传递和引用传递使用特点

  1. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用
//传入地址修改值
func main(){
    num := 14//test1(num)test3(&num)
   fmt.Println("main num=", num)//24

}

//n就是 *int类型
func test3(n *int) {*n = *n + 10
   fmt.Println("test03 n=", *n)//24
}
//*取值
//&取地址

9-变量作用域

  1. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
  2. 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
  3. 如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
赋值语句不能在函数外执行
package main

import "fmt"
var Age int = 20
Name := "test" //先声明后赋值  var Name string / Name = "test",执行语句需在函数体内执行
func main() {
    fmt.Println("name",Name)
}

课堂练习

var age int = 50         //其他包不可用
var Name string = "jack" //其他包可用

func main() {
   fmt.Println("age=", age)
   fmt.Println("Name=", Name)
   fmt.Println()test()
}

func test() {
   age := 10
   Name := "time"
   fmt.Println("age=", age)
   fmt.Println("Name=", Name)
}

-------------------
age= 50 
Name= jack 

age= 10 
Name= time
//如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
for i := 0; i < 10; i++ {
   fmt.Println(i)
}

for i := 0; i < 10; i++ {
   fmt.Println(i)
}
fmt.Println(i) //未定义

要局部使用
var i int
for i = 0; i < 10; i++ {
   fmt.Println(i)
}

---------------------------
var age int = 50         //其他包不可用
fmt.Println(age)
test01()
test02()
test03()

func test01() {
   fmt.Println(age)
}

func test02() {//就近原则
   age := 12
   fmt.Println(age)
}

func test03() {
   fmt.Println(age)
}

函数课堂联系题

  1. )函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔
  2. 编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表
//todo

10-字符串常用的系统函数

  1. 统计字符串的长度,按字节len(str)
  2. 字符串遍历,同时处理中文问题,r:=[]rune(str)
  3. 字符串转整数
  4. 整数转字符串
  5. 字符串转 []byte
  6. []byte 转字符串
  7. 10进驻转2,8,16进制
  8. 查找字符串中是否存在指定的字符串中
  9. 统计一个字符串有几个指定的子串
  10. 不区分大小写的字符串比较
  11. 返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index
  12. 返回子串在字符串最后一次出现的index,如果没有返回-1
  13. 将指定的子串替换成 另一个子串:strings.Replace(“go go hello”,”go”,”go语言”,n) n可以指你希望替换几个,如果n=-1表示全部替换
  14. 按照指定的某个字符,为分割标示,将一个字符串拆分成字符串数组:strings.Split(“hello,world,ok”,”,”)
  15. 将字符串的字母进行大小写的转换strings.ToLower() 、 strings.ToUpper()
//1-取长度
str := "hello 北京"
//golang的编码是utf8,ascii的字符(字吗和数字)占一个字节,汉字占用三个字节
fmt.Println("str len=", len(str)) //5+1+3+3=12

//2-中文遍历
str2 := "hello北京"
r := []rune(str2) //切片
for i := 0; i < len(r); i++ {
   fmt.Printf("字符=%c\n", r[i]) //中文乱码 一个汉字三个字符
}
/**
字符=h
字符=e
字符=l
字符=l
字符=o
字符=北
字符=京
*/ 
//3-字符串转整数
str3 := "123"
n, err := strconv.Atoi(str3)
if err != nil {
   fmt.Println("错误结果", err)
} else {
   fmt.Println("结果是", n)
} //结果是 123

//4-整数转字符串
m := strconv.Itoa(12345)
fmt.Println("结果是", m) //结果是 12345

//5-字符串转切片 []byte
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes) // [104 101 108 108 111 32 103 111]

//6-切片 []byte 到字符串
str = string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str) //str=abc

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

//8-查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //bool
b := strings.Contains("seafood", "sea")
fmt.Printf("b=%v\n", b) //b=true

//9-统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
num := strings.Count("ceheeeee", "e")
fmt.Printf("b=%v\n", num) //b=6

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

//12-返回子串在字符串第一次出现的 index 值,如果没有返回-1 : strings.Index("NLT_abc", "abc") // 4
index := strings.Index("NTL_abcabcabc", "abc")
fmt.Printf("index=%v\n", index) //index=4

//13-返回子串在字符串最后一次出现的 index,如没有返回-1 : strings.LastIndex("go golang", "go")
indexL := strings.LastIndex("NTL_abcabcabc", "abc")
fmt.Printf("index=%v\n", indexL) //index=10

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

//15-按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split("hello,wrold,ok",",")
strArr := strings.Split("hello,hello,test", ",")
for i := 0; i < len(strArr); i++ {
   fmt.Printf("str[%v]=%v\n", i, strArr[i])/**
   str[0]=hello
   str[1]=hello
   str[2]=test
   */ 
}
fmt.Printf("strArr=%v\n", strArr) //strArr=[hello hello test] 值拷贝不改变本身

//16-将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
str4 := "golang hello"
str4 = strings.ToLower(str4)  //改变了str4
fmt.Printf("str4=%v\n", str4) //str4=golang hello
str4 = strings.ToUpper(str4)
fmt.Printf("str4=%v\n", str4) //str4=GOLANG HELLO
  1. 将字符串左右两边的空格去掉 strings.TrimSpace()
  2. 将字符串左右两边指定的字符去掉【包括空格】 strings.Trim(“!hello! “,”!”)
  3. 将字符串左边指定的字符去掉: strings.TrimLeft(“!hello! “,”!”)
  4. 将字符串右边指定的字符去掉: strings.TrimRight(“!hello! “,”!”)
  5. 判断字符串是否以指定的字符串开头 strings.HasPrefix(“ftp://192.168.1.1","ftp")
  6. 判断字符串是否以指定的字符串结束 strings.HasSuffix(“ftp://192.168.1.1/aa","aa") //false
//17-将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
str = strings.TrimSpace(" tn a lone gopher ntrn ")
fmt.Printf("str=%q\n", str) //str="tn a lone gopher ntrn"

//18-将字符串左右两边指定的字符去掉 : strings.Trim("! hello! ", " !") // ["hello"] //将左右两边 !
//和 " "去掉
str = strings.Trim("! hello  !! ", " !")
fmt.Printf("str=%q\n", str) //str="hello"

//19-将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !") // ["hello"] //将左边 ! 和 "
//"去掉
str = strings.TrimLeft("! hello! ", " !")
fmt.Printf("str=%q\n", str) //str="hello! "

//20-将字符串右边指定的字符去掉 :strings.TrimRight("! hello! ", " !") // ["hello"] //将右边 ! 和 "
//"去掉
str = strings.TrimRight("! hello! ", " !")
fmt.Printf("str=%q\n", str) //str="! hello"

//21-判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp") // true
b = strings.HasPrefix("ftp://192.168.10.1", "ftp")
fmt.Printf("str=%v\n", b) //str=true

//22-判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT_abc.jpg", "abc") //false
b = strings.HasSuffix("NLT_abc.jpg", "abc")
fmt.Printf("str=%v\n", b) //str=false

11-时间和日期相关函数(202)

time.Time类型,用于表示时间

当前时间、年月日时分秒

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

//now=2021-03-25 16:40:28.2768429 +0800 CST m=+0.003021201 now type=time.Time

//获取年月日时分秒
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())

月=March 
月=3 
日=25 
时=16 
分=43 
秒=42

格式化日期时间

  1. printf或者sprintf (sprintf 字符串)
  2. 使用time.Format()方法

2006-01-02 15:04:05这个字符串必须这样写!

下面的自由调整以获取灵活的时间组合

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

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

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()

---------------------------------------------------------------
当前年月日 2021-4-15 19:52:44 
datastr=当前年月日 2021-4-15 19:52:44 
2021-04-15 19:52:44 
2021-04-15 
19:52:44

//fmt.Printf(now.Format("2006-02-01 15:04:05"))

时间的常量

不能除,所以产生那么多常量,不能用 Second /10

const (
   Nanosecond  Duration = 1                  //纳秒
   Microsecond          = 1000 * Nanosecond  //微妙
   Millisecond          = 1000 * Microsecond //毫秒
   Second               = 1000 * Millisecond //秒
   Minute               = 60 * Second        //分钟
   Hour                 = 60 * Minute        //小时
)

//time.secod 秒

i := 0
for {
   i++
   fmt.Println(i)
   time.Sleep(time.Second)
   time.Sleep(time.Millisecond*100) //100毫秒
}
  1. time的Unix和UnixNano的方法(作用:获取到随机数)
  2. Uinx int64 从1970年到现在经过的时间 单位秒
  3. UnixNano 从1970年到现在经过的时间 单位纳秒
  4. 如果值超过了int64返回未定义
fmt.Printf("unix=%v unixnano=%v", time.Now().Unix(), time.Now().UnixNano())
//unix=1616731171 unixnano=1616731171139452200

时间和日期的课堂练习

统计函数执行的时间

func main(){
   start := time.Now().Unix()
   fmt.Println(start)

   test01()
   end := time.Now().Unix()
   fmt.Println(end)
   fmt.Printf("执行test01()耗时%v秒", end-start)
}
func test01() {
   str := ""for i := 0; i < 10000; i++ {
      str += "hello" + strconv.Itoa(i)
      fmt.Println(str)}
}

内置函数

studygolang.com/pkgdoc

  1. len:用来求长度,比如 string、array、slice、map、channel
  2. new:用来分配内存,主要用来分配值类型,比如 int、float32,struct…返回的是指针
  3. make:用来分配内存,主要哟用来分配引用类型,比如chan、map、slice
数组:v中元素的数量
数组指针:*v中元素的数量(v为nilpanic)
切片、映射:v中元素的数量;若v为nillen(v)即为零
字符串:v中字节的数量
通道:通道缓存中队列(未读取)元素的数量;若v为 nillen(v)即为零

指针存的是一个地址,这个地址指向了一个值
num1 := 100
fmt.Printf("num1的类型%T,num1的值%v,num1的地址%v\n", num1, num1, &num1)

num2 := new(int)
//num2的类型 *ine
//num2的值 = 一个地址A(系统分配)
//num2的地址  = 地址A指向的地址B(系统分配)
//num2指向的值 = 0
fmt.Printf("num2的类型%T,num2的值%v,num2的地址%v", num2, num2, &num2,*num2)

------------------------------------------------------------------
num1的类型int,num1的值100,num1的地址0xc00000a0a0 
num2的类型*int,num2的值0xc00000a0a8,num2的地址0xc000006030

变量存的是一个值,值有个地址

指针存的是一个地址A,这个地址对应的有个值B,这个值自身也有地址C

Go错误处理机制

func test() {//使用defer + recover 来捕获和处理异常defer func() {
      err := recover() //recover()内置函数,可以捕获到异常if err != nil {
         fmt.Println("err=", err)}}()

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

func main() {test()
   fmt.Println("main下的方法")
}

----------------------------------------------------------
err= runtime error: integer divide by zero 
main下的方法
  1. 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
  2. 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行
  3. 这里引出我们要将的错误处理机制
基本说明
  1. Go 语言不支持传统的 try…catch…finally 这种处理
  2. Go 中引入的处理方式为:defer, panic, recover
  3. Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
使用 defer+recover 来处理错误

8-自定义错误

Go 程序中,也支持自定义错误, 使用 errors.New 和 panic 内置函数。

  1. errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
  2. panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类

型的变量,输出错误信息,并退出程序.

func main() {test02()
}

func test02() {
   err := readConf("config2.ini")if err != nil {panic(err)}

   fmt.Println("test02继续执行")
}

func readConf(name string) (err error) {if name == "config.ini" {return nil
   } else {return errors.New("读取文件错误")}
}

函数作业

循环打印输入的月份的天数   使用continue实现
要有判断输入的月份是否错误的语句

编写一个函数

随机猜数游戏:

随机生成一个1--100的整数

有十次机会

如果第一次就猜中,提示:“天才”

如果第2--3次猜中,提示“聪明”

如果第4-9次猜中,提示“一般”

如果最后一次猜中,提示“猜对”

一次都没有猜对,提示“哈哈”

编写一个函数,输出100以内的所有素数(素数就是只能被1和本身整除的书)。每行显示5个,并求和

编写一个函数,判断是打鱼还是晒网

如果从199011日起开始执行“三天打鱼两天晒网”,如何判断在以后的某一天中是打鱼还是晒网

输出小写的a-z以及大写的Z-A,使用for循环。Asic码