文档章节

Go笔记-函数

漂泊尘埃
 漂泊尘埃
发布于 2017/02/28 10:35
字数 1789
阅读 4
收藏 0

函数

在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笔记-web
漂泊尘埃

漂泊尘埃

粉丝 5
博文 36
码字总数 71385
作品 0
朝阳
私信 提问
兄弟连区块链教程Fabric1.0源代码分析Peer

区块链教程Fabric1.0源代码分析Peer,2018年下半年,区块链行业正逐渐褪去发展之初的浮躁、回归理性,表面上看相关人才需求与身价似乎正在回落。但事实上,正是初期泡沫的渐退,让人们更多的...

兄弟连区块链入门教程
11/05
0
0
TypeScript学习笔记之六函数(重点箭头函数)

一、使用环境 Mac 电脑 WebStorm TypeScript3.x版本 二、基本函数 2.1、函数声明定义 2.2、函数表达式定义 2.3、用接口定义函数 2.4、函数参数:可选参数 和默认值参数 三、箭头函数 3.1、基...

摸着石头过河_崖边树
12/10
0
0
Oracle笔记 目录索引

Oracle笔记 一、oracle的安装、sqlplus的使用 Oracle笔记 二、常用dba命令行 Oracle笔记 三、function 、selectOracle笔记 四、增删改、事务 Oracle笔记 五、创建表、约束、视图、索引、序列...

ibm_hoojo
2011/05/03
0
0
Mini 容器学习笔记1——环境搭建(基础篇)

一. 环境下载 到Mini 容器的官方网站下载NLite框架的二进制文件,下载并解压后就可以了。 我们使用NLite框架需要用到下面的文件: NLite.dll(必要) 二. 建立NLite应用程序 新建一个控制台应用...

netcasewqs
2011/08/26
0
0
machine learning博客索引

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/robinXushuai/article/details/80711026 本系列为台大林轩田老师《机器学习基石》和《机器学习技法》课程的部...

_席达_
06/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

docker搞个wordpress

1.先把wordpress的镜像下载下来 docker pull wordpress 2.下载mysql docker pull mysql:lastest 3.启动mysql docker run --name blog -e root -d mysql:5.7 docker run --name some-mysql -e......

无极之岚
7分钟前
0
0
【宇润日常疯测-005】PHP 中的 clone 和 new 性能比较

clone和new本不应该放在一起比较,它们的作用是不同的。但可能有一些场景下,可以用clone也可以用new,那么这时候我们选哪个呢? 我编写了两个测试,第一个是声明一个空类,第二个是带构造方...

宇润
8分钟前
0
1
点击按钮弹出类似IOS 底部 dialog

implementation 'com.baoyz.actionsheet:library:1.1.7' 然后设置按钮点击监听,,调用下列代码即可 ActionSheet.createBuilder(this, getSupportFragmentManager()) ......

lanyu96
11分钟前
1
0
专访阿里云专有云马劲,一个理性的理想主义者

“我的故事都是和团队技术相关的,自己还真没有什么引人入胜的故事。”当马劲被问到能不能多分享些个人经历故事时他笑着说,我们就干脆怀着好奇聊了聊他和阿里云专有云一路走来的故事。 马劲...

阿里云官方博客
43分钟前
1
0
java环形缓冲区

import java.util.ArrayList;import java.util.List;/** * * 环形缓冲区<br/> * 一. 写数据:<br/> * 1. push: 当数据已写满时返回false,否则可以正常写入返回true<br/>......

whoisliang
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部