Go笔记-函数
Go笔记-函数
漂泊尘埃 发表于10个月前
Go笔记-函数
  • 发表于 10个月前
  • 阅读 0
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

函数

在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

Go不支持函数重载,也不支持泛型,因为这会影响性能。

不支持嵌套(nested)、重载(overload)、默认参数(default paremeter)。

函数定义

package main

import (
	"errors"
	"fmt"
)

func main() {
	ret, err := Add(1, 1)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("ret=", ret)
	}
}

func Add(a int, b int) (ret int, err error) {
	if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
		err = errors.New("Should be non-negative numbers!")
		return
	}
	return a + b, nil
}

返回值列表也要用()括起来


返回值是被初始化成零值的:

func test() (a, b int) {
	b = 1
	return
}
调用:
a, b := test()
fmt.Println(a, b) // 0 1

返回值列表可以不加名称,这样每次return都必须返回所有的值,而且nil不能用作int这样的类型

func Add(a, b int) (int, error) {
	if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
		return -1, errors.New("Should be non-negative numbers!")
	}
	return a + b, nil
}

如果参数列表中若干个相邻的参数类型的相同,比如上面例子中的a和b,则可以在参数列表中省略前面变量的类型声明

func Add(a, b int) (ret int, err error)

返回值列表也同样


如果只有一个返回值,可以这样

func Add(a, b int) int{ 
	// ... 
} 

命名规范

小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。

这个规则也适用于类型和变量的可见性

不定参数

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

func myfunc(args ...int) {
	for _, v := range args {
		fmt.Printf("%v\t", v)
	}
}
  • 形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会
  • 从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type

不定参数的传递

funcmyfunc(args ...int) { 
	// 按原样传递
	myfunc3(args...) 
	// 传递片段,实际上任意的int slice都可以传进去
	myfunc3(args[1:]...) 
}

任意类型的不定参数

下面是Go语言标准库中fmt.Printf()的函数原型:

funcPrintf(format string, args ...interface{}) { 
 // ... 
}
func main() {
	var v1 int = 1
	var v2 int64 = 234
	var v3 string = "hello"
	var v4 float32 = 1.234
	myfunc(v1, v2, v3, v4)
}

func myfunc(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "is an int value.")
		case string:
			fmt.Println(arg, "is a string value.")
		case int64:
			fmt.Println(arg, "is an int64 value.")
		default:
			fmt.Println(arg, "is an unknown type.")
		}
	}
}

输出结果:

1 is an int value.
234 is an int64 value.
hello is a string value.
1.234 is an unknown type.

如果是传递数组:

func main() {
	values := []string{"a", "b"}
	DO("command", values...)

}

func DO(commandName string, args ...interface{}) {
}

则会报错:

cannot use values (type []string) as type []interface {} in argument to DO。

var intf []interface{}
var strs []string = []string{"a", "b"}
intf = strs // 这个也是会报错的。

匿名函数

在Go里面,函数可以像普通变量一样被传递或使用

赋值给变量

f := func(a, b int) bool {
	return a > b
}
fmt.Println(f(1, 3)) // false

另一种方式是:(var f (func(a, b int) bool)括号里是类型)

var f (func(a, b int) bool) = func(a, b int) bool {
	return a > b
}

直接执行

func(a, b int) {
	fmt.Println(a > b)
}(1, 3)

闭包

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么 被闭包引用的变量会一直存在

	var j int = 5
	f := func() func() { // 返回一个函数
		var i int = 10
		return func() {
			fmt.Printf("i,j:%d,%d\n", i, j)
		}
	}() // 执行匿名函数,这样f引用的就是return的func

	f() // i,j:10,5

	j *= 2

	f() // i,j:10,10

变量a指向的闭包函数引用了局部变量i和j,i的值被隔离,在闭包外不能被修改,改变j的值以后,再次调用a,发现结果是修改过的值。

在变量a指向的闭包函数中,只有内部的匿名函数才能访问变量i,而无法通过其他途径访问到,因此保证了i的安全性。

strings.Title()中使用到了闭包。

另一个例子:

func main() {
	//stringsStudy.StudyCount()
	var f = Adder()
	fmt.Print(f(1), " - ")
	fmt.Print(f(20), " - ")
	fmt.Print(f(300))
}
func Adder() func(int) int {
	var x int
	return func(delta int) int {
		x += delta
		return x
	}
}

输出:1 - 21 - 321

闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量。

这些局部变量同样可以是参数,例如 Adder(as int)中的as。

工厂函数

一个返回值为另一个函数的函数可以被称之为工厂函数,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。下面的函数演示了如何动态返回追加后缀的函数:

func MakeAddSuffix(suffix string) func(string) string {
    return func(name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }
        return name
    }
}

现在,我们可以生成如下函数:

addBmp := MakeAddSuffix(“.bmp”)
addJpeg := MakeAddSuffix(“.jpeg”)

然后调用它们:

addBmp(“file”) // returns: file.bmp
addJpeg(“file”) // returns: file.jpeg

可以返回其它函数的函数和接受其它函数作为参数的函数均被称之为高阶函数,是函数式语言的特点。

使用闭包调试

当您在分析和调试复杂的程序时,无数个函数在不同的代码文件中相互调用,如果这时候能够准确地知道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 runtime 或 log 包中的特殊函数来实现这样的功能。包 runtime 中的函数 Caller() 提供了相应的信息,因此可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:

import (
	"fmt"
	"log"
	"runtime"
)

func main() {
	where := func() {
		_, file, line, _ := runtime.Caller(1)
		log.Printf("%s:%d", file, line)
	}
	where()
	doSomeThing()
	where()
	doSomeThing()
	where()
}

func doSomeThing() {
	for i := 0; i < 10; i++ {
		j := 5
		j += i
		if j > 20 {
			fmt.Println("j=", j)
		}
	}
}

输出:

2014/12/06 15:45:16 E:/Go/projects/src/study/main.go:14
2014/12/06 15:45:16 E:/Go/projects/src/study/main.go:16
2014/12/06 15:45:16 E:/Go/projects/src/study/main.go:18

这是where()的位置,Caller(1)中的1表示堆栈中第1个的信息,第0个是本函数,第1个就是上一个函数,这是是main函数中的where()位置。

传递指针

func Add(i *int) {
	*i++
}

func main() {
	i := 4
	Add(&i)
	fmt.Println(i) // 5
}

Go语言中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)

让函数直接实现接口

type Tester interface {
	Do()
}

type FuncDo func()

func (f FuncDo) Do() { f() }

func main() {
	var t Tester = FuncDo(func() {
		fmt.Println("hello world")
	})
	t.Do()
}
标签: Go 函数
共有 人打赏支持
漂泊尘埃
粉丝 4
博文 35
码字总数 70992
×
漂泊尘埃
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: