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