「GCTT 出品」Go 函数 -- Go 语言新手的带图教程

简单易懂的 Go 函数带图教程

注意:该教程仅介绍 Go 函数,不包括:可变参数、延迟函数、外部函数、方法、HTTP、封包编码等。


什么是函数?

函数是一个独立的,可以被重用的,可以一次又一次运行的代码块。函数可以有输入参数,也可以有返回值输出。

为什么我们需要函数?

  • 增加可读性、可测试性和可维护性
  • 使代码的不同部分可以分别执行
  • 可以由小模块组成新的模块
  • 可以向类型增加行为
  • 便于组织代码
  • 符合 DRY 原则

声明了一个函数 Len,输入参数为 s,类型为 string,返回值类型为 int。


✪ 首先:声明一个 Len 函数

func Len(s string) int {
 return utf8.RuneCountInString(s)
}

✪ 然后:通过它的名字调用它

Len("Hello world ")

在线运行程序

输入参数和返回值类型

输入参数被用来把数据传递给函数。返回值类型被用来从函数中返回数据。从函数中返回的数据被称为返回值。

采用一个名为 s 的 string 类型输入参数,并返回一个返回值类型为 int 的没有名字的返回值。

函数签名就是一个函数的类型 -- 由输入参数类型和返回值类型组成。


func jump()
// 签名:func()
func Len(s string) int
// 签名:func(string) int
func multiply(n ...float64) []float64
// 签名:func(...float64) []float64

Go 语言中的函数是一等公民,可以被任意赋值传递。

flen := Len
flen("Hello!")

在线运行程序

一个函数签名的示例代码。

当一个函数被调用时,它的主体将以提供的输入参数运行。如果函数声明了至少一个返回值类型,那么函数将会返回一个或多个返回值。


你可以直接从 RuneCountInString 函数返回,因为它也返回一个 int。

func Len(s string) int {
 return utf8.RuneCountInString(s)
}
lettersLen := Len("Hey!")

这个函数使用表达式作为返回值。

func returnWithExpression(a, b int) int {
 return a * b * 2 * anotherFunc(a, b)
}

函数块

每一组括号都会创建一个新的函数块,任何在函数块内声明的标识符只在该函数块内可见。

const message = "Hello world "
func HelloWorld() {
 name := "Dennis"
 message := "Hello, earthling!"
}

HelloWorld()
/*
★ message 常量在这里可见。
★ 在函数内的变量 name 在这里不可见。
★ 在函数内被隐藏的变量 message 在这里不可见。
*/

在线运行程序

现在,让我们看看输入参数和返回值类型不同风格的声明方式。

声明一个类型为 String 的输入参数 s,和一个整数返回值类型。

一个函数的输入参数和返回值类型就像变量一样起作用。

Niladic 函数

Niladic 函数不接受任何输入参数。

func tick() {

fmt.Println( time.Now().Format( time.Kitchen ) )

}

tick()

// Output: 13:50pm etc.

在线运行程序

如果一个函数没有返回值,你可以省略返回值类型和 return 这个关键字。

Singular 函数

func square(n int) int {
 return n * n
}
square(4)
// Output: 16

当函数只返回一个返回值时,不要使用括号。

多个输入参数和返回值

func scale(width, height, scale int) (int, int) {
 return width * scale, height * scale
}
w, h := scale(5, 10, 2)
// Output: w is 10, h is 20

多个返回值类型应该用圆括号括起来。

自动类型分配

Go 语言会自动为前面的参数声明类型。


这些声明是一样的:

func scale(width, height, scale int) (int, int)
func scale(width int, height int, scale int) (int, int)

错误值

一些函数通常会返回错误 -- 多个返回值让这使用很方便。

func write(w io.Writer, str string) (int, error) {
 return w.Write([]byte(s))
}
write(os.Stdout, "hello")
// Output: hello

从 Write 函数直接返回和返回多个返回值类型是相同的。因为它也返回一个 int 和一个错误值。

func write(w io.Writer, str string) (int, error) {
 n, err := w.Write([]byte(s))
 return n, err
}

如果一切正常,你可以直接返回 nil 作为结果:

func div(a, b float64) (float64, error) {
 if b == 0 {
 return 0, errors.New("divide by zero")
 }
 return a / b, nil
}
r, err := div(-1, 0)
// err: divide by zero

丢弃返回值

你可以使用下划线来丢弃返回值。

/*
假设我们有如下函数:
*/
func TempDir(dir, prefix string) (name string, err error)

丢弃错误返回值(第 2 个返回值):

name, _ := TempDir("", "test")

丢弃全部返回值:

TempDir("", "test")

省略参数名字

你也可以在未使用的输入参数中,把下划线当作名字使用。-- 以满足一个接口为例(或者看这里)。

func Write(_ []byte) (n int, err error) {
 return 0, nil
}

命名的返回值参数让你可以像使用变量一样使用返回值,而且它让你可以使用一个空的 return。


返回值 pos 的行为就像是一个变量,函数 biggest 通过一个空的 return 返回它(return 后面没有任何表达式)。

// biggest 返回切片 nums 中最大的数字的下标。
func biggest(nums []int) (pos int) {
 if len(nums) == 0 {
 return -1
 }
 m := nums[0]
 for i, n := range nums {
 if n > m {
 m = n
 pos = i
 }
 }
 // returns the pos
 return
}
pos := biggest([]int{4,5,1})
// Output: 1

上面的程序没有经过优化,时间复杂度为 O(n)。

什么时候该使用命名返回值参数?

  • 命名的返回值参数主要用作返回值的提示。
  • 不要为了跳过在函数内部的变量声明而使用命名返回值参数来替代。
  • 如果它使你的代码更具有可读性,请使用它。

当你使用命名返回值参数时,也有一个有争议的优化技巧,但编译器很快就会修复这个问题来禁止它的使用。

小心变量覆盖问题

func incr(snum string) (rnum string, err error) {
 var i int
 // start of a new scope
 if i, err := strconv.Atoi(snum); err == nil {
 i = i + 1
 }
 // end of the new scope
 rnum = strconv.Itoa(i)
 return
}
incr("abc")
// Output: 0 and nil

变量 i 和 err 只在 if 代码块内可见。最后,错误不应该是 nil,因为 abc 不能被转化为整数,所以这是一个错误,但是我们没有发现这个错误。

点击这里查看该问题解决方案。

值传递

函数 pass 把输入参数的值设置为了对应的零值。

func pass(s string, n int) {
 s, n = "", 0
}

我们传递两个变量给 pass 函数:

str, num := "knuth", 2
pass(str, num)

函数执行完,我们的两个变量的值没有任何变化。

str is "knuth"
num is 2

这是因为,当我们传递参数给函数时,参数被自动的拷贝了一份新的变量。这被叫做值传递。

在线运行程序

值传递和指针

下面这个函数接受一个指向 string 变量的指针。它修改了指针 ps 指向的值。然后它尝试将指针的值设置为 nil。所以,指针将不会再指向传递进来的 string 变量的地址。

func pass(ps *string) {
 *ps = "donald"
 ps = nil
}

我们定义了一个新的变量 s,然后我们通过 & 运算符来获取它的内存地址,并将它的内存地址保存在一个新的指针变量 ps 中。

s := "knuth"
ps := &s

让我们把 ps 传递给 pass 函数。

pass(ps)

在函数运行结束之后,我们会看到变量 s 的值已经改变。但是,指针 ps 仍然指向变量 s 的有效地址。

// Output:
// s : "donald"
// ps: 0x1040c130

指针 ps 是按值传递给函数 pass 的,只有它指向的地址被拷贝到了函数 pass 中的一个新的指针变量(形参)。所以,在函数里面把指针变量设置为 nil 对传递给函数做参数的指针(实参)没有影响。

&s 和 ps 是不同的变量,但是他们都指向相同的变量 s。


在线运行程序

到目前为止,我们已经学完了函数的参数声明方式。现在,让我们一起来看看如何正确的命名函数、输入参数和返回值类型。

函数命名

使用函数的好处有增加代码的可读性和可维护性等。你可能需要根据实际情况选择性的采取这些意见。

尽可能简短

当选择尽可能简短的命名。要选择简短、自描述而且有意义的名字。

// Not this:
func CheckProtocolIsFileTransferProtocol(protocolData io.Reader) bool
// This:
func Detect(in io.Reader) Name {
 return FTP
}
// Not this:
func CreateFromIncomingJSONBytes(incomingBytesSource []byte)
// This:
func NewFromJSON(src []byte)

使用驼峰命名法

// This:
func runServer()
func RunServer()
// Not this:
func run_server()
func RUN_SERVER()
func RunSERVER()

缩略词应该全部大写:

// Not this:
func ServeHttp()
// This:
func ServeHTTP()

选择描述性的参数名

// Not this:
func encrypt(i1, a3, b2 byte) byte
// This:
func encrypt(privKey, pubKey, salt byte) byte
// Not this:
func Write(writableStream io.Writer, bytesToBeWritten []byte)
// This:
func Write(w io.Writer, s []byte)
// 类型就非常清晰了,没有必要再取名字了

使用动词

// Not this:
func mongo(h string) error
// This:
func connectMongo(host string) error
// 如果这个函数是在包 Mongo 内,只要这样就好了:
func connect(host string) error

使用 is 和 are

// Not this:
func pop(new bool) item
// This:
func pop(isNew bool) item

不需要在命名中带上类型

// Not this:
func show(errorString string)
// This:
func show(err string)

使用 Getters 和 Setters

在 Go 语言中没有 Getters 和 Setters。但是,你可以通过函数来模拟。

// Not this:
func GetName() string
// This:
func Name() string
// Not this:
func Name() string
// This:
func SetName(name string)

Go 函数不支持的特性:

因为我会在即将发布的文章中说明下面问题的一些解决方法,所以你不需要去 duckduckgo或者 Google 搜索答案。

  • 函数重载 -- 它可以通过类型断言来模拟。
  • 模式匹配器函数。
  • 函数声明中的默认参数值。
  • 在声明中按任意顺序通过名字指定输入参数。

希望你能把这片文章分享给你的朋友。谢谢!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

热门产品

php编程基础教程.pptx|php编程培训,php,编程,基础,教程,pptx
php编程基础教程.pptx

历史上的今天:04月19日

热门专题

易捷尔单招|易捷尔单招,易捷尔单招培训,易捷尔单招报名,易捷尔单招考试,易捷尔单招培训学校,易捷尔单招分数
易捷尔单招
昆明网站建设|昆明网站建设,昆明网站开发,昆明网站建设公司,昆明网站建设价格,昆明网站设计,昆明网站制作,网页设计,高端网站建设,高端网站设计
昆明网站建设
自考本科|自考本科有用吗,自考文凭,自考本科文凭,自考文凭有用吗,自考本科文凭有用吗,自考文凭承认吗
自考本科
云南开放大学|云南开放大学报名,云南开放大学报考,云南开放大学,什么是云南开放大学,云南开放大学学历,云南开放大学学费,云南开放大学报名条件,云南开放大学报名时间,云南开放大学学历,云南开放大学专业
云南开放大学
开放大学|开放大学报名,开放大学报考,开放大学,什么是开放大学,开放大学学历,开放大学学费,开放大学报名条件,开放大学报名时间,开放大学学历,开放大学专业
开放大学
中源管业|中源管业,中源管业公司,中源管业有限公司,中源管业电话,中源管业地址,中源管业电力管,中源管业mpp电力管,中源管业cpvc电力管,中源管业pe穿线管
中源管业
天麻的功效与作用吃法|天麻的功效与作用,天麻的功效与作用吃法,天麻炖什么治头痛最好,天麻的功效与作用禁忌,天麻多少钱一斤,天麻的功效与作用吃法及禁忌,天麻怎么吃效果最好,天麻粉的功效与作用,天麻怎么吃
天麻的功效与作用吃法
大理科技管理学校|大理科技管理中等职业技术学校,大理市科技管理中等职业技术学校
大理科技管理学校

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部