Go语言核心编程---06函数、包和错误处理

本文深入探讨Go语言的函数、包和错误处理机制。讲解了函数的基本语法、包的概念和作用、错误处理的defer、panic、recover机制,以及字符串和时间日期的系统函数。同时,介绍了如何利用闭包实现特定功能,强调了Go语言中变量作用域、参数传递方式和包管理的重要性。
摘要由CSDN通过智能技术生成

6.1 函数

6.1.1 函数的基本语法

func 函数名 (形参列表) (返回值列表){

    执行语句...

    return 返回值列表

}

  • 形参列表:表示函数的输入

  • 函数中的语句:表示为了实现某一功能代码块

  • 函数可以有返回值,也可以没有

6.1.2 函数使用的注意实现和细节讨论
  • 函数的形参列表可以是多个,返回值列表也可以是多个。

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

  • 函数的命名遵循标识符命名规范,首字母不能是数字。首字母大写该函数可以被本包文件和其他包文件使用;首字母小写只能被本包文件使用,其它包不能使用。

  • 函数中的变量是局部的,函数外不生效。

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

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

  • Go函数不支持函数重载。

  • 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量对函数调用。 因此,函数也可以作为形参并且调用。

  • 为了简化数据类型定义,Go支持自定义数据类型。type 自定义数据类型名 数据类型

  • 支持对函数返回值命名,如:a, b = myFunction(c,d),a、b就是对返回值命名。

  • 使用"_"标识符,忽略返回值。

  • Go支持可变参数,可变参数需要放在形参列表最后。


//args是slice切片,通过args[index]可以访问到各个值

//支持0到多个参数

func sum(args... int) sum int {

}

//支持1到多个参数

func sum(n1 int, args... int) sum int {

}

//例子

func sum(n1 int, args... int) int {

    sum := n1

    for i := 0; i < len(args); i++ {

        sum += args[i]

    }

    return sum

}

res := sum(10, -1, 2, 90, 0) // 10是n1,-1开始是args

fmt.Println("res=", res)

6.1.3 init函数

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

  • 注意事项

    • 如果一个文件同时包含全局变量定义、init函数和main函数,则执行的流程:全局变量定义—init函数—main函数

    • 如果main包(全局变量定义、init函数、main函数)引用了go包(全局变量定义、init函数),且执行的流程:go全局变量定义—go init函数—main全局变量定义—main init函数—main main函数

6.1.4 匿名函数

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

  • 使用方式1(只能调用一次):

res := func (n1 int, n2 int) int {

    return n1 + n2

} ( 10, 20)

fmt.Println("res = ", res) //30

  • 使用方式2(将匿名函数赋给一个变量,再通过这个变量来调用匿名函数):

a := func (n1 int, n2 int) int {

    return n1 - n2

}

res = a(10, 30)

fmt.Println("res = ", res) //-20

  • 全局匿名函数

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

6.1.5 函数的defer

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


package main

import "fmt"

func sum(n1 int, n2 int) int {
   //当执行到defer时,会将defer后面的语句压入到独立的栈,暂时不执行
   //当函数执行完毕后,再从栈中按照先进后出的方式执行(即栈的特性)
   defer fmt.Println("ok1 n1 = ", n1) //执行3
   defer fmt.Println("ok2 n2 = ", n2) //执行2
   res := n1 + n2
   fmt.Println("ok3 res = ", res) //执行1
   return res
}

func main() {
   res := sum(10, 20)
   fmt.Println("res = ", res) //执行4
}
  • defer的注意事项和细节:

    • 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中,然后继续执行函数下一语句。
  • 当函数执行完毕后,再从defer中依次从栈顶取出语句执行(遵循栈先进后出的原则)。

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


package main

import "fmt"

func sum(n1 int, n2 int) int {
   //当执行到defer时,会将defer后面的语句压入到独立的栈,暂时不执行
   //当函数执行完毕后,再从栈中按照先进后出的方式执行(即栈的特性)
   defer fmt.Println("ok1 n1 = ", n1) //执行3
   defer fmt.Println("ok2 n2 = ", n2) //执行2
   n1++ //这句话不会影响栈中存放的n1值,仍然为10
   n2++ //同理,栈中的n2仍然为20
   res := n1 + n2
   fmt.Println("ok3 res = ", res) //执行1
   return res
}

func main() {
   res := sum(10, 20)
   fmt.Println("res = ", res) //执行4
}
  • defer的最佳实践:defer最重要的价值在于当函数执行完毕后,可以及时的释放函数创建的资源。

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

    • 在defer后,可以继续使用创建资源

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


//模拟代码如下

func test(){

    file = openfile(文件名)

    defer file.close()

    //其他代码

    

    connect = openDatabase()

    defer connect.close()

    //其他代码

}

6.1.6 函数参数传递方式
  • 两种传递方式:值传递和引用传递。

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

  • 值类型和引用类型

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

    • 引用类型:指针、slice切片、map、管道chan、interface等

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

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

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

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

6.1.7 变量作用域
  • 函数内部声明/定义的变量叫做局部变量,作用域仅限于函数内部。

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

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


6.2 包

6.2.1 包的基本概念

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

6.2.2 包的三大作用
  • 区分相同名字的函数、变量等标识符

  • 当程序文件很多时,可以很好地管理项目

  • 控制函数、变量等访问范围,即作用域

6.2.3 包的相关说明
  • 打包基本语法:package 包名

  • 引入包的基本语法:import "包的路径"

  • 如果要引用别的包的变量、函数,注意变量名/函数名首字母要大写,相等于public的作用

6.2.4 包使用的注意事项和细节讨论
  • 在给一个文件打包时,该包对应一个文件夹,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

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

    • 在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入。
  • 为了让其它包的文件可以访问到本包的函数,则该函数名的首字母需要大写

  • 在访问其它包函数、变量时,其语法是包名.函数名

  • 如果包名比较长,Go支持给包取别名,取别名后,原来的包名就不能用了,只能用别名来访问该包的函数和变量,如:util ".../.../.../utls",util就是别名,后面是原来的包名。

  • 在同一包下,不能有相同的函数名/全局变量名,否则报重复定义。

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

6.2.5 闭包

闭包就是一个函数和其相关的引用环境组合的一个整体。


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

}

  • 上述的代码说明:

    • AddUpper是一个函数,返回的数据类型是 fun (int) int

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

    • 可以理解为:闭包是类,函数是操作,n是字段。函数和它使用的n构成闭包。

    • 当我们反复调用f函数时,因为n是初始化一次,因此每调用一次就进行累加。

    • 闭包的关键:分析出返回的函数它使用(引用)到那些变量,因为函数和它引用到的变量共同构成闭包。

请编写一个程序,具体要求如下:
(1)编写一个函数makeSuffix(suffix string)可以接受一个文件后缀名(比如.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 main() {

   //测试makeSuffic使用
   //返回一个闭包
   f := makeSuffix(".jpg")
   fmt.Println("文件名winter处理后 = ", f("winter")) //winter.jpg
   fmt.Println("文件名bird.jpg处理后 = ", f("bird.jpg")) //bird.jpg
}
  • 上述代码的说明:
    • 返回的匿名函数和makeSuffic(suffix string)的suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量
    • 闭包的好处:如果使用传统的方法也可以实现这个功能,但是每次都需要传入后缀名(如.jpg),而闭包可以保留上次引用的某个值,所以传入一次就可以反复引用。

6.3 字符串常用的系统函数

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

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

//字符串遍历,同时处理由中文的问题:r := []rune(str)
str2 := "hello北京"
r := []rune(str2)
for i := 0; i < len(r); i++ {
   fmt.Printf("字符 = %c\n", r[i])
}
  • 字符串转整数: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)
}
  • 整数转字符串:str = strconv.Itoa(12345)

//整数转字符串:str = strconv.Itoa(12345)
str = strconv.Itoa(12345)
fmt.Printf("str = %v, str = %T", str, str) //str = 12345, str = string
  • 字符串转[]byte:```var bytes = []byte(“hello go”)

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

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

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

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

  • 不区分大小写的字符串比较(==是区分字母大小写的):strings.EqualFold("abc", "ABC") //true

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

  • 返回子串在字符串最后一次出现的index,如果没有返回-1:strings.LastIndex("go golang", "go") //3

  • 将指定的子串替换成另外一个子串:strings.Replace("go go hello", "go" ,"go语言", n) //n可以指定你希望替换几个,如果n=-1那么全部替换

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

  • 将字符串的字母进行大小写的转换:strings.ToLower("Go") //go strings.ToUpper("Go") //GO

  • 将字符串左右两边的空格去掉:strings.TrimSpace(" tn a lone gopher ntn ")

  • 将字符串左右两边指定的字符去掉:strings.Trim("!?hello?", "?!") //将左右两边的?和!去掉

  • 将字符串左边指定的字符去掉:strings.TrimLeft("!?hello?", "?!") //将左边的?和!去掉

  • 将字符串右边指定的字符去掉:strings.TrimRight("!?hello?", "?!") //将右边的?和!去掉

  • 判断字符串是否以指定的字符串开头:strings.HasPrefix("ftp://192.168.1.1", "ftp") //true

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


6.4 时间和日期相关函数

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

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

  • 查看日期信息


//1.获取当前时间
now := time.Now()
fmt.Println("当前时间为:", now) //当前时间为: 2020-05-12 11:17:16.561951 +0800 CST m=+0.000098979

//2.通过now可以欧取到年月日时分秒
fmt.Printf("年 = %v\n", now.Year()) //年 = 2020
fmt.Printf("月 = %v\n", now.Month()) //月 = May,可以用int()转为数字5
fmt.Printf("日 = %v\n", now.Day()) //日 = 12
fmt.Printf("时 = %v\n", now.Hour()) //时 = 11
fmt.Printf("分 = %v\n", now.Minute()) //分 = 20
fmt.Printf("秒 = %v\n", now.Second()) //秒 = 1
  • 格式化日期时间

    • 使用Printf或者Springtf

    • 使用time.Format()方法:2006/01/02 15:04:05这几个数字不能修改,但是/可以改成-或者其他,各个数字也可以自由组合。


//3.格式化日期时间:方式1
fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d", now.Year(), now.Month(), now.Day(),
   now.Hour(), now.Minute(), now.Second()) //当前年月日 2020-5-12 11:23:15
dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d", now.Year(), now.Month(), now.Day(),
   now.Hour(), now.Minute(), now.Second())
fmt.Printf("dateStr = %v\n", dateStr) //dateStr = 当前年月日 2020-5-12 11:24:31

//方式2:数字不能修改,这是固定的格式,但是/可以改成-或者空格
fmt.Printf(now.Format("2006/01/02 15:04:05")) //2020/05/12 11:26:44
fmt.Println()
fmt.Printf(now.Format("2006/01/02")) //2020/05/12
fmt.Println()
fmt.Printf(now.Format("15:04:05")) //11:26:44
fmt.Println()
  • 时间的常量
    在这里插入图片描述
    常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到100毫秒:100*time.Millisecond,不能用除法来表示。
  • time的Unix和UnixNano方法
    都是从January 1, 1970 UTC到时间点t所经过的时间,Unix单位是秒,UnixNano的单位是纳秒。

6.5 内置函数

  • len:用来求长度,如:string、array、slice、map、channel。
  • new:用来分配内存,主要用来分配值类型,比如int、float32、struct…返回的是指针。
  • make:用来分配内存,主要用来分配应用类型,比如channel、map、slice。

6.6 错误处理

  • 基本说明
    • Go语言追求简洁优雅,所以不支持传统的try-catch-finally这种方式。
    • Go中引入的处理方式是:defer、panic、recover
    • 这几个异常的使用场景可以简单的描述为:Go中可以抛出一个panic异常,然后在defer中通过recover捕获这个异常,然后正常处理。
  • 使用defer+recover来处理错误
package main

import "fmt"

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()下面的代码...")
}
  • 错误处理的好处

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

  • 自定义错误

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

  • errors.New("错误说明"),会返回一个error类型的值,表示一个错误。

  • panic内置函数,接收一个interface{}类型的值作为参数,可以接收error类型的变量,输出错误信息,并退出程序


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

func test02() {
   err := readConf("config.ini")
   if err != nil {
      //读取文件错误,输出这个错误,并终止程序
      panic(err) 
   }
   fmt.Println("test02()后面的代码...")
}

func main() {

   //测试
   test02()
   fmt.Println("main()下面的代码...")
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值