1、函数签名
函数的签名就是他的参数类型和返回值类型,与名字无关。相同签名的函数就是同一类型的,相同类型的变量才可以赋值。
Go支持函数类型的变量和赋值。
Go语言中函数名字的大小写不仅仅是风格,更直接体现了该函数的可见性。小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。该规则同样适用于类型和变量的可见性。
2、参数
/* 测试函数 */ func test(a, b int) (c int, d int) { return a + b, a - b } main函数: e, f := test(3, 4) fmt.Println(e, f) 输出内容: 7 -1
func main() { fmt.Println(test(3, 4)) } //显示命名返回值 func test(a, b int) (c, d int) { c = a + b d = a - b return }
不定参数
在C语言时代大家一般都用过printf()函数,从那个时候开始其实已经在感受不定参数的魅力和价值。如同C语言中的printf()函数,Go语言标准库中的fmt.Println()等函数的实现也严重依赖于语言的不定参数功能。
本节我们将介绍不定参数的用法。合适地使用不定参数,可以让代码简单易用,尤其是输入输出类函数,比如日志函数等。
1. 不定参数类型
不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受不定参数类型:
func myfunc(args ...int) { for _, arg := range args { fmt.Println(arg) } }
这段代码的意思是,函数myfunc()接受不定数量的参数,这些参数的类型全部是int,所以它可以用如下方式调用:
myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)
形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。
假如没有...type这样的语法糖,开发者将不得不这么写:
func myfunc2(args []int) { for _, arg := range args { fmt.Println(arg) } }
从函数的实现角度来看,这没有任何影响,该怎么写就怎么写。但从调用方来说,情形则完全不同:
myfunc2([]int{1, 3, 7, 13})
你会发现,我们不得不加上[]int{}来构造一个数组切片实例。但是有了...type这个语法糖,我们就不用自己来处理了。
2. 不定参数的传递
假设有另一个变参函数叫做myfunc3(args ...int),下面的例子演示了如何向其传递变参:
func myfunc(args ...int) { // 按原样传递 myfunc3(args...) // 传递片段,实际上任意的int slice都可以传进去 myfunc3(args[1:]...) }
3. 任意类型的不定参数
之前的例子中将不定参数类型约束为int,如果你希望传任意类型,可以指定类型为interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:
func Printf(format string, args ...interface{}) {
// ...
}
用interface{}传递任意类型数据是Go语言的惯例用法。使用interface{}仍然是类型安全的,这和 C/C++ 不太一样。关于它的用法,可参阅3.5节的内容。代码清单2-4示范了如何分派传入interface{}类型的数据。
3、多返回值
与C、C++和Java等开发语言的一个极大不同在于,Go语言的函数或者成员的方法可以有多个返回值,这个特性能够使我们写出比其他语言更优雅、更简洁的代码,比如File.Read()函数就可以同时返回读取的字节数和错误信息。如果读取文件成功,则返回值中的n为读取的字节数,err为nil,否则err为具体的出错信息:
func (file *File) Read(b []byte) (n int, err Error)
同样,从上面的方法原型可以看到,我们还可以给返回值命名,就像函数的输入参数一样。返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。在函数中执行不带任何参数
的return语句时,会返回对应的返回值变量的值。
Go语言并不需要强制命名返回值,但是命名后的返回值可以让代码更清晰,可读性更强,同时也可以用于文档。
如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线“_”来跳过这个返回值,比如下面的代码表示调用者在读文件的时候不想关心Read()函数返回的错误码:
n, _ := f.Read(buf)
4、匿名函数
在Go里面,函数可以像普通变量一样被传递或使用,这与C语言的回调函数比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:
func(a, b int, z float64) bool { return a*b <int(z) }
匿名函数可以直接赋值给一个变量或者直接执行:
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK } (reply_chan) // 花括号后直接跟参数列表表示函数调用
5、defer压后
压后defer语句,用于注册一个函数,当defer所在函数返回时,调用defer注册的函数。
压后语句注册的函数要等到defer所在的函数返回时,按照LIFO后进先出的次序依次执行。
func testdefer() { for i := 0; i < 4; i++ { defer fmt.Println(i) } fmt.Println("-------") } 函数执行后输出内容: ------- 3 2 1 0
6、闭包
Go 语言不能在一个函数里声明另外一个函数,但是可以在一个函数中声明一个函数类型的变量。
func t(b int) func(int) int { return func(n int) int { //此处的函数就是闭包 b++ return n + b } } main函数 var tt = t(1) //此处的1传递给b,并一直保存 fmt.Println(tt(1)) fmt.Println(tt(1)) fmt.Println(tt(1)) fmt.Println(tt(1)) 输出: 3 4 5 6
闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。闭包就是将函数内部和函数外部连接起来的一座桥梁。
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
7、panic和recover
panic:
panic 英文原意:n. 恐慌,惊慌;大恐慌 adj. 恐慌的;没有理由的 vt. 使恐慌 vi. 十分惊慌
panic 是用来表示非常严重的不可恢复的错误的。在Go语言中这是一个内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。panic的作用就像我们平常接触的异常。不过Go可没有try…catch,所以,panic一般会导致程序挂掉(除非recover)。所以,Go语言中的异常,那真的是异常了。你可以试试,调用panic看看,程序立马挂掉,然后Go运行时会打印出调用栈。
但是,关键的一点是,即使函数执行的时候panic了,函数不往下走了,运行时并不是立刻向上传递panic,而是到defer那,等defer的东西都跑完了,panic再向上传递。所以这时候 defer 有点类似 try-catch-finally 中的 finally。
panic就是这么简单。抛出个真正意义上的异常。
recover:
recover 英文原意: vt. 恢复;弥补;重新获得 vi. 恢复;胜诉;重新得球 n. 还原至预备姿势
上面说到,panic的函数并不会立刻返回,而是先defer,再返回。这时候(defer的时候),如果有办法将panic捕获到,并阻止panic传递,那就异常的处理机制就完善了。
Go语言提供了recover内置函数,前面提到,一旦panic,逻辑就会走到defer那,那我们就在defer那等着,调用recover函数将会捕获到当前的panic(如果有的话),被捕获到的panic就不会向上传递了,于是,世界恢复了和平。你可以干你想干的事情了。
不过要注意的是,recover之后,逻辑并不会恢复到panic那个点去,函数还是会在defer之后返回。
package main import ( "fmt" ) func main() { defer func() { fmt.Println("defer start") if err := recover(); err != nil { fmt.Println(err) } fmt.Println("defer end") }() f() } func f() { fmt.Println("aa") panic(123) fmt.Println("bb") } 输出: aa defer start 123 defer end
panic函数会终止f()函数的执行,由于预先定义了defer处理,因此在main函数返回前调用defer处理,如果defer中没有recover处理的话,会抛出异常;如果defer中存在recover处理,则可以捕获异常,其中recover()函数返回的是panic()的参数。
panic()抛出异常,defer确保返回前被调用特定处理过程,recover()函数则实现异常信息的捕获。通过panic() defer() recover()函数的配合可以非常优雅的实现对异常的try{..}catch(){...}finally{...}处理
8、函数和方法的比较
1、声明方式不同
方法: func (r [*]ReceiverType) funcName(param) (result) {...} a. r 为struct对象的接收者,接收者不同,方法也不同 b. r 可以为值传递也可以为引用传递 c. param 和 result 可有可无,与普通函数一样 d. 调用方法,用 "." 连接,即 r.funcName() e. func (r *ReceiverType) funcName()和 func (r ReceiverType) funcName 不能同时存在(此处funcName相同) f. method 可以用于所有的类型 函数: func funcName(param) (result) {...}
2、调用方式不同
函数调用: funcName(param)
方法调用: r.funcName(param)
关于method的介绍参考:http://blog.csdn.net/chuangrain/article/details/9345619
---------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------
参考1:http://www.cnblogs.com/howDo/archive/2013/06/04/GoLang-function.html
不可或缺的函数,在Go中定义函数的方式如下:
func (p myType ) funcName ( a, b int , c string ) ( r , s int ) {
return
}
通过函数定义,我们可以看到Go中函数和其他语言中的共性和特性
共性
- 关键字——func
- 方法名——funcName
- 入参——— a,b int,b string
- 返回值—— r,s int
- 函数体—— {}
特性
Go中函数的特性是非常酷的,给我们带来不一样的编程体验。
为特定类型定义函数,即为类型对象定义方法
在Go中通过给函数标明所属类型,来给该类型定义方法,上面的 p myType
即表示给myType声明了一个方法, p myType
不是必须的。如果没有,则纯粹是一个函数,通过包名称访问。packageName.funcationName
如:
//定义新的类型double,主要目的是给float64类型扩充方法
type double float64
//判断a是否等于b
func (a double) IsEqual(b double) bool {
var r = a - b
if r == 0.0 {
return true
} else if r < 0.0 {
return r > -0.0001
}
return r < 0.0001
}
//判断a是否等于b
func IsEqual(a, b float64) bool {
var r = a - b
if r == 0.0 {
return true
} else if r < 0.0 {
return r > -0.0001
}
return r < 0.0001
}
func main() {
var a double = 1.999999
var b double = 1.9999998
fmt.Println(a.IsEqual(b))
fmt.Println(a.IsEqual(3))
fmt.Println( IsEqual( (float64)(a), (float64)(b) ) )
}
上述示例为 float64 基本类型扩充了方法IsEqual,该方法主要是解决精度问题。 其方法调用方式为: a.IsEqual(double)
,如果不扩充方法,我们只能使用函数IsEqual(a, b float64)
入参中,如果连续的参数类型一致,则可以省略连续多个参数的类型,只保留最后一个类型声明。
如 func IsEqual(a, b float64) bool
这个方法就只保留了一个类型声明,此时入参a和b均是float64数据类型。 这样也是可以的: func IsEqual(a, b float64, accuracy int) bool
变参:入参支持变参,即可接受不确定数量的同一类型的参数
如 func Sum(args ...int)
参数args是的slice,其元素类型为int 。经常使用的fmt.Printf就是一个接受任意个数参数的函数 fmt.Printf(format string, args ...interface{})
支持多返回值
前面我们定义函数时返回值有两个r,s 。这是非常有用的,我在写C#代码时,常常为了从已有函数中获得更多的信息,需要修改函数签名,使用out ,ref 等方式去获得更多返回结果。而现在使用Go时则很简单,直接在返回值后面添加返回参数即可。
如,在C#中一个字符串转换为int类型时逻辑代码
int v=0;
if ( int.TryPase("123456",out v) )
{
//code
}
而在Go中,则可以这样实现,逻辑精简而明确
if v,isOk :=int.TryPase("123456") ; isOk {
//code
}
同时在Go中很多函数充分利用了多返回值
- func (file *File) Write(b []byte) (n int, err error)
- func Sincos(x float64) (sin, cos float64)
那么如果我只需要某一个返回值,而不关心其他返回值的话,我该如何办呢? 这时可以简单的使用符号下划线”_“ 来忽略不关心的返回值。如:
_, cos = math.Sincos(3.1415) //只需要cos计算的值
命名返回值
前面我们说了函数可以有多个返回值,这里我还要说的是,在函数定义时可以给所有的返回值分别命名,这样就能在函数中任意位置给不同返回值复制,而不需要在return语句中才指定返回值。同时也能增强可读性,也提高godoc所生成文档的可读性
如果不支持命名返回值,我可能会是这样做的
func ReadFull(r Reader, buf []byte) (int, error) {
var n int
var err error
for len(buf) > 0 {
var nr int
nr, err = r.Read(buf)
n += nr
if err !=nil {
return n,err
}
buf = buf[nr:]
}
return n,err
}
但支持给返回值命名后,实际上就是省略了变量的声明,return时无需写成return n,err
而是将直接将值返回
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
函数也是“值”
和Go中其他东西一样,函数也是值,这样就可以声明一个函数类型的变量,将函数作为参数传递。
声明函数为值的变量(匿名函数:可赋值个变量,也可直接执行)
//赋值
fc := func(msg string) {
fmt.Println("you say :", msg)
}
fmt.Printf("%T \n", fc)
fc("hello,my love")
//直接执行
func(msg string) {
fmt.Println("say :", msg)
}("I love to code")
输出结果如下,这里表明fc 的类型为:func(string)
func(string)
you say : hello,my love
say : I love to code
将函数作为入参(回调函数),能带来便利。如日志处理,为了统一处理,将信息均通过指定函数去记录日志,且是否记录日志还有开关
func Log(title string, getMsg func() string) {
//如果开启日志记录,则记录日志
if true {
fmt.Println(title, ":", getMsg())
}
}
//---------调用--------------
count := 0
msg := func() string {
count++
return "您没有即使提醒我,已触犯法律"
}
Log("error", msg)
Log("warring", msg)
Log("info", msg)
fmt.Println(count)
这里输出结果如下,count 也发生了变化
error : 您没有即使提醒我,已触犯法律
warring : 您没有即使提醒我,已触犯法律
info : 您没有即使提醒我,已触犯法律
3
函数也是“类型”
你有没有注意到上面示例中的 fc := func(msg string)...
,既然匿名函数可以赋值给一个变量,同时我们经常这样给int赋值 value := 2
,是否我们可以声明func(string) 类型 呢,当然是可以的。
//一个记录日志的类型:func(string)
type saveLog func(msg string)
//将字符串转换为int64,如果转换失败调用saveLog
func stringToInt(s string, log saveLog) int64 {
if value, err := strconv.ParseInt(s, 0, 0); err != nil {
log(err.Error())
return 0
} else {
return value
}
}
//记录日志消息的具体实现
func myLog(msg string) {
fmt.Println("Find Error:", msg)
}
func main() {
stringToInt("123", myLog) //转换时将调用mylog记录日志
stringToInt("s", myLog)
}
这里我们定义了一个类型,专门用作记录日志的标准接口。在stringToInt函数中如果转换失败则调用我自己定义的接口函数进行日志处理,至于最终执行的哪个函数,则无需关心。
defer 延迟函数
defer 又是一个创新,它的作用是:延迟执行,在声明时不会立即执行,而是在函数return后时按照后进先出的原则依次执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行到,这样就能做很多我们想做的事,如释放资源,清理数据,记录日志等
这里我们重点来说明下defer的执行顺序
func deferFunc() int {
index := 0
fc := func() {
fmt.Println(index, "匿名函数1")
index++
defer func() {
fmt.Println(index, "匿名函数1-1")
index++
}()
}
defer func() {
fmt.Println(index, "匿名函数2")
index++
}()
defer fc()
return func() int {
fmt.Println(index, "匿名函数3")
index++
return index
}()
}
func main() {
deferFunc()
}
这里输出结果如下,
0 匿名函数3
1 匿名函数1
2 匿名函数1-1
3 匿名函数2
有如下结论:
- defer 是在执行完return 后执行
- defer 后进先执行
另外,我们常使用defer去关闭IO,在正常打开文件后,就立刻声明一个defer,这样就不会忘记关闭文件,也能保证在出现异常等不可预料的情况下也能关闭文件。而不像其他语言:try-catch
或者 using()
方式进行处理。
file , err :=os.Open(file)
if err != nil {
return err
}
defer file.Close()
//dosomething with file
相关推荐
golang函数查询手册.chm
当我们编写一个如下的Golang函数: ```go func one(a int) (int, int) { return a, a + 5 } ``` 这个函数返回两个整数。在汇编中,可以看到Golang并不直接将返回值存储在某个寄存器中,而是通过调用者提供的内存...
### Golang中的函数与方法详解 #### 一、引言 在编程领域,不同语言对“函数”和“方法”的定义可能有所不同。在C/C++、Python和Java等语言中,“函数”和“方法”通常被视为同一概念的不同叫法。然而,在Golang中...
golang 函数以及函数和方法的区别 在接触到go之前,我认为函数和方法只是同一个东西的两个名字而已(在我熟悉的c/c++,python,java中没有明显的区别),但是在golang中者完全是两个不同的东西。官方的解释是,方法...
在 Go 语言中,函数和方法有着明显的区别,这与许多其他编程语言不同。本文将详细介绍 Go 语言中的函数和方法,以及它们之间的差异。 函数在 Go 语言中是最基础的代码组织单元,其定义格式如下: ```go func 函数...
### Golang 函数执行时间统计装饰器实现详解 在现代软件开发中,特别是后端服务领域,性能优化是一项持续性的任务。为了确保系统的稳定性和高效性,开发者常常需要监测各个功能模块的执行效率,其中一个重要指标...
goderive goderive派生了您不想维护的普通golang函数,并使它们保持最新状态。 它通过解析未实现的函数的go代码来执行此操作,然后生成这些函数goderive goderive派生不需要维护的普通golang函数并使它们保持最新...
戈德利夫 goderive派生出您不想维护的普通 golang 函数,并使它们保持最新状态。 它通过解析未实现的函数的 go 代码来完成此操作,然后通过从输入参数类型派生它们的实现来为您生成这些函数。例子在下面的代码中, ...
功能性的一个简单的 golang 函数库。 注意:目前接口用于实现“容器”,我不确定这是一个好主意,这个实现可能会改变。去做: [x] 也许 [ ] 要么 [ ] 作家 [ ] 待定 ;)依赖关系只需要来测试断言。
【资源简介】 golang语言介绍,安装部署,运行第一个程序及基础介绍,工具介绍,go module,基础...08.golang函数介绍.md 09.golang指针结构体接口.md 10.golang的错误处理.md 11.golang协程.md 12.golang常用函数.md
golang 方便使用的助手函数
Go 函数使用分析。 你真正使用它多少次? ##用法 确定使用一组包调用函数的次数。 您可以使用带有-p标志的正则表达式指定分析范围。 对于此标志匹配的每个包和依赖项,giveupthefunc 将跟踪该包中的所有函数...
请注意,由于PHP和Golang函数的机制完全不同,因此不可能100%地实现相同的行为。 最低Go版本要求: 作业系统转到版本Linux 1.9 OSX 1.12 视窗1.13日期/时间功能PHP功能Golang功能微型时间date_add 日期添加date_...
皮套放置Mailgun的golang库和工具的地方时钟减少(几乎)替换系统time包以使计划的事件在测试中具有确定性。 有关详细信息,请参见HttpSign HttpSign是一个用于在Web服务之间签名和验证HTTP请求的库。 有关详细信息...
这包括执行PHP脚本、表达式,设置全局变量,以及实现PHP对Golang函数的回调。这样的跨语言调用机制提供了灵活性,使得开发者可以在Golang程序中利用PHP的特性或现有代码库。不过,需要注意的是,这种交互可能带来...
1. **编写 Golang 代码**:首先,在 Go 代码中使用 cgo 定义 C 函数,这些函数将作为 Java 与 Golang 交互的接口。确保这些函数遵循 C ABI(应用程序二进制接口),因为 JNI 需要与 C 函数兼容。 ```go package ...
将 Golang 函数封装成 `func(L *lua.State) int` 形式的函数。 2. 使用 `SetGlobal` 将该函数注册到全局环境中。 示例代码: ```go func hello(L *lua.State) int { L.Push(lua.LString("Hello from Go!")) ...
如果您需要更多Golang示例,可以在GitHub上查看我的其他go存储库: 展示了如何对Golang函数的执行时间进行基准测试在终端上或gif可视化各种排序算法当前仅包含蛇克隆计算并将其另存为PNG 包含各种Go软件包(例如,...