函数的基本概念

为完成某一功能的程序指令(语句)的集合,称为函数。
在 Go 中,函数分为: 自定义函数系统函数(查看 Go 编程手册)

函数的基本语法

  1. func 函数名(形参列表)(返回值列表){
  2. 执行语句...
  3. return 返回值列表
  4. }
  1. 形参列表:表示函数的输入
  2. 函数中的语句:表示为了实现某一功能的代码块
  3. 函数可以有返回值,也可以没有

    快速入门案例

    ```go package utils import ( “fmt” )

//将计算的功能,放到一个函数中,然后在需要使用,调用即可 //为了让其它包的文件使用Cal函数,需要将C大小类似其它语言的public func Cal(n1 float64, n2 float64, operator byte) float64 {

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

}

```go
package main
import (
    "fmt"
    "go_code/chapter06/fundemo01/utils"//引入自定义函数
)


func main() {

    fmt.Println("utils.go Num~=", utils.Num1)
    //请大家完成这样一个需求:
    //输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
    //分析思路....
    var n1 float64 = 1.2
    var n2 float64 = 2.3
    var operator byte = '+'
    result := utils.Cal(n1, n2 , operator) 
    fmt.Println("result~=", result)

    n1 = 4.5
    n2 = 6.7
    operator = '*'
    result = utils.Cal(n1, n2 , operator)
    fmt.Printf("result~=%.2f", result)

}

包的引出

  1. 在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如 main.go中,去使用 utils.go 文件中的函数,如何实现?
  2. 现在有两个程序员共同开发一个 Go 项目,程序员 A希望定义函数 Cal ,程序员 B也想定义函数也叫 Cal。两个程序员为此还吵了起来,怎么办?

    包的原理图

    包的本质实际上就是创建不同的文件夹,来存放程序文件。
    函数、包和错误处理 - 图1

    包的基本概念

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

    包的三大作用

  3. 区分相同名字的函数、变量等标识符

  4. 当程序文件很多时,可以很好的管理项目
  5. 控制函数、变量等访问范围,即作用域

    包的相关说明

    package 包名
    
    import "包的路径"
    

    包使用的注意事项和细节讨论

  6. 在给一个文件打包时,该包对应一个文件夹,比如这里的 utils 文件夹对应的包名就是 utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

  7. 当一个文件要使用其它包函数或变量时,需要先引入对应的包

    //引入方式 1:
    import    "包名"
    //引入方式 2: 
    import    (
    "包名"
    "包名"
    )
    package 指令在 文件第一行,然后是 import  指令。
    在 import 包时,路径从 $GOPATH  的    src 下开始,不用带 src ,  编译器会自动从 src 下开始引入
    
  8. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问

  9. 在访问其它包函数,变量时,其语法是 包名.函数名
  10. 如果包名较长,Go 支持给包取别名,

    1. 注意细节:取别名后,原来的包名就不能使用了
    2. 说明: 如果给包取了别名,则需要使用别名来访问该包的函数和变量
      package main
      import (
      "fmt"
      util "go_code/chapter06/fundemo01/utils"
      )
      
  11. 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

  12. 如果你要编译成一个可执行程序文件,就需要将这个包声明为 main , 即 package main .这个就是一个语法规范,如果你是写一个库 ,包名可以自定义

    函数的调用机制

    ```go package main

import ( “fmt” )

//一个函数 test func test(n1 int) { n1 = n1 + 1 fmt.Println(“test() n1=”, n1) }

func main() {

n1 := 10
//调用test
test(n1)
fmt.Println("main() n1=", n1) 

}

输出
```go
test() n1= 11
main() n1= 10

分析说明:

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

    return 语句

    基本语法

    func 函数名(形参列表)(返回值列表){
     执行语句...
     return 返回值列表
    }
    
  4. go函数支持返回多个值

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

    案例演示

    ```go package main

import ( “fmt” )

//一个函数 getSum func getSum(n1 int, n2 int) int { sum := n1 + n2 fmt.Println(“getSum sum = “, sum) // 30 //当函数有return语句时,就是将结果返回给调用者 //即谁调用我,就返回给谁 return sum }

//请编写要给函数,可以计算两个数的和和差,并返回结果 func getSumAndSub(n1 int, n2 int) (int, int) { sum := n1 + n2 sub := n1 - n2 return sum, sub }

func main() { sum := getSum(10, 20) fmt.Println(“main sum = “, sum) // 30

//调用getSumAndSub
res1, res2 := getSumAndSub(1, 2) //res1 = 3 res2 = -1
fmt.Printf("res1=%v res2=%v\n", res1, res2)

//希望忽略某个返回值,则使用 _ 符号表示占位忽略
_, res3 := getSumAndSub(3, 9)
fmt.Println("res3=", res3)

}

<a name="oOsR3"></a>
# 函数的递归调用
<a name="vkack"></a>
## 基本介绍
一个函数在**函数体内**又**调用了本身**,我们称为递归调用
<a name="ZM2Bi"></a>
## 递归调用快速入门
```go
package main

import (
    "fmt"
)

func test(n int) {
    if n > 1 {
        n--
        test(n)
    }
    fmt.Println("n=", n)
}

func main() {
    test(3) 
}

输出

n= 1
n= 1
n= 2

递归调用的总结

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

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

  5. 函数的形参列表可以是多个,返回值列表也可以是多个。

  6. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
  7. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似 public , 首字母小写,只能被本包文件使用,其它包文件不能使用,类似 privat
  8. 函数中的变量是局部的,函数外不生效 ```go package main import ( “fmt” )

//函数中的变量是局部的,函数外不生效 func test() { //n1 是 test函数的局部变量, 只能在test函数中使用 var n1 int = 10 }

func main() { //这里不能使用n1 因为n1 是 test函数的局部变量 fmt.Println(“n1= “, n1) }


5. **基本数据类型**和**数组**默认都是**值传递**的,即进行值拷贝。在函数内修改,不会影响到原来的值
5. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。
5. Go 函数**不支持函数重载**
```go
func test02(n1 int) {

}

func test02(n1 int, n2 int) {//报错 函数重复定义

}
  1. 在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用 ```go package main import ( “fmt” )

//在Go中,函数也是一种数据类型, //可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用 func getSum(n1 int, n2 int) int { return n1 + n2 }

func main() {

a := getSum
fmt.Printf("a的类型%T, getSum类型是%T\n", a, getSum)

res := a(10, 40) // 等价  res := getSum(10, 40)
fmt.Println("res=", res)

}

输出
```go
a的类型func(int, int) int, getSum类型是func(int, int) int
res= 50
  1. 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用 ```go package main import ( “fmt” )

//在Go中,函数也是一种数据类型, //可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用 func getSum(n1 int, n2 int) int { return n1 + n2 }

//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用 func myFun(funvar func(int, int) int, num1 int, num2 int ) int { return funvar(num1, num2) }

func main() { res2 := myFun(getSum, 50, 60) fmt.Println(“res2=”, res2) }

输出
```go
res2= 110
  1. 为了简化数据类型定义,Go 支持自定义数据类型 基本语法:type 自定义数据类型名 数据类型

    // 给int取了别名 , 在go中 myInt 和 int 虽然都是int类型,但是go认为myInt和int两个类型
    type myInt int 
    
    var num1 myInt // 
    var num2 int
    num1 = 40
    num2 = int(num1) //各位,注意这里依然需要显示转换,go认为myInt和int两个类型
    fmt.Println("num1=", num1, "num2=",num2) // num1= 40 num2= 40
    
  2. 支持对函数返回值命名

    //支持对函数返回值命名
    func getSumAndSub(n1 int, n2 int) (sum int, sub int){
    sub = n1 - n2
    sum = n1 + n2
    return
    }
    
  3. 使用 _ 标识符,忽略返回值

    _, sub := getSumAndSub(1, 2)
    
  4. Go 支持可变参数

    //案例演示: 编写一个函数sum ,可以求出  1到多个int的和
    //可以参数的使用 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
    func sum(n1 int, args... int) int {
    sum := n1 
    //遍历args 
    for i := 0; i < len(args); i++ {
        sum += args[i]  //args是slice切片,通过args[index],可以访问到各个值
    }
    return sum
    }
    

    init 函数

    基本介绍

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

    案例说明

    ```go package main import ( “fmt” )

//init函数,通常可以在init函数中完成初始化工作 func init() { fmt.Println(“init()…”) //1 }

func main() { fmt.Println(“main()…”) //2 }

输出
```go
init()...
main()...

inti 函数的注意事项和细节

  1. 如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程 全局变量定义->init 函数->main 函数 ```go package main import ( “fmt” )

var age = test()

//为了看到全局变量是先被初始化的,我们这里先写函数 func test() int { fmt.Println(“test()”) //1 return 90 }

//init函数,通常可以在init函数中完成初始化工作 func init() { fmt.Println(“init()…”) //2 }

func main() { fmt.Println(“main()…age=”, age) //3

}

输出
```go
test()
init()...
main()...age= 90
  1. init 函数最主要的作用,就是完成一些初始化的工作

    匿名函数

    介绍

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

    匿名函数使用方式 1

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

     //在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
    
     //案例演示,求两个数的和, 使用匿名函数的方式完成
     res1 := func (n1 int, n2 int) int {
         return n1 + n2
     }(10, 20)
    
     fmt.Println("res1=", res1)//res1= 30
    

    匿名函数使用方式 2

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

     //将匿名函数func (n1 int, n2 int) int赋给 a变量
     //则a 的数据类型就是函数类型 ,此时,我们可以通过a完成调用
     a := func (n1 int, n2 int) int {
         return n1 - n2
     }
     res2 := a(10, 30)
     fmt.Println("res2=", res2)//res2= -20
     res3 := a(90, 30)
     fmt.Println("res3=", res3)//res3= 60
    

    全局匿名函数

    如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效 ```go package main import ( “fmt” )

var ( //fun1就是一个全局匿名函数 Fun1 = func (n1 int, n2 int) int { return n1 * n2 } )

func main() { //全局匿名函数的使用 res4 := Fun1(4, 9) fmt.Println(“res4=”, res4)//res4= 36 }

<a name="D0cKY"></a>
# 闭包
<a name="GeUBv"></a>
## 介绍
闭包就是一个**函数**和与**其相关的引用环境**组合的一个**整体**(实体)
<a name="vhlxl"></a>
## 案例演示
```go
//累加器
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(1)) // 11
    fmt.Println(f(2)) // 13
    fmt.Println(f(3)) // 16

}

对上面代码的说明和总结

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

    闭包的最佳实践

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

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

    return func(name string) string {
        //如果 name 没有指定后缀,则加上,否则就返回原来的名字
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }

        return name
    }
}
//传统方式实现
func makeSuffix2(suffix string, name string) string {

    //如果 name 没有指定后缀,则加上,否则就返回原来的名字
    if !strings.HasSuffix(name, suffix) {
        return name + suffix
    }

    return name

}

func main() {

    //测试makeSuffix 的使用
    //返回一个闭包
    f2 := makeSuffix(".jpg")//如果使用闭包完成,好处是只需要传入一次后缀。
    fmt.Println("文件名处理后=", f2("winter"))   // winter.jgp
    fmt.Println("文件名处理后=", f2("bird.jpg")) // bird.jpg

    fmt.Println("文件名处理后=", makeSuffix2("jpg", "winter"))   // winter.jgp
    fmt.Println("文件名处理后=", makeSuffix2("jpg", "bird.jpg")) // bird.jpg

}

上面代码的总结和说明:

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

    函数的defer

    为什么需要defer

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

    快速入门案例

    ```go func sum(n1 int, n2 int) int { //当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈) //在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈 //当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行 defer fmt.Println(“ok1 n1=”, n1) //defer 3. ok1 n1 = 10 defer fmt.Println(“ok2 n2=”, n2) //defer 2. ok2 n2= 20

    n1++ // n1 = 11 n2++ // n2 = 21 res := n1 + n2 // res = 32 fmt.Println(“ok3 res=”, res) // 1. ok3 res= 32 return res }

func main() { res := sum(10, 20) fmt.Println(“res=”, res) // 4. res= 32 }

输出
```go
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32

defer 的注意事项和细节

  1. 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中[暂时称该栈为 defer 栈], 然后继续执行函数下一个语句
  2. 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制)
  3. 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈
  4. 在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源), 可以执行 defer file.Close() defer connect.Close()
  5. 在 defer 后,可以继续使用创建资源
  6. 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.

    函数参数传递方式

    基本介绍

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

    两种传递方式

  7. 值传递

  8. 引用传递

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

值类型和引用类型

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

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

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

  4. 引用类型默认是引用传递:变量存储的是地址,这个地址对应的空间才是正真存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就会成为一个垃圾,由GC来回收
  5. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用 。

    变量作用域

  6. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部 ```go //函数 func test() { //age 和 Name的作用域就只在test函数内部 age := 10 Name := “tom~” }

func main() {

}


2. 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
```go
//函数外部声明/定义的变量叫全局变量,
//作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
var age int = 50
var Name string = "jack~"

//函数
func test() {
    //age 和 Name的作用域就只在test函数内部
    age := 10
    Name := "tom~"
    fmt.Println("age=", age) // 10
    fmt.Println("Name=", Name) // tom~
}

func main() {
    fmt.Println("age=", age) //  50
    fmt.Println("Name=", Name) // jack~
    test()
}
  1. 如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块

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

    字符串常用的系统函数

    字符串在我们程序开发中,使用的是非常多的,常用的函数需要掌握

  2. 统计字符串的长度,按字节len(str)

     //统计字符串的长度,按字节 len(str)
     //golang的编码统一为utf-8 (ascii的字符(字母和数字) 占一个字节,汉字占用3个字节)
     str := "hello北" 
     fmt.Println("str len=", len(str)) // str len= 8
    
  3. 字符串遍历,同时处理有中文的问题r := []rune(str)

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

    输出

    字符=h
    字符=e
    字符=l
    字符=l
    字符=o
    字符=你
    字符=好
    
  4. 字符串转整数n, err := strconv.Atoi("12")

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

     str = strconv.Itoa(12345)
     fmt.Printf("str=%v, str=%T\n", str, str)//str=12345, str=string
    
  6. 字符串 转 []byte var bytes = []byte("hello go")

     var bytes = []byte("hello go")
     fmt.Printf("bytes=%v\n", bytes)//bytes=[104 101 108 108 111 32 103 111]
    
  7. []byte 转 字符串:str = string([]byte{97, 98, 99})

     str = string([]byte{97, 98, 99}) 
     fmt.Printf("str=%v\n", str)//str=abc
    
  8. 10 进制转 2, 8, 16 进制 str = strconv.FormatInt(123, 2)

     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
    
  9. 查找子串是否在指定的字符串中:strings.Contains("seafood", "foo")

     b1 := strings.Contains("seafood", "mary")
     fmt.Printf("b1=%v\n", b1)//b1=false
     b2 := strings.Contains("seafood", "foo")
     fmt.Printf("b2=%v\n", b2)//b2=true
    
  10. 统计一个字符串有几个指定的子串 strings.Count("ceheese", "e")

     num := strings.Count("ceheese", "e")
     fmt.Printf("num=%v\n", num)//num=4
    
  11. 不区分大小写的字符串比较(== 是区分字母大小写的)fmt.Println(strings.EqualFold("ab", "Ab"))

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

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

    index = strings.LastIndex("go golang", "go") //3
    fmt.Printf("index=%v\n", index)//index= 3
    
  14. 将指定的子串替换成 另外一个子串: strings.Replace("hello hello world", "hello", "你好", n) n 可以指定你希望替换几个,如果 n=-1 表示全部替换

    str2 = "hello hello world"
    str = strings.Replace(str2, "hello", "你好", 1)
    fmt.Printf("str=%v str2=%v\n", str, str2)//str=你好 hello world str2=hello hello world
    
  15. 按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组 strings.Split("hello,wrold,ok", ",")

    strArr := strings.Split("hello,wrold,ok", ",")
    fmt.Printf("strArr=%v\n", strArr)//strArr=[hello wrold ok]
    
  16. 将字符串的字母进行大小写的转换strings.ToLower("Go") strings.ToUpper("Go")

    str = "goLang Hello"
    str = strings.ToLower(str)
    fmt.Printf("str=%v\n", str) //str=golang hello
    str = strings.ToUpper(str)
    fmt.Printf("str=%v\n", str) //str=GOLANG HELLO
    
  17. 将字符串左右两边的空格去掉:strings.TrimSpace(" hello world ")

    str = strings.TrimSpace(" hello world ")
    fmt.Printf("str=%q\n", str)//str="hello world"
    
  18. 将字符串左右两边指定的字符去掉 strings.Trim("! he! llo! ", " !")将左右两边 !和 “ “去掉

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

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

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

    时间和日期相关函数

    说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。

  23. 时间和日期相关函数,需要导入 time 包

    import    "time"
    
  24. 获取当前时间

     now := time.Now()
     fmt.Printf("now=%v now type=%T\n", now, now)//now=2022-05-17 17:06:30.530646 +0800 CST m=+0.001361101 now type=time.Time
    
  25. 通过now可以获取到年月日,时分秒

     //1. 获取当前时间
     now := time.Now()
     fmt.Printf("now=%v now type=%T\n", now, now)
    
     //2.通过now可以获取到年月日,时分秒
     fmt.Printf("年=%v\n", now.Year())//年=2022
     fmt.Printf("月=%v\n", now.Month())//月=May
     fmt.Printf("月=%v\n", int(now.Month()))//月=5
     fmt.Printf("日=%v\n", now.Day())//日=17
     fmt.Printf("时=%v\n", now.Hour())//时=17
     fmt.Printf("分=%v\n", now.Minute())//分=6
     fmt.Printf("秒=%v\n", now.Second())//秒=30
    
  26. 格式化日期时间

    1. 方式 1: 就是使用 Printf 或者 SPrintf
    2. 方式 2: 使用 time.Format() 方法完成

      //格式化日期时间
      //  方式 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)
      
      //  方式 2: 使用 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()
      

      输出 ```go 当前年月日 2022-5-17 17:6:30 dateStr=当前年月日 2022-5-17 17:6:30

2022-05-17 17:06:30 2022-05-17 17:06:30 2022

对上面代码的几点说明:

- `"2006/01/02 15:04:05"`这个字符串的各个数字是固定的,必须是这样写
- `"2006/01/02 15:04:05"`这个字符串各个数字可以自由的组合,这样可以按程序需求来返回时间和日期
- 十二小时制:`"2006/01/02 03:04:05"`  二十四小时制:`"2006/01/02 15:04:05"`
5. 时间的常量
```go
Nanosecond = 1 //纳秒
Microsecond    = 1000 * Nanosecond    //微秒
Millisecond    = 1000 * Microsecond //毫秒
Second        = 1000 * Millisecond //秒
Minute    = 60 * Second //分钟
Hour    = 60 * Minute //小时

常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到 100 毫秒100 * time. Millisecond

  1. 结合 Sleep 来使用一下时间常量

     //需求,每隔1秒中打印一个数字,打印到10时就退出
     //需求2: 每隔0.1秒中打印一个数字,打印到10时就退出
     i := 0
     for {
         i++
         fmt.Println(i)
         //休眠
         time.Sleep(time.Second) //休眠1秒
         // time.Sleep(time.Millisecond * 100) //休眠0.1秒
         if i == 10 {
             break
         }
     }
    
  2. time 的 Unix 和 UnixNano 的方法

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

    输出

    unix时间戳=1652837674 unixnano时间戳=1652837674755709400
    

    时间和日期的课堂练习 ```go package main import ( “fmt” “time” “strconv” )

func test03() {

str := ""
for i := 0; i < 100000; i++ {
    str += "hello" + strconv.Itoa(i)//数字转字符串
}

}

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

<a name="POATc"></a>
# 内置函数
Golang 设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为 Go 的内置函数。<br />文档:[https://studygolang.com/pkgdoc](https://studygolang.com/pkgdoc) -> builtin<br />几个常用的内置函数

1. len:用来求长度,比如 string、array、slice、map、channel
1. new:用来**分配内存**,主要用来**分配值类型**,比如 int、float32,struct...返回的是指针
```go
    num1 := 100
    fmt.Printf("num1的类型%T , num1的值=%v , num1的地址%v\n", num1, num1, &num1)

    num2 := new(int) // *int
    //num2的类型%T => *int
    //num2的值 = 地址 0xc0000160c0 (这个地址是系统分配)
    //num2的地址%v = 地址 0xc000006030  (这个地址是系统分配)
    //num2指向的值 = 100
    *num2  = 100
    fmt.Printf("num2的类型%T , num2的值=%v , num2的地址%v\n num2这个指针,指向的值=%v", 
        num2, num2, &num2, *num2)

输出

num1的类型int , num1的值=100 , num1的地址0xc000016088
num2的类型*int , num2的值=0xc0000160c0 , num2的地址0xc000006030
 num2这个指针,指向的值=100
  1. make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice。

    错误处理

    引出错误处理

    ```go func test() { num1 := 10 num2 := 0 res := num1 / num2 fmt.Println(“res=”, res) }

func main() { //测试错误 test() fmt.Println(“main()下面的代码…”) }

对上面代码的说明

1. 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃)
1. 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
1. 这里引出我们要将的错误处理机制
<a name="C1r7p"></a>
## 基本说明

1. Go 语言追求简洁优雅,所以,Go 语言**不支持传统的 try…catch…finally**  这种处理。
1. Go 中引入的处理方式为:**defer, panic, recover**
1. 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
<a name="mXU3b"></a>
## 使用defer+recover 来处理错误
```go
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()
    fmt.Println("main()下面的代码...")
}

错误处理的好处

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

自定义错误

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

  1. errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
  2. panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序 ```go //函数去读取以配置文件init.conf的信息 //如果文件名传入不正确,我们就返回一个自定义的错误 func readConf(name string) (err error) { if name == “config.ini” {
     //读取...
     return nil
    
    } else {
     //返回一个自定义错误
     return errors.New("读取文件错误")
    
    } }

func test() { //err := readConf(“config.ini”) err := readConf(“config1.ini”) if err != nil { //如果读取文件发送错误,就输出这个错误,并终止程序 panic(err) } fmt.Println(“test()继续执行….”) }

func main() { //测试自定义错误的使用 test() fmt.Println(“main()下面的代码…”) } ```