Go语言精修(尚硅谷笔记)第六章

六、函数、包和错误处理

6.1 函数概念

不用函数的弊端

1)写法可以完成功能, 但是代码冗余

2 ) 同时不利于代码维护

概念:为完成某一功能的程序指令(语句)的集合,称为函数。

在Go中,函数分为: 自定义函数、系统函数

基本语法

//函数的基本语法
func 函数名(形参列表)(返回值列表){ // 形参名在前 形参类型在后
    执行语句..
    return 返回值列表
}
  • 形参列表:表示函数的输入
  • 函数中的语句:表示为了实现某一功能代码块
  • 函数可以有返回值,也可以没有

案例

package main
import (
	"fmt"
)

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
}

func main() {

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

6.2 包的概念

包的本质实际上就是创建不同的文件夹,来存放程序文件。

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

包的三大作用

  • 区分相同名字的函数、变量等标识符
  • 当程序文件很多时,可以很好的管理项目
  • 控制函数、变量等访问范围,即作用域

打包基本语法

package 包名

引入包的基本语法

import"包的路径"

注意事项

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

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

  • 引入方式 1 :import “包名”

  • 引入方式 2 :

    import (
    	"包名"
    	"包名"
    )
    
  • package 指令在 文件第一行,然后是 import 指令。

  • 在import 包时,路径从 $GOPATH的 src 下开始,不用带src, 编译器会自动从src下开始引入

3 ) 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public,这样才能跨包访问。比如 utils.go 的

//为了让其它包的文件使用Cal函数,需要将C大小类似其它语言的public
func Cal(n1 float64, n2 float64, operator byte) float64 {....

4 ) 在访问其它包函数,变量时,其语法是 包名.函数名, 比如这里的 main.go文件中

utils.Cal(n1, n2 , operator) 

5 ) 如果包名较长,Go支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了

package main
import (
	"fmt"
	util "go_code/chapter06/fundemo01/utils"
)

说明: 如果给包取了别名,则需要使用别名来访问该包的函数和变量

6.) 在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

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

6.3 函数的调用机制

( 1 ) 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来

( 2 ) 在每个函数对应的栈中,数据空间是独立的,不会混淆

( 3 ) 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。

6.4 return

基本语法和说明

// Go函数支持返回多个值,这一点是其他编程语言没有的
func 函数名(形参列表)(返回值类型列表){
    语句
    return 返回值列表
}

1.如果返回多个值,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略

2.如果返回值只有一个,返回值类型列表可以不写()

6.5 函数递归调用

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

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

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

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

3 ) 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)

4 ) 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁

题 1 :斐波那契数

请使用递归的方式,求出斐波那契数 1 , 1 , 2 , 3 , 5 , 8 , 13

给你一个整数n,求出它的斐波那契数是多少?

package main
import (
	"fmt"
)

/*
请使用递归的方式,求出斐波那契数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)
	}
}

func main() {
	res := fbn(3)
	//测试
	fmt.Println("res=", res)
	fmt.Println("res=", fbn(4)) // 3
	fmt.Println("res=", fbn(5)) // 5 
	fmt.Println("res=", fbn(6)) // 8 
}

6.6 函数注意事项

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

2 ) 形参列表和返回值列表的数据类型可以是值类型和引用类型。

3 ) 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat

4 ) 函数中的变量是局部的,函数外不生效

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

func main() {	
	fmt.Println("n1=", n1) //报错,这里不能使用n1
}

5 ) 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

package main
import (
	"fmt"
)

func test02(n1 int){
	n1 = n1 + 10
	fmt.Println("test02() n1=", n1)
}

func main() {	
	num := 20
	test02(num)
	fmt.Println("main() num=", num)
}

6.) 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用 。

package main
import (
	"fmt"
)

// n1 就是 *int 类型
func test03(n1 *int) {
	fmt.Printf("n1的地址 %v\n",&n1)
	*n1 = *n1 + 10
	fmt.Println("test03() n1= ", *n1) // 30
}

func main() {
	num := 20
	fmt.Printf("num的地址=%v\n", &num)
	test03(&num)
	fmt.Println("main() num= ", num) // 30
}

7 ) Go函数不支持函数重载

package main
import (
	"fmt"
)
//有两个test02不支持重载
func test02(n1 int) {	
	n1 = n1 + 10
	fmt.Println("test02() n1= ", n1)
}
//有两个test02不支持重载
func test02(n1 int , n2 int) {
	
}

func main() {

}

8 ) 在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)
}

9 ) 函数既然是一种数据类型,因此在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)
}

10 ) 为了简化数据类型定义,Go支持自定义数据类型

基本语法:type 自定义数据类型名 数据类型 // 理解: 相当于一个别名

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

//再加一个案例
//这时 myFun 就是 func(int, int) int类型
type myFunType func(int, int) int

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

func main() {
	
	// 给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)

	//看案例
	res3 := myFun2(getSum, 500, 600)
	fmt.Println("res3=", res3)
}

11 )支持对函数返回值命名

package main
import (
	"fmt"
)

//支持对函数返回值命名
func getSumAndSub(n1 int, n2 int) (sum int, sub int){
	sub = n1 - n2
	sum = n1 + n2
	return
}

func main() {

	//看案例
	a1, b1 := getSumAndSub(1, 2)
	fmt.Printf("a=%v b=%v\n", a1, b1)
}

12 ) 使用 _ 标识符,忽略返回值

13 ) Go支持可变参数

//支持0到多个参数
func sum(args...int){
}
//支持1到多个参数
func sum(n1 int,args... int) sum int{
}

说明:

  1. args是slice切片,通过args[index]可以访问到各个值
  2. 案例演示:编写一个函数sum,可以求出1到多个int的和
  3. 如果一个函数的形参列表中有可变的参数,则可变参数需要放到形参列表的最后

6.7 init()函数

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

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()")//1
	return 90
}
// init函数,通常可以在init函数中完成初始化工作
func init() {
    fmt.Println("init()")//2
}

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

2 ) init函数最主要的作用,就是完成一些初始化的工作

package utils
import "fmt"
var Age int
var Name string

//Age 和 Name 全局变量,我们需要在main.go 使用
//但是我们需要初始化Age 和 Name

//init 函数完成初始化工作
func init() {
	fmt.Println("utils 包的  init()...")
	Age = 100
	Name = "tom~"
}

3 ) 细节说明: 面试题:案例如果main.go 和 utils.go 都含有 变量定义,init函数时,执行的流程又是怎么样的呢?

在这里插入图片描述

6.8 匿名函数

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

匿名函数使用方式 1

在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。 【案例演示】

package main
import (
	"fmt"
)
func main() {
	//在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次

	//案例演示,求两个数的和, 使用匿名函数的方式完成
	res1 := func (n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)

	fmt.Println("res1=", res1)
}

匿名函数使用方式 2

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

package main
import (
	"fmt"
)

func main() {

	//将匿名函数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)
	res3 := a(90, 30)
	fmt.Println("res3=", res3)
}

全局匿名函数

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

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

6.9 **闭包

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

闭包让你可以在一个内层函数中访问到其外层函数的作用域。

可简单理解为:有权访问另一个函数作用域内变量的函数都是闭包。

package main
import (
	"fmt"
)

//累加器
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 ) 闭包的说明

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

返回的是一个匿名函数, 但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。

6.9.1使用案例

1 ) 编写一个函数 makeSuffix(suffixstring) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包

2 ) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回 文件名.jpg, 如果已经有.jpg后缀,则返回原文件名。

3 ) 要求使用闭包的方式完成

4 ) strings.HasSuffix, 该函数可以判断某个字符串是否有指定的后缀。

package main
import (
	"fmt"
	"strings"
)

//
// 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 {
		//如果 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(suffixstring) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用到suffix这个变量

2 ) 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。(以面向对象思想理解闭包----------外部整体 像一个类,先传入的.jpg 像设置类里的一个public属性,再向返回函数传参 像调用类的成员函数,此时成员函数可以调用类里已设置的属性。)

6.9.2 闭包经典使用场景

1、return一个内部函数,读取内部函数的变量;

2、函数作为参数

3、IIFE(自执行函数)

5、使用回调函数就是在使用闭包

6、将外部函数创建的变量值始终保持在内存中;(会出现内存泄漏)

6.9.3 使用闭包注意点

因为使用闭包会包含其他函数的作用域,会比其他函数占据更多的内存空间,不会在调用结束之后被垃圾回收机制(简称GC机制)回收,多度使用闭包会过度占用内存,造成内存泄漏。

6.9.4 闭包相关面试题

1、简述什么是闭包,闭包的作用是什么?写出一个简单的闭包例子。

2、闭包会造成内存泄漏吗?

会,因为使用闭包会包含其他函数的作用域,会比其他函数占据更多的内存空间,不会在调用结束之后被垃圾回收机制回收,多度使用闭包会过度占用内存,造成内存泄漏。

3、for循环和闭包(必刷题)

6.10 defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。看下模拟代码。

package main
import (
	"fmt"
)

func sum(n1 int, n2 int) int {
	
	//当执行到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

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

}

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

执行后,输出的结果:

ok3 res= 30
ok2 n2= 20
ok1 n1 = 10
res= 30

注意事项

1 ) 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer 后的语句压入到一个栈中[我为了讲课方便,暂时称该栈为defer栈],然后继续执行函数下一个语句。

2 ) 当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制),所以同学们看到前面案例输出的顺序。

3 ) 在defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。

defer使用

1 ) 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源), 可以执行 defer file.Close() defer connect.Close()

2 ) 在defer后,可以继续使用创建资源.

3 ) 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.

4 ) 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。

6.11 函数传递方式

传递方式分类

1 ) 值传递

2 ) 引用传递

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

值类型和引用类型

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

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

各自的特点

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

image-20210113192416699

2 ) 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,改地址对应的数据空间就成为一个垃圾,由GC来回收。

image-20210113192704640

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

6.12 变量作用域

1 ) 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部

2 ) 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效

3 ) 如果变量是在一个代码块,比如 for/if中,那么这个变量的的作用域就在该代码块

6.13 字符串常用系统函数

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

2 ) 字符串遍历,同时处理有中文的问题 r:=[]rune(str)

3 ) 字符串转整数: n,err:=strconv.Atoi(" 12 ")

4 ) 整数转字符串 str=strconv.Itoa( 12345 )

5 ) 字符串 转 []byte: varbytes=[]byte(“hello go”)

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

7 ) 10 进制转 2 , 8 , 16.进制: str=strconv.FormatInt( 123 , 2 )// 2 - > 8 , 16

8 ) 查找子串是否在指定的字符串中:strings.Contains(“seafood”,“foo”)//true

9 ) 统计一个字符串有几个指定的子串 : strings.Count(“ceheese”,“e”)// 4

10 ) 不区分大小写的字符串比较(==是区分字母大小写的):fmt.Println(strings.EqualFold(“abc”,“Abc”))//true

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

12 ) 返回子串在字符串最后一次出现的index,如没有返回- 1 :strings.LastIndex(“gogolang”,“go”)

13 ) 将指定的子串替换成 另外一个子串:strings.Replace(“gogohello”,“go”,“go语言”,n)n可以指定你希望替换几个,如果n=- 1 表示全部替换

14 ) 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split(“hello,wrold,ok”,“,”)

15 ) 将字符串的字母进行大小写的转换:strings.ToLower(“Go”)//gostrings.ToUpper(“Go”)//GO

16.) 将字符串左右两边的空格去掉: strings.TrimSpace("tnalonegopherntrn ")

17 ) 将字符串左右两边指定的字符去掉 : strings.Trim(“!hello!”,“!”) //[“hello”]//将左右两边! 和 ""去掉

18 ) 将字符串左边指定的字符去掉 : strings.TrimLeft(“!hello!”,“!”) //[“hello”]//将左边! 和 " "去掉

19 ) 将字符串右边指定的字符去掉 :strings.TrimRight(“!hello!”,“!”) //[“hello”]//将右边! 和 " "去掉

20 ) 判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp:// 192. 168. 10. 1 ",“ftp”)//true

21 ) 判断字符串是否以指定的字符串结束:strings.HasSuffix(“NLT_abc.jpg”,“abc”)//false

6.14 时间和日期相关函数

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

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

package main

import (
	"fmt"
	"time"
)

func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)
}

3 ) 如何获取到部分的日期信息

package main
import (
	"fmt"
	"time"
)
func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)

	//2.通过now可以获取到年月日,时分秒
	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())
}

4 ) 格式化日期时间

方式 1 : 就是使用Printf 或者 SPrintf

package main
import (
	"fmt"
	"time"
)

func main() {
	//格式化日期时间
	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)
}

方式二: 使用 time.Format() 方法完成:

package main
import (
	"fmt"
	"time"
)

func main() {
	//格式化日期时间的第二种方式
	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()
}

5 ) 时间的常量

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

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

100 *time.Millisecond

7 ) time的Unix和UnixNano的方法

image-20210113223340801

编写一段代码来统计 函数test 03 执行的时间

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()
	end := time.Now().Unix()
	fmt.Printf("执行test03()耗费时间为%v秒\n", end-start)
}

6.15 系统函数

1 ) len:用来求长度,比如string、array、slice、map、channel

2 ) new:用来分配内存,主要用来分配值类型,比如int、float 32 ,struct返回的是指针

package main
import (
	"fmt"
)

func main() {

	num1 := 100
	fmt.Printf("num1的类型%T , num1的值=%v , num1的地址%v\n", num1, num1, &num1)

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

上面代码对应的内存分析图:

image-20210113223826843

3 ) make:用来分配内存,主要用来分配引用类型,比如channel、map、slice。

6.16 错误处理

1 ) 在默认情况下,当发生错误后(panic),程序就会退出(崩溃.)

2 ) 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示(邮件,短信。。。)

基本说明

1 ) Go语言追求简洁优雅,所以,Go语言不支持传统的 trycatchfinally 这种处理。

2 ) Go中引入的处理方式为: defer , panic , recover

3 ) 这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

package main
import (
	"fmt"
	"time"
)

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()
	for {
		fmt.Println("main()下面的代码...")
		time.Sleep(time.Second)
	}
}

自定义错误

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

1 ) errors.New(“错误说明”), 会返回一个error类型的值,表示一个错误

2 ) panic内置函数 ,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序.

package main
import (
	"fmt"
	_ "time"
	"errors"
)
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
	if name == "config.ini" {
		//读取...
		return nil
	} else {
		//返回一个自定义错误
		return errors.New("读取文件错误..")
	}
}

func test02() {

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

func main() {
	//测试自定义错误的使用
	test02()
	fmt.Println("main()下面的代码...")
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值