Go (二) 函数部分1 -- 函数定义,传参,返回值,作用域,函数类型,defer语句,匿名函数和闭包,panic

一、函数

1.1、函数的定义

Go语言中定义函数使用func关键字,具体格式如下:

参数返回值时:
func 函数名(参数 type) (返回值type){
    函数体代码

    return
}

参数无返回值时:

func 函数名(参数 type){
    函数体代码

}

无参数无返回值时:

func 函数名(){
    函数体代码

}

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

1.2、函数的使用(汇总)

// 定义一个求和函数(有参数),有return返回值时(需要给返回值定义一个类型)
func s1(x int, y int) int {
	return x + y
}

// 定义一个函数(无参数),有return返回值时(需要给返回值定义一个类型)
func s4() string {
	return "sudada"
}

// 定义一个求和函数(有参数),没有return返回值时
func s2(x int, y int) {
	fmt.Println(x + y)
}

// 定义一个函数(无参数),没有return返回值时
func s3() {
	fmt.Println("szq")
}

// return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名
// return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq"
func s5(x int, y int) (szq int) {
	szq = x + y
	return // 这里就可以不写szq,写上也没关系
}

// 多个返回值
func s6() (int, string) {
	return 1, "2"
}

// 参数类型的简写(2个参数连续且类型一致时,可以这么写)
func s7(x, y int) int {
	return x + y
}

// 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值)
func s8(x string, y ...int) {
	fmt.Println(x)
	fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片
}

// go语音中函数没有默认参数的概念
func main() {
	fmt.Println(s1(1, 2))
	s2(2, 3)
	s3()
	fmt.Println(s4())
	fmt.Println(s5(2, 2))
	fmt.Println(s6())
	s8("szq")
}

1.3、函数传参

1.3.1、形参类型可以简写

函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:

// 参数类型的简写(2个参数连续且类型一致时,可以这么写)
func s7(x, y int) int {
	return x + y
}

上面的代码中,s7函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

1.3.2、可变长参数

可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识
注意:可变参数通常要作为函数的最后一个参数。

// 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值)
func s8(x string, y ...int) {
	fmt.Println(x)
	fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片
}

可变长参数使用:

func intSum2(x ...int) int {
	fmt.Println(x) // x是一个切片
	sum := 0
    // for range循环时,key是索引,value是值
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

func main() {
	ret1 := intSum2()
	ret2 := intSum2(10)
	ret3 := intSum2(10, 20)
	ret4 := intSum2(10, 20, 30)
	fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60
}

固定参数搭配可变参数使用时,可变参数放在固定参数后面,示例代码如下:

func intSum3(x int, y ...int) int {
	fmt.Println(x, y)
	sum := x
	for _, v := range y {
		sum = sum + v
	}
	return sum
}

func main() {
	ret5 := intSum3(100)
	ret6 := intSum3(100, 10)
	ret7 := intSum3(100, 10, 20)
	ret8 := intSum3(100, 10, 20, 30)
	fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160
}

本质上,函数的可变参数是通过切片来实现的。

1.4、函数 返回值

Go语言中通过return关键字向外输出返回值。

1.4.1、多返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}
func main() {
	a, b := calc(1, 2)
	fmt.Println(a)
	fmt.Println(b)
}

1.4.1、多返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

// return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名
// return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq"
func s5(x int, y int) (szq int) {
	szq = x + y
	return // 这里就可以不写szq,写上也没关系
}

// 例子2
func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

func main() {
	a, b := calc(1, 2)
	fmt.Println(a)
	fmt.Println(b)
}

1.5、函数的递归调用(函数体内调用了函数本身)

函数递归需要遵守的重要原则:

1.执行一个函数时,就创建一个新的受保护的独立空间。

2.函数的局部变量是独立的,不会相互影响。

3.递归必须向退出递归的条件逼近(例子里面的"x--"),否则就是死循环。

4.当一个函数执行完毕,或者遇到return,就会返回,遵循谁调用,就将返回结果给谁。同时函数执行完毕或被返回时,该函数本身也会被回收。

package main

import (
	"fmt"
)

func test(x int)  {
	if x > 2 {
        // 递归必须向退出递归的条件逼近
		x--
		test(x)
	}
	fmt.Println(x)
}

func test1(x int)  {
	if x > 2 {
        // 递归必须向退出递归的条件逼近
		x--
		test1(x)
	} else {
		fmt.Println(x)
	}
}

func main() {
	test(4)  // 返回值:2,2,3
	test1(4) // 返回值:2
}

1.6、init函数

init函数会在main函数执行之前调用,通常在init函数中完成初始化的工作

package main

import "fmt"

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

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

// 返回值: init main

细节:

1、一个文件同时包含全局变量定义,init函数main函数,则执行的顺序是:全局变量定义 -> init函数 -> main函数

package main

import "fmt"

var age = test()

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

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

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

// 返回值:test init main

二、包

包的基本语法:package "包名"

引入包:import "包的路径"

使用包内的函数:"包名".xxx

2.1、包的简单使用例子

// 外部的包(utils.go)
package utils

// 这个函数是给外部使用(该函数可以导出),所以函数首字母大写
func Calc(x, y int) (sum , sub int) {
	sum = x + y
	sub = x - y
	return
}

// 引用包(main.go)
package main
import (
    "fmt"
    "Study/go006/utils"
    // abc "Study/go006/utils" 还可以写成别名的方式,使用的使用就是abc.xxx
)

func main() {
	a, b := utils.Calc(20, 10)
	fmt.Println(a, b)  // 返回值:30,10
}

2.2、包的注意事项

1.文件的包名(package utils)通常和文件(utils.go)所在的文件夹名(utils)一致,一般为小写字母。

2.import "PATH/包名(文件夹名)"

2.1.在import包时,路径从$GOPATH的src目录下开始(不包含src),编译器会自动从src目录下开始引用。

3.使用包的数据utils.xxx时,这里的utils就是"包名(package utils)"如果包名改为"(package abc)",那么使用时就要写成abc.xxx

4.包内(可以导出的)函数、变量,首字母必须大写

5.如果给包(utils)起了别名为(abc),那么在使用包时,使用别名即可abc.xxx

6.同一个包下,不能有重复函数名,全局变量名

7.如果要编译一个可执行的文件,就需要将这个包声明为main (main包只能有一个),即package main,如果是一个库文件,则包名可以自定义。(编译时需要编译main包所在的文件夹)

三、函数作用域

3.1、全局变量

全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。

函数中查找变量的顺序:
   1.先在函数的内部查找
   2.找不到就往函数的外面查找,一直找到全局,如果找不到就报错。

var szq = 123

func s0()  {
	fmt.Println(szq)
}

func main() {
	s0() // 123
}

3.2、局部变量

1.局部变量又分为两种: 函数内定义的变量无法在该函数外使用

func testLocalVar() {
	//定义一个函数局部变量x,仅在该函数内生效
	var x int64 = 100
	fmt.Printf("x=%d\n", x)
}

func main() {
	testLocalVar()
	fmt.Println(x) // 此时无法使用变量x
}

2.如果局部变量和全局变量重名,优先访问局部变量

//定义全局变量num
var num int64 = 10

func testNum() {
	num := 100
	fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
}
func main() {
	testNum() // num=100
}

3.3、语句块定义的变量

1.通常我们会在if条件判断、for循环、switch语句上使用这种定义变量的方式。

func testLocalVar2(x, y int) {
	fmt.Println(x, y) //函数的参数也是只在本函数中生效
	if x > 0 {
		z := 100  //变量z只在if语句块生效
		fmt.Println(z)
	}
	fmt.Println(z) //此处无法使用变量z
}
func main() {
	testLocalVar2(1,2)
}

2.还有for循环语句中定义的变量,也是只在for语句块中生效

func testLocalVar3() {
	for i := 0; i < 10; i++ {
		fmt.Println(i) //变量i只在当前for语句块中生效
	}
	fmt.Println(i) //此处无法使用变量i
}

func main() {
	testLocalVar3()
}

四、函数类型与变量

4.1、定义函数类型

4.1.1.什么是函数类型,举个例子:

// 函数作为参数赋值给变量时,有哪些类型
func s1(){
	fmt.Println("szq")
}

func s2() int{
	return 18
}

func s3(x int) int{
	return x+1
}

func main() {
	a := s1
	// 打印变量a的类型
	fmt.Printf("%T\n",a) //func():这是一个普通的函数类型
    a()  // 此时a就等于函数s1,加()即可执行该函数,返回值"szq"

	b := s2
	// 打印变量b的类型
	fmt.Printf("%T\n",b) //func() int :这是一个函数类型(包括函数的返回值类型)
    fmt.Println(b())  // 此时b就等于函数s2,加()即可执行该函数,返回值18

	c := s3
	// 打印变量c的类型
	fmt.Printf("%T\n",c) //func(int) int :这是一个函数类型(包括函数的参数类型和函数的返回值类型)
    fmt.Println(c(1))  // 此时c就等于函数s3,加()即可执行该函数,返回值2
}

4.1.2.函数作为一个参数

// 一、函数作为参数时,无形参
// 定义函数s1
func s1() int {
	return 18
}

// s1函数作为参数传递给s2函数
// x func() int 说明:
    //  "x"(形参的名称)
    //  "func() int"形参x的类型是一个"函数类型",该函数有一个返回值,返回值的类型是int
func s2(x func() int){
	res := x()
	fmt.Println(res)
}

func main() {
	re1 := s1 // 将函数赋值给变量re1
	fmt.Printf("%T\n",re1) // func() int

	s2(s1)  // 函数s1作为参数传递给函数s2(s1函数的类型必须满足s2的接收参数类型,即:func() int)
	s2(re1) // 变量re1作为参数传递给函数s2(变量re1的类型必须满足s2的接收参数类型,即:func() int)
}

// 二、函数作为参数时,有形参
func s3(x int) int{
	return x+1
}

// funvar func(int) int 说明:
    //  "funvar"(形参的名称)
    //  "func(int) int"形参funvar的类型是一个"函数类型",该函数会接收一个参数,参数类型是int,并且有一个返回值,返回值的类型是int
func szq(funvar func(int) int, num int) int {
	res := funvar(20)  // 这里funvar(20)就等于s3(20),拿到的是函数s3的执行结果
	return res + num
}

func main() {
	fmt.Println(szq(s3,10))  // 返回值:31
}

4.1.3.函数作为一个返回值

// 定义函数s1
func s1() int {
	return 18
}

func ff(a int,b int)int{
	return a+b
}

// 函数作为一个返回值返回
// x func() int :形参x的类型是一个"函数类型",该函数有一个返回值,返回值的类型是int
// y func(int,int)int :"函数s3的返回值类型是一个"函数类型",该函数会接收2个参数,参数类型是int,并且有一个返回值,返回值的类型是int
func s3(x func() int)(y func(int,int) int){
	fmt.Println(x())
	res := func(a int,b int)int{
		return a+b
	}
	//res := ff // 这里也可以换一种写法,ff函数的格式需要满足y即可
	return res
}

func main() {
	szq := s3(s1) // 把函数s1做为参数传入到函数s3中,执行函数s3的代码后会打印18,并且拿到一个返回值res,此时的res是一个函数类型。
	fmt.Println(szq(1,2)) // 拿到的返回值res类型为"函数类型:func(int,int)int",那么就可以正常的传值,查看执行结果
}
//返回结果:
//18
//3

4.2、函数类型的变量 (变量的类型为函数类型,然后为该变量赋值)

了解以上函数类型之后,我们可以使用type关键字来定义一个函数类型的变量,具体格式如下:
type myFun func(int, int) int
上面语句定义了一个变量myFun,是一个函数类型这种函数接收两个int类型的参数并且返回一个int类型的返回值。

package main

import "fmt"

// 定义一个变量"myFunc",类型为函数类型(函数需要接收2个int类型的参数,并有一个返回值也是int类型)
type myFunc func(int, int) int

// 定义一个全局变量c,c的类型myFunc
var c myFunc

// 定义一个普通函数,类型为:add(x, y int) int
func add(x, y int) int {
	return x + y
}

// 形参 n func(int, int) int 可以写成 n myFunc (变量myFunc的类型就是函数类型)
func szq(a,b int, n myFunc) int  {
	res := n(a,b)
	return res
}

func main() {
	c = add  // 给变量c做赋值操作(变量c = add函数)
	fmt.Println(add(1,2))  // 返回值为:3
	fmt.Printf("%T\n",c)   // main.myFunc
	fmt.Println(c(1,2))    // 像调用add一样调用c,返回值为:3
	fmt.Println(szq(1,2, add))  // 返回值为:3
}

五、defer语句 (延时机制)

5.1、Go语言中的defer语句会将其后面跟随的语句进行延迟处理

defer归属的函数即将返回时(简单理解为函数内的逻辑执行完毕后,在执行defer语句),将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行(先入后出)

简单例子:

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}
//输出结果:
//start
//end
//3
//2
//1

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

5.2、defer的执行时机(先给返回值赋值,然后执行defer,最后执行return)

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值RET(return)指令两步。defer语句执行的时机就在返回值赋值操作后,RET(return)指令执行前具体如下图所示:

defer执行时机

简单例子:

package main

import "fmt"

func sum(a,b int) int {
	defer fmt.Println("3")
	defer fmt.Println("2")
	defer fmt.Println("1")
	res := a+b
	fmt.Println("sum", res)
	return res
}

func main() {
	fmt.Println("main")
	res := sum(2,3)
	fmt.Println("mian-res", res)
}

// 返回值
// main
// sum 5
// 1
// 2
// 3
// mian-res 5

5.3、defer经典案例

// 第一步:返回值赋值
//defer
//第二步:真的的return,返回一个值

func f1() int {
	x := 5
	defer func() {
		x++  // 修改的是x,不是返回值
	}()
	return x
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5   // 返回值为x
}

func f3() (y int) {
	x := 5
	defer func() {
		x++  // 修改的是x
	}()
	return x // 返回值为y,y的值=x=5
}
func f4() (x int) {
	defer func(x int) {
		x++  // 改变的是函数中的副本
	}(x)
	return 5 // 返回值=x=5
}

func main() {
	fmt.Println(f1()) //5
	fmt.Println(f2()) //6
	fmt.Println(f3()) //5
	fmt.Println(f4()) //5
}

5.4、defer面试题

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	// var x int = 1
	x := 1
	// var y int = 2
	y := 2
	// 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去。 即:defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3),详见下面的分析结果:
	defer calc("AA", x, calc("A", x, y))
	// 这里的x因为在前面已经做了定义,所以可以直接用"="号赋值
	x = 10
	// 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去即:defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12),详见下面的分析结果:
	defer calc("BB", x, calc("B", x, y))
	// 这里的y因为在前面已经做了定义,所以可以直接用"="号赋值
	y = 20
}

// 分析以上代码的执行过程:
// 1)x := 1
// 2)y := 2
// 3)defer calc("AA", x, calc("A", x, y)) == defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3)
// 4)x = 10
// 5)defer calc("BB", x, calc("B", x, y)) == defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12)
// 6)y = 20
// 7)defer calc("BB", 10, 12) == BB 10 12 22
// 8)defer calc("AA", 1, 3) == AA 1 3 4

//输出结果:
//A 1 2 3      先执行函数内的函数
//B 10 2 12    先执行函数内的函数
//BB 10 12 22  在按照defer的逻辑,最后定义的最先执行
//AA 1 3 4     按照defer的逻辑,最先定义的最后执行

六、匿名函数和闭包

6.1、匿名函数 (匿名函数多用于实现回调函数和闭包,匿名函数一般都用在函数内部)

函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:
func (参数) (返回值){
    函数体
}

1.把匿名函数赋值给一个变量

// 匿名函数使用1:将匿名函数赋值给一个"全局"变量,该匿名函数可以任意地方使用
var f1 = func(x,y int){
	fmt.Println(x+y)
}

func main() {
	f1(1,2) // 调用变量f1就是调用匿名函数,返回值为:3

	// 匿名函数使用2:在函数内部将匿名函数赋值给一个变量,该匿名函数只能在函数内部使用
	f2 := func(x,y int){
		fmt.Println(x+y)
	}
	f2(10,20) // 调用匿名函数,返回值为:3

	// 如果只是调用一次的函数,可以简写成"立即执行函数"
	func(x,y int){
		fmt.Println(x+y)
	}(2,3) // 调用"立即执行函数",返回值为:5
}

2.匿名函数作为立即执行函数(只是一次调用的函数)

func main() {
	// 如果只是调用一次的函数,可以简写成"立即执行函数"
	func(x,y int){
		fmt.Println(x+y)
	}(2,3) // 调用"立即执行函数",返回值为:5
}

6.2、闭包 (一个函数既"引用了函数内的变量",又"引用了函数外的变量")

闭包的底层原理:
  1.函数可以作为返回值,
  2.函数内部查找顺序:先在函数内找,然后在到函数外部找。

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。 首先我们来看一个例子:

// 例子1:
// 闭包的实际应用,要求:s1(s2) 把s2作为参数传入到s1
func s1(f func()) {
	fmt.Println("s1")
	f()
}

func s2(x, y int) {
	fmt.Println("s2")
	fmt.Println(x + y)
}

//s3函数要接收s2函数的传参,并且返回值的类型要符合s1函数参数类型
//f func(int, int), x, y int 这些都是给s2函数使用的
//func() 这个是给s1函数使用的
func s3(f func(int, int), x, y int) func() {
	res := func() {
		f(x, y)
	}
	return res // 这里的返回值res是一个func()类型,符合s1函数的接收类型
}

func main() {
	ret := s3(s2, 100, 300) // 这里ret也就等于s3函数内的res
	s1(ret)                 // s1函数传入ret,本质上执行的是s2函数的代码。
}

//输出结果
//s1
//s2
//400

// 例子2:
import "fmt"

// 函数的返回值是一个匿名函数,但这个匿名函数引用到函数外的变量n,因此这个匿名函数就和变量n形成一个整体,构成闭包。
// 当反复调用f函数时,因为n只初始化一次,因此每调用一次就进行累计。
func addUpper() func(int) int {
	var n int = 10
	return func(x int) int {
		n = n +x
		return n
	}
}

func main() {
	f1 := addUpper()
	fmt.Println(f1(1))  // 返回值:11
	fmt.Println(f1(2))  // 返回值:13
	fmt.Println(f1(3))  // 返回值:16
}

例子2:在函数内修改函数的参数值

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	f1, f2 := calc(10)
	fmt.Println(f1(1), f2(2)) //11 9,这里每次改的都是calc函数内的base,也就是说随着每次的改修,base的值会发生改变。
	fmt.Println(f1(3), f2(4)) //12 8
	fmt.Println(f1(5), f2(6)) //13 7
}

七、内置函数

7.1、内置函数介绍

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make用来分配内存,主要用来分配引用类型,比如channel、map、slice
append用来追加元素到数组、slice中
panic和recover用来做错误处理

7.2、panic 主动抛出异常

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	panic("出现了严重的错误!")  // 主动报错
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

// 输出结果:
//	func A   			  // 报错之前执行的代码
//	panic: 出现了严重的错误! // 报错的输出

//	goroutine 1 [running]:
//	main.funcB(...)     // 报错的代码位置点
//		/Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:10
//	main.main()         // 报错代码的执行点
//		/Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:18 +0x96

7.3、panic + recover + defer 接收错误信息后,抛出异常提示,同时代码继续执行

Go中可以抛出一个panic的异常,然后再defer中通过recover捕获这个异常,然后正常执行代码。

注意:
recover()必须搭配defer使用。
defer一定要在可能引发panic的语句之前定义。

例子1:使用panic抛出异常,然后使用recover + defer捕捉异常

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover() // recover()拿到的就是panic的输出。使用recover()后程序不会崩溃退出,继续往下执行。
		fmt.Println(err)
	}()
	panic("出现了严重的错误!") // defer要在panic之前定义,否则panic程序崩溃,没法执行defer语句了。
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

//输出结果:
//func A
//出现了严重的错误!
//func C

例子2:使用recover + defer 捕捉异常

import "fmt"

// 使用defer + recover来捕获和处理异常
func test()  {
	defer func() {
		err := recover() // "recover()"是一个内置函数,可以捕捉到异常
		if err != nil {  // 如果错误不为空,说明捕捉到了异常
			fmt.Println(err)  // 输出异常信息
		}
	}()
	a := 10
	b := 0
	res := a / b
	fmt.Println(res)
}

func main() {
	test()  // 抛出异常信息:runtime error: integer divide by zero
	fmt.Println("main代码逻辑")
}

例子3:使用自定义错误:errors.New("读取文件失败")

import (
	"errors"
	"fmt"
)

func test(name string) (err error) {
	if name == "config.ini" {
		return nil
	} else {
		// 返回一个自定义错误
		return errors.New("读取文件失败")
	}
}

func test02()  {
	err := test("config.123")
	if err != nil {
		// 读取文件发生错误时,输出自定义错误,并终止程序
		panic(any(err))
		// Golang 1.18 版本开始引入any类型可以替代空接口 interface{} ,直接写成panic(err)会有错误提示,但是执行不会报错
	}
	fmt.Println("test02...")
}

func main() {
	test02()
	fmt.Println("main...")
}

// 返回值:panic: 读取文件失败

7.4、内置函数(rune,strconv,byte)例子

package main

import (
	"fmt"
	"strconv"
)

func main() {
	str2 := "hello上海"
	// 字符串便利时,处理有中文的场景,使用:"[]rune(xxx)"
	str3 := []rune(str2)
	for n := 0; n < len(str3); n++ {
		fmt.Printf("%c\n",str3[n]) // 返回值:"hello上海"
	}

	// 字符串转为整数
	num, err := strconv.Atoi("123")
	// err == nil时,说明无报错
	if err != nil {
		fmt.Println("error: ",err)
	} else {
		fmt.Println("value: ",num)
	}

	// 整数转化为字符串
	str := strconv.Itoa(12345)
	fmt.Println("str value: ",str) // 返回值:str value:  12345

	// 字符串转[]byte
	var bytes = []byte("hello go")
	fmt.Printf("%v\n",bytes) // 返回值:[104 101 108 108 111 32 103 111]

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

7.5、内置函数 new(用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针)

import "fmt"

func main() {
	var num int = 100
	fmt.Printf("%T %v %v\n",num,num,&num)
	// 返回值:int 100 0xc00000a0e8

	num2 := new(int)
	*num2 = 100
	fmt.Printf("%T %v %v %v\n",num2,num2,&num2,*num2)
	// 返回值:*int 0xc00000a108 0xc00006a028 100
}

7.6、内置函数 make(用来分配内存,主要用来分配引用类型,比如channel、map、slice)

八、常用模块

8.1、strings模块的常用方法

import (
	"fmt"
	"strings"
)

func main() {
	// 查找指定字符串
	// 判断字符串"food"是否存在于"seafood"中,存在返回true不存在返回false
	b := strings.Contains("seafood","food")
	fmt.Printf("%v\n",b) // 返回值:true
	// 判断字符串"asd"是否存在于"seafood"中,存在返回true不存在返回false
	c := strings.Contains("seafood","asd")
	fmt.Printf("%v\n",c) // 返回值:false

	// 统计字符串的数量
	// 字符串"e"在"cehese"出现的次数
	num1 := strings.Count("cehese","e")
	fmt.Printf("%v\n",num1) // 返回值:false

	// 字符串替换,1指的是替换1次,为-1表示替换全部
	// 把"go"替换为"go语言",针对"go go hello"做替换
	str4 := strings.Replace("go go hello","go","go语言",-1)
	fmt.Println(str4) // 返回值:"go语言 go语言 hello"

	// 字符串指定分隔符,得到一个数组
	fmt.Println(strings.Split("hello,world",",")) // 返回值:[hello world]

	// 字母大小写转换
	fmt.Println(strings.ToLower("Go")) // 返回值:gp
	fmt.Println(strings.ToUpper("Go")) // 返回值:GO

	// 字符串去掉两边的空格
	fmt.Println(strings.TrimSpace(" is shanghai ")) // 返回值:"is shanghai"

	// 字符串去掉左右两边指定的字符串
	fmt.Println(strings.Trim("! hello !","!")) // 返回值: " hello "

	// 去掉字符串左边指定的字符
	fmt.Println(strings.TrimLeft("to shanghai","to")) // 返回值: " shanghai"

	// 去掉字符串右边指定的字符
	fmt.Println(strings.TrimRight("to shanghai gogo","gogo"))  // 返回值:"to shanghai"

	// 字符串是否以指定的字符串开头
	fmt.Println(strings.HasPrefix("www.baidu.com","www"))  // 返回值:true

	// 字符串是否以指定的字符串结尾
	fmt.Println(strings.HasSuffix("www.baidu.com","com"))  // 返回值:true
}

8.2、time模块

8.2.1、常用方法

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()

	fmt.Printf("%v \n",now) // 返回值:2023-10-22 19:49:50.0430013 +0800 CST m=+0.001257301
	fmt.Println(now.Year()) // 返回值:年
	fmt.Println(now.Month()) // 返回值:月(英文月份)
	fmt.Println(int(now.Month())) // 返回值:月(数字)
	fmt.Println(now.Day()) // 返回值:日
	fmt.Println(now.Hour()) // 返回值:时
	fmt.Println(now.Minute()) // 返回值:分
	fmt.Println(now.Second()) // 返回值:秒

	// 格式化时间,方式1:
	dateStr := fmt.Sprintf("当前时间: %d/%d/%d %d:%d:%d",now.Year(),
		now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second(),
	)
	fmt.Printf("%v\n",dateStr) // 返回值:当前时间: 2023/10/22 20:2:38

	// 格式化时间,方式2:
	fmt.Println(now.Format("2006-01-02 15:04:05")) // 返回值:当前时间的"年-月-日 时:分:秒"
	fmt.Println(now.Format("2006-01-02")) // 返回值:当前时间的"年-月-日"
	fmt.Println(now.Format("15:04:05")) // 返回值:当前时间的"时:分:秒"

	fmt.Println(now.Format("2006")) // 返回值:当前时间的"年"
	fmt.Println(now.Format("01")) // 返回值:当前时间的"月"
	fmt.Println(now.Format("02")) // 返回值:当前时间的"日"
	fmt.Println(now.Format("15")) // 返回值:当前时间的"时"
	fmt.Println(now.Format("04")) // 返回值:当前时间的"分"
	fmt.Println(now.Format("05")) // 返回值:当前时间的"秒"
}

8.2.2、时间常量

    time.Microsecond:1微妙
    time.Millisecond:1毫秒
    time.Second:1秒
    time.Minute:1分钟
    time.Hour:1小时

import (
	"fmt"
	"time"
)

func main() {
	// 时间常量
	// time.Microsecond:1微妙
	// time.Millisecond:1毫秒
	// time.Second:1秒
	// time.Minute:1分钟
	// time.Hour:1小时

	// 等待1秒
	time.Sleep(time.Second)
	// 等待10秒
	time.Sleep(10 * time.Second)

	// 获取时间戳
	now := time.Now()
	fmt.Println(now.Unix())  // 返回值:1697989319
}

8.2.3、获取函数的执行时间小例子

import (
	"fmt"
	"strconv"
	"time"
)

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

func main() {
	now_time := time.Now().Unix()
	test()
	end_time := time.Now().Unix()
	res := end_time-now_time
	fmt.Println(res)  // 返回值:5
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值