《Go语言从入门到进阶实战》学习笔记:第五章 函数

(1)函数本身可以作为值进行传递。

(2)支持匿名函数和闭包(closure)。

(3)函数可以满足接口。

5.1函数声明

1、普通函数声明形式

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

      函数体

}

2、参数类型简写

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

3、函数的返回值

(1)带有变量名的返回值

func named_Ret_Values()(a,b int){
    a=1
    b=2
    return
}

可以在函数体中直接对函数返回值进行赋值。在命名的返回值方式的函数体中,在函数结束前需要显示地使用return语句进行返回。(同一种类型返回值和命名返回值两种形式只能二选一,不能混用)

4、调用函数

result:=add(1,2)

5、示例:将“秒”解析为时间单位

package main

import "fmt"

const(
	//定义每分钟的秒数
	seconds_Per_Minute=60
	//定义每小时的秒数
	seconds_Per_Hour=seconds_Per_Minute*60
	//定义每天的秒数
	seconds_Per_Day=seconds_Per_Hour*24
)

//将传入的秒解析为三种时间单位
func resolve_Time(seconds int)(day int,hour int,minutes int)  {
	day=seconds/seconds_Per_Day
	hour=seconds/seconds_Per_Hour
	minutes=seconds/seconds_Per_Minute
	return
}

func main(){
	//将返回值作为打印参数
	fmt.Println(resolve_Time(1000))

	//只获得小时和分钟
	_,hours,minutes:=resolve_Time(18000)
	fmt.Println(hours,minutes)
	day,_,_:=resolve_Time(90000)
	fmt.Println(day)
}

5.2函数变量--把函数作为值保存到变量中

在Go语言中,函数也是一种类型,可以和其他类型一样被保存在变量中。

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

func main(){
    //将变量f声明为func()类型,f就是回调函数
    var f func()
    f = fire
    //和调用fire()结果相同
    f()
}

5.3示例:字符串的链式处理--操作与数据分离的设计技巧

package main

import (
	"fmt"
	"strings"
)

//字符串处理函数,传入字符串切片和处理链
func String_Process(list []string,chain []func(string)string)  {
	//遍历每个字符串
	for index,str := range list{
		//第一个需要处理的字符串
		result:=str
		//遍历每一个处理链
		for _,proc:=range chain{
			//输入一个字符串进行处理,返回数据作为下一个处理链的输入
			result=proc(result)

		}
		//将结果放回切片
		list[index]=result
	}
}
//自定义的移除前缀的处理函数
func remove_Prefix(str string)string  {
	return strings.TrimPrefix(str,"go")
}

//字符串处理主流程
func main()  {
	//待处理的字符串列表
	list := []string{
		"go scanner",
		"go parser",
		"go compiler",
		"go printer",
		"go formater",
	}
	//处理函数链
	chain:=[]func(string)string{
		remove_Prefix,
		strings.TrimSpace,
		strings.ToUpper,
	}
	//处理字符串
	String_Process(list,chain)
	//输出处理好的字符串
	for _,str:=range list{
		fmt.Println(str)
	}
}

5.4匿名函数--没有函数名字的函数

1、定义一个匿名函数

func (参数列表)(返回参数列表){

    函数体

}

(1)在定义时调用匿名函数

//最后的(100)表示对匿名函数的调用,传递参数为100
func (data int){
    fmt.Println("hello",data)
}(100)

(2)将匿名函数赋值给变量

//将匿名函数体保存到f()中
f:=func(data int){
    fmt.Println("hello",dat)
}

//使用f()调用
f(100)

2、匿名函数作为回调函数

package main

import "fmt"

//遍历切片的每一个元素,通过给定函数进行元素访问
func visit(list []int,f func(int))  {
	for _,v:=range list{
		f(v)
	}
}

func main()  {
	//使用匿名函数打印切片内容
	visit([]int{1,2,3,4}, func(v int) {
		fmt.Println(v)
	})
}

3、使用匿名函数实现操作封装

package main

import "fmt"

//遍历切片的每一个元素,通过给定函数进行元素访问
func visit(list []int,f func(int))  {
	for _,v:=range list{
		f(v)
	}
}

func main()  {
	//使用匿名函数打印切片内容
	visit([]int{1,2,3,4}, func(v int) {
		fmt.Println(v)
	})
}

5.5函数类型实现接口--把函数作为接口来调用

还没看明白。

5.6闭包(Closure)--引用了外部变量的匿名函数

函数+引用环境=闭包

1、在闭包内部修改引用变量

//准备一个字符串
str:="hello world"

//创建一个匿名函数
foo:=func(){
    //匿名函数中访问str
    str="hello dude"
}

//调用匿名函数
foo()

结果:
hello dude

2、示例:闭包的记忆效应

package main

import "fmt"

//提供一个值,每次调用函数会指定对值进行累加
func Accumulate(value int)func()int  {
	//返回一个闭包
	return func() int {
		//累加
		value++
		//返回一个累加值
		return value
	}
}

func main()  {
	//创建一个累加器,初始值为1
	accumulate:=Accumulate(1)
	//累加1并打印
	fmt.Println(accumulate())
	fmt.Println(accumulate())
	//打印累加器的函数地址
	fmt.Printf("%p\n",accumulate)
	//创建一个累加器,初始值为10
	accumulate2:=Accumulate(10)
	//累加10并打印
	fmt.Println(accumulate2())

	//打印累加器的函数地址
	fmt.Printf("%p\n",accumulate2)
}

3、闭包实现生成器

package main

import "fmt"

//创建一个玩家生成器,输入名称,输出生成器
func player_Gen(name string)func()(string,int)  {
	//血量一直为150
	hp:=150

	//返回创建的闭包
	return func() (string, int) {
		//将变量引用到闭包中
		return name,hp
	}
}

func main()  {
	//创建一个玩家生成器
	generator:=player_Gen("high_noon")
	//返回玩家的名字和血量
	name,hp:=generator()
	//打印值
	fmt.Println(name,hp)
}

5.7可变参数--参数数量不固定的函数形式

格式

func函数名(固定参数列表,v...T)(返回参数列表){
函数体
}

v是可变参数变量,类型为[]T。

T为可变参数的类型,当T为interface{}时,传入的可以是任意类型

1、fmt包中的例子

fmt.Println()函数声明

func Println(a ...interface())(n int,err error){
    return Fprintln(os.Stdout,a...)
}

fmt.Printf()函数声明

func Printf(format string,a ...interface{})(n int,err error){
    return Fprintf(os.Stdout,format,a...)
}

2、遍历可变参数表--获取每一个参数的值

package main

import (
	"bytes"
	"fmt"
)

//定义一个函数,参数数量为0~n,类型约束为字符串
func join_String(slist ...string)string  {
	//定义一个字节缓冲区,快速地连接字符串
	var b bytes.Buffer
	//遍历可变参数列表slist,类型为[]string
	for _,s := range slist{
		b.WriteString(s)
	}
	//将连接好的字节数组转换为字符串
	return b.String()
}

func main()  {
	//输入3个字符串,将它们连成一个字符串
	fmt.Println(join_String("pig","and","rat"))
	fmt.Println(join_String("hammer","mom","and","hawk"))
}

3、获取可变参数类型--获取每一个参数的类型

package main

import (
	"bytes"
	"fmt"
)

func print_Type_Values(slist ...interface{})string  {
	//字节缓冲区作为快速字符串连接
	var b bytes.Buffer
	//遍历参数
	for _,s:=range slist{
		//将interface{}类型格式化为字符串
		str:=fmt.Sprintf("%v",s)
		//类型的字符串描述
		var type_String string
		//对s进行类型断言
		switch s.(type) {
		case bool://当s为布尔类型
			type_String="bool"
		case string:
			type_String="string"
		case int:
			type_String="int"
		}
		//写值字符串前缀
		b.WriteString("values:")
		//写入值
		b.WriteString(str)
		//写入类型前缀
		b.WriteString("type: ")
		//写入类型
		b.WriteString(type_String)
		//写入换行符
		b.WriteString("\n")

	}
	return b.String()
}

func main()  {
	//将不同类型的变量通过print_Type_Value()打印出来
	fmt.Println(print_Type_Values(100,"str",true))
}

4、在多个可变参数函数中传递参数

package main

import "fmt"

//实际打印的参数
func raw_Print(raw_List ...interface{})  {
	//遍历可变参数切片
	for _,a:=range raw_List {
		//打印参数
		fmt.Println(a)
	}
}

//打印函数封装
func print(slist ...interface{})  {
	//将slist可变参数切片完整传递给下一个函数
	raw_Print(slist...)
}

func main()  {
	print(1,2,3)
}

5.8延迟执行语句(defer)

先被defer的语句最后被执行,最后被defer的语句,最先被执行。

1、多个延迟执行语句的处理顺序

package main

import "fmt"

func main()  {
	fmt.Println("defer begin")
	//将defer放入延迟调用栈
	defer fmt.Println(1)
	defer fmt.Println(2)
	//最后一个放入,位于栈顶,最先调用
	defer fmt.Println(3)
	fmt.Println("defer end")
}

结果:
defer begin
defer end
3
2
1

延迟调用是在defer所在的函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时。

2、使用延迟执行语句在函数退出时释放资源

defer是在函数结束时执行的,处理资源释放能非常方便。

(1)使用延迟并发解锁

//使用两种方法读取数据
var(
	//一个演示用的映射
	value_By_Key=make(map[sting]int)
	//保证使用映射时的并发安全的互斥锁
	value_By_Key_Guard sync.Mutex
)

//根据键读取值
func read_Value(key string) int {
	//对共享资源加锁
	value_By_Key_Guard.Lock()
	//取值
	v:=value_By_Key[key]
	//对共享资源解锁
	value_By_Key_Guard.Unlock()
	return v
}

func read_Value2(key string) int {
	value_By_Key_Guard.Lock()
	//defer后面的语句不会马上调用,而是延迟到函数结束时调用
	defer value_By_Key_Guard.Unlock()
	return value_By_Key[key]
}

(2)使用延迟释放文件句柄

//对比两种方法,发现defer更简单方便
//根据文件名查询其大小
func file_Size(filename string) int64 {
	//根据文件名打开文件,返回文件句柄和错误
	f,err:=os.Open(filename)
	//如果打开时错误,返回文件大小为0
	if err!=nil{
		return 0
	}
	//获取文件状态信息
	info,err:=f.Stat()
	//如果获取信息时发生错误,关闭文件并返回文件大小为0
	if err!=nil{
		f.Close()
		return 0
	}
	//取文件大小
	size:=info.Size()
	//关闭文件
	f.Close()
	return size
}

func file_Size2(filename string) int64 {
	f,err:=os.Open(filename)
	if err!=nil{
		return 0
	}
	//延迟调用Close(),此时Close不会被调用
	defer f.Close()
	info,err:=f.Stat()
	if err!=nil{
		//defer机制触发,调用Close关闭文件
		return 0
	}
	size:=info.Size()
	//defer机制触发,调用Close关闭文件
	return size
}

5.9处理运行时发生的错误

Go语言的错误处理思想及设计包含以下特征:

(1)一个可能造成错误的函数,需要返回值中返回一个错误接口(error)。如果调用成功,错误接口返回nil,否则返回错误。

(2)在函数调用后需要检查错误,如果发生错误,进行必要的错误处理。

1、除数为0的例子

package main

import (
	"errors"
	"fmt"
)

//定义除数为0的错误
var err_Division_By_Zero = errors.New("division by zero")

func div(dividend,divisor int)(int,error)  {
	//判断除数为0的情况并返回
	if divisor==0{
		return 0,err_Division_By_Zero
	}
	//正常计算,返回空错误
	return dividend/divisor,nil
}

func main()  {
	fmt.Println(div(1,0))
}

在解析中使用自定义错误

package main

import "fmt"

//声明一个解析错误
type Parse_Error struct {
	Filename string //文件名
	Line int   //行号
}
//实现error接口,返回错误描述
func (e *Parse_Error)Error()string  {
	return fmt.Sprintf("%s:%d",e.Filename,e.Line)
}

//创建一些解析错误
func new_Parse_Error(filename string,line int)error  {
	return &Parse_Error{filename,line}
}

func main()  {
	var e error
	//创建一个错误实例,包含文件名和行号
	e=new_Parse_Error("main.go",1)
	//通过error接口查看错误描述
	fmt.Println(e.Error())
	//根据错误接口的具体类型,获取详细的错误信息
	switch detail:=e.(type) {
	case *Parse_Error:
		//这是一个解析错误
		fmt.Printf("Filename:%s Line:%d\n",detail.Filename,detail.Line)
	default:
		//其他类型的错误
		fmt.Println("other error")
	}
}

猜你喜欢

转载自blog.csdn.net/qq_36214481/article/details/88760767