『GoLang』函数

函数介绍

Go语言函数基本组成包括:

  • 关键字func
  • 函数名
  • 参数列表
  • 返回值
  • 函数体
  • 返回语句

语法如下:

func 函数名(参数列表) (返回值列表) {
    // 函数体
    return
}

除了main()init()函数外,其它所有类型的函数都可以有参数与返回值

一个简单的例子:

package main

func main() {
    println("In main before calling greeting")
    greeting()
    println("In main after calling greeting")
}

func greeting() {
    println("In greeting: Hi!!!!!")
}

代码输出:

In main before calling greeting
In greeting: Hi!!!!!
In main after calling greeting

函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参与/或者不同的返回值,在 Go 里面函数重载是不被允许的。这将导致一个编译错误:

funcName redeclared in this book, previous declaration at lineno

Go 语言不支持这项特性的主要原因是函数重载需要进行多余的类型匹配影响性能;没有重载意味着只是一个简单的函数调度。所以你需要给不同的函数使用不同的名字,我们通常会根据函数的特征对函数进行命名

函数也可以作为函数类型被使用。函数类型也就是函数签名,函数类型表示具有相同参数和结果类型的所有函数的集合。函数类型的未初始化变量的值为nil。就像下面:

type  funcType func (int, int) int

上面通过 type 关键字,定义了一个新类型,函数类型 funcType

函数也可以在表达式中赋值给变量,这样作为表达式中右值出现,我们称之为函数值字面量(function literal),函数值字面量是一种表达式,它的值被称为匿名函数,就像下面一样:

f := func() int { return 7 }

下面代码对以上2种情况都做了定义和调用:

package main

import (
    "fmt"
    "time"
)

type funcType func(time.Time)  // 定义函数类型funcType

func main() {
    f := func(t time.Time) time.Time { return t }  // 方式一:直接赋值给变量
    fmt.Println(f(time.Now()))

    var timer funcType = CurrentTime  // 方式二:定义函数类型funcType变量timer
    timer(time.Now())

    funcType(CurrentTime)(time.Now())  // 先把CurrentTime函数转为funcType类型,然后传入参数调用
    // 这种处理方式在Go 中比较常见
}

func CurrentTime(start time.Time) {
    fmt.Println(start)
}

函数参数与返回值

函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与 C、C++、Java 和 C#,多值返回是 Go 的一大特性,为我们判断一个函数是否正常执行提供了方便。

我们通过 return 关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 return panic 结尾。

函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:func f(int, int, float64)

没有参数的函数通常被称为 niladic 函数(niladic function),就像 main.main()

按值传递和按引用传递

Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 Function(arg1)

如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递,比如 Function(&arg1),此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。

几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。

在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)。

命名返回值

如下的几个函数带有一个 int 参数,返回两个 int 值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值。

getX2AndX3getX2AndX3_2 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,定义函数时需要在参数列表后使用 () 把它们括起来,比如 getX2AndX3(input int) (int, int)

需要注意的是,即使只有一个命名返回值,也需要使用 () 括起来,如:

func getX2AndX3_3(input int) (x2 int){
    x2 = 2 * input
    return
}

命名返回值作为结果形参(result parameters)被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的 return 语句。

package main

import "fmt"

var num int = 10
var numx2, numx3 int

func main() {
    numx2, numx3 = getX2AndX3(num)
    PrintValues()
    numx2, numx3 = getX2AndX3_2(num)
    PrintValues()
}

func PrintValues() {
    fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}

func getX2AndX3(input int) (int, int) {
    return 2 * input, 3 * input
}

func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    // return x2, x3, 这两种返回对于命名返回值等价
    return
}

输出结果:

num = 10, 2x num = 20, 3x num = 30
num = 10, 2x num = 20, 3x num = 30

即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。:

func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    return 1,2
}

任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在 return 语句里面都要明确指出包含返回值的变量或是一个可计算的值。

变参函数

如果函数的最后一个参数是采用 ...type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。

func myFunc(a, b, arg ...int) {}

例如:

func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")

Greeting 函数中,变量 who 的值为 []string{"Joe", "Anna", "Eileen"}

如果参数被存储在一个 slice 类型的变量 l 中,则可以通过 l... 的形式来传递参数,调用变参函数。

package main

import "fmt"

func main() {
    x := min(1, 3, 2, 0)
    fmt.Printf("The minimum is: %d\n", x)

    l := []int{7,9,3,5,1}
    x = min(l...)
    fmt.Printf("The minimum in the slice is: %d", x)
}

func min(s ...int) int {
    if len(s)==0 {
        return 0
    }
    min := s[0]
    for _, v := range s {
        if v < min {
            min = v
        }
    }
    return min
}

defer和追踪

关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。

关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源。

示例:

package main

import "fmt"

func main() {
    function1()
}

func function1() {
    fmt.Printf("In function1 at the top\n")
    defer function2()
    fmt.Printf("In function1 at the bottom!\n")
}

func function2() {
    fmt.Printf("Function2: Deferred until the end of the calling function!")
}

输出:

In Function1 at the top
In Function1 at the bottom!
Function2: Deferred until the end of the calling function!

使用 defer 的语句同样可以接受参数:

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

输出:

0

这里可以看出来,defer语句虽然推迟执行,但是执行命令是已经确认的,不会由于后面i增加了就改变打印出来的i的值

当有多个 · 行为被注册时,它们会以逆序执行(类似栈,即后进先出):

func f() {
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d ", i)
    }
}

输出:

4 3 2 1 0

关键字 defer 允许我们进行一些函数执行完成后的收尾工作,例如:

  1. 关闭流文件
// open a file
defer file.Close()
  1. 解锁一个加锁的资源
mu.Lock()
defer mu.Unlock()
  1. 打印最终报告
printHeader()
defer printFooter()
  1. 关闭数据库链接
// open a database connection
defer disconnectFromDB()

内置函数

以下是一个简单的列表,会在后面对它们进行逐个深入的讲解。

名称 说明
close 用于管道通信
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)make(type)
new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针。它也可以被用于基本类型:v := new(int)
make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作new() 是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数,在部署环境中建议使用 fmt 包
complex、real imag 用于创建和操作复数

这些再具体到某种应用时再介绍详细用法

函数回调

Go 语言中函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调。

package main

import (
    "fmt"
)

func main() {
    callback(1, Add)
}

func Add(a, b int) {
    fmt.Printf("%d 与 %d 相加的和是: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
    f(y, 2) // 回调函数f
}

输出:

1 与 2 相加的和是: 3

匿名函数

函数值字面量是一种表达式,它的值被称为匿名函数。从形式上看当我们不给函数起名字的时候,可以使用匿名函数,例如:

func(x, y int) int { return x + y }

这样的函数不能够独立存在,但可以被赋值于某个变量,即保存函数的地址到变量中:

fplus := func(x, y int) int { return x + y }

然后通过变量名对函数进行调用:

fplus(3, 4)

当然,也可以直接对匿名函数进行调用,注意匿名函数的最后面加上了括号并填入了参数值,如果没有参数,也需要加上空括号,代表直接调用:

func(x, y int) int { return x + y } (3, 4)

闭包

匿名函数同样也被称之为闭包

闭包可被允许调用定义在其环境下的变量,可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。闭包继承了函数所声明时的作用域,作用域内的变量都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁。也可以理解为内层函数引用了外层函数中的变量或称为引用了自由变量。

实质上看,闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。由闭包的实质含义,我们可以推论:闭包获取捕获变量相当于引用传递,而非值传递;对于闭包函数捕获的常量和变量,无论闭包何时何处被调用,闭包都可以使用这些常量和变量,而不用关心它们表面上的作用域。

换句话说闭包函数可以访问不是它自己内部的变量(这个变量在其它作用域内声明),且这个变量是未赋值的,它在闭包里面赋值。

应用闭包:将函数作为返回值

package main

import "fmt"

func main() {
    var f = Adder()
    fmt.Println(f(1))
    fmt.Println(f(20))
    fmt.Println(f(300))
}

func Adder() func(int) int {
    var x int
    return func(delta int) int {
        x += delta
        return x
    }
}

输出:

1
21
321

三次调用函数 f 的过程中函数 Adder() 中变量 delta 的值分别为:1、20 和 300。

我们可以看到,在多次调用中,变量 x 的值是被保留的,即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量

工厂函数

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

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

原文地址:https://www.cnblogs.com/ice-coder/p/12633793.html

时间: 2024-10-28 14:53:56

『GoLang』函数的相关文章

『TensorFlow』函数查询列表_神经网络相关

神经网络(Neural Network) 激活函数(Activation Functions) 操作 描述 tf.nn.relu(features, name=None) 整流函数:max(features, 0) tf.nn.relu6(features, name=None) 以6为阈值的整流函数:min(max(features, 0), 6) tf.nn.elu(features, name=None) elu函数,exp(features) - 1 if < 0,否则featuresE

『TensorFlow』函数查询列表_张量属性调整

数据类型转换Casting 操作 描述 tf.string_to_number(string_tensor, out_type=None, name=None) 字符串转为数字 tf.to_double(x, name='ToDouble') 转为64位浮点类型–float64 tf.to_float(x, name='ToFloat') 转为32位浮点类型–float32 tf.to_int32(x, name='ToInt32') 转为32位整型–int32 tf.to_int64(x, n

『Golang』Martini框架入门

本文介绍golang中的优秀web开发框架martini! 序 Martini框架是使用Go语言作为开发语言的一个强力的快速构建模块化web应用与服务的开发框架.Martini是一个专门用来处理Web相关内容的框架,其并没有自带有关ORM或详细的分层内容.所以当我们使用Martini作为我们的开发框架时,我们还需要选取适合的ORM等其他包. 安装 go get github.com/codegangsta/martini 使用 我们可以使用如下的代码来测试我们安装的包是否是可用的: // ser

『Golang』—— 标准库之 os

Golang 的 os 库基本承袭 Unix 下 C 语言的用法 path 库: func Base(path string) string //取文件名,不含目录部分 func Dir(path string) string //取路径中的目录名部分,不含文件名 func Join(elem ...string) string //拼接字段,中间自动添加 '/' os 库: 1 ackage main 2 3 import "os" 4 import "os/exec&qu

『GoLang』包

可见性规则 在Go语言中,标识符必须以一个大写字母开头,这样才可以被外部包的代码所使用,这被称为导出.标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的.但是包名不管在什么情况下都必须小写. 在设计Go语言时,设计者们也希望确保它不是过于以ASCII为中心,这意味着需要从7位ASCII的范围来扩展标识符的空间. 所以Go语言标识符规定必须是Unicode定义的字母或数字,标识符是一个或多个Unicode字母和数字的序列, 标识符中的第一个字符必须是Unicode

『GoLang』面向对象

我们总结一下前面看到的:Go 没有类,而是松耦合的类型.方法对接口的实现. 面向对象语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢? Go实现面向对象的两个关键是struct和interface,结构代替类,因为Go语言不提供类,但提供了结构体或自定义类型,方法可以被添加到结构体或自定义类型中.结构体之间可以嵌套,类似继承.而interface定义接口,实现多态性. 封装(数据隐藏) 和别的面向对象语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层: 包

『TensorFlow』函数查询列表_数值计算

基本算术运算 操作 描述 tf.add(x, y, name=None) 求和 tf.sub(x, y, name=None) 减法 tf.mul(x, y, name=None) 乘法 tf.div(x, y, name=None) 除法 tf.mod(x, y, name=None) 取模 tf.abs(x, name=None) 求绝对值 tf.neg(x, name=None) 取负 (y = -x). tf.sign(x, name=None) 返回符号 y = sign(x) = -

『TensorFlow』常用函数实践笔记

查询列表: 『TensorFlow』函数查询列表_数值计算 『TensorFlow』函数查询列表_张量属性调整 『TensorFlow』函数查询列表_神经网络相关 经验之谈: 节点张量铺设好了之后,只要不加sess.run(),可以运行脚本检查张量节点是否匹配,无需传入实际数据流. 'conv1'指节点,'conv1:0'指节点输出的第一个张量. sess上下文环境中的函数调用即使不传入sess句柄,函数体内也存在于默认的sess环境中,可以直接sess.run(). image_holder

『Python』常用函数实践笔记

库安装: 1).pip & conda 2).在win10下手动安装python库的方法: 『python』计算机视觉_OpenCV3库安装 原生: list.append():添加元素到list末尾 list.extend():使用一个list扩展另一个list 字典列表化:字典是有顺序的,而且list字典等于list字典的key dict = {'c':1,'b':2,'a':3} list(dict) # Out[13]: # ['c', 'b', 'a'] list(dict.keys(