Go进阶之路——函数与方法

函数

函数是语句序列的集合,能够将一个大的工作分解为小的任务,对外隐藏了实现细节。

函数组成

  • 函数名
  • 参数列表(parameter-list)
  • 返回值(result-list)
  • 函数体(body)
package main

import "fmt"

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

func main() {
	fmt.Println(add(42, 13))
}

函数可以没有参数或接受多个参数。

在这个例子中,`add` 接受两个 int 类型的参数。

与很多编程语言不同的是,Go函数的参数类型在变量名 之后,函数的返回类型也在函数名称 之后。

当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。

在这个例子中 ,

x int, y int

可以被缩写为

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

Go的声明语法:

Go的声明不同于C族的语言,用虚构的说明性语言可以这样理解:

x: int
p: pointer to int
a: array[3] of int

简洁起见Go删除了冒号和一些关键字:

x int
p *int
a [3]int

这样从左到右的样式的优点之一,随着类型变得越来越复杂,越能有所体现。例如一个函数变量的声明(类似于C中的函数指针):

f func(func(int,int) int, int) int

声明一个函数变量 f,它是 func(func(int,int) int, int) 这样一个函数,该函数的返回类型是 int。

或者,如果f返回一个函数:

f func(func(int,int) int, int) func(int, int) int

声明一个函数变量f,它是 func(func(int,int) int, int) 这样一个函数,该函数的返回类型是 func(int, int) 这样一个函数,该返回类型函数的返回类型为 int。

从左到右,清晰可见。

类型和表达式语法之间的区别使在Go中编写和调用闭包变得容易:

sum := func(a, b int) int { return a+b } (3, 4)

在数组中,Go的类型语法将方括号放在类型的左侧,而表达式语法将其放在表达式的右侧:

var a []int
x = a[1]

Go的指针使用C语言中的*表示法(为了与熟悉的C形式相关联,并没有用别的符号代替,来摆脱*带来的麻烦),但是无法对指针类型进行类似的逆转。

var p *int
x = *p

我们不能说

var p *int
x = p*

因为该后缀*会与乘法混淆。


Go语言的一大特性——多值返回

package main

import "fmt"

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

Go中的函数可以返回任意数量的返回值。

如上代码中,swap 函数返回了两个字符串。

命名返回值

Go中函数的返回值可以被命名,并且像变量那样使用。

返回值的名称应当具有一定的意义,可以作为文档使用。

没有参数的 return 语句返回结果的当前值。也就是`直接`返回。

直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。

package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

如上代码中,命名了返回的两个int值 x、y。改代码返回结果为:

相当于  return x,y

尝试在return后添加参数如   return y,x

返回结果为

修改代码:

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	var a int = x
	var b int = y
	return a,b
}

返回结果:

可以发现,即使命名了返回值,你也可以返回其它的结果。当然,以上行为不断降低了代码的可读性(迷之操作)。

参数可变函数

package main

import "fmt"

func sum(nums ...int)int{
    fmt.Println("len of nums is : ", len(nums))
    res := 0
    for _, v := range nums{
        res += v
    }
    return res
}

func main(){
    fmt.Println(sum(1))
    fmt.Println(sum(1,2))
    fmt.Println(sum(1,2,3))
}

匿名函数

func main(){
    func(name string){
       fmt.Println(name)
    }("Hello, world!")
}

闭包

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		fmt.Print("sum = ", sum)
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println("**************")
		fmt.Println(", sum +", i, "=",pos(i))  //Println()通过","连接时会自动添加空格,Print()不会
		fmt.Println(", sum -", 2*i, "=",neg(-2*i))
	}
}

Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

如上代码中,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。

实战例子: 

斐波纳契闭包

package main

import "fmt"

// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
	n := 0
	pre := 1
	pos := 1
	return func() int {
		if n < 2 {
			n++
			return 1
		} else {
			n++
			temp := pos
			pos = pos + pre
			pre = temp
			return pos
		}
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

函数作为参数

package main

import "fmt"

func sayHello(name string) {
	fmt.Println("Hello ", name)
}

func logger(f func(string), name string) {
	fmt.Println("start calling method sayHello")
	f(name)
	fmt.Println("end calling method sayHello")
}

func main() {
	logger(sayHello, "Tony")
}

传值和传引用

package main

import "fmt"

func sendValue(name string) {
	name = "Ma Yun"
}

func sendAddress(name *string) {
	*name = "Ma Yun"
}

func main() {
	// 传值和传引用
	str := "马云"
	fmt.Println("before calling sendValue, str : ", str)
	sendValue(str)
	fmt.Println("after calling sendValue, str : ", str)

	fmt.Println("before calling sendAddress, str : ", str)
	sendAddress(&str)
	fmt.Println("after calling sendAddress, str: ", str)
}

方法

方法主要源于 OOP 语言,在传统面向对象语言中 (例如 C++), 我们会用一个“类”来封装属于自己的数据和函数,这些类的函数就叫做方法。

虽然 Go 不是经典意义上的面向对象语言,但是我们可以在一些接收者(自定义类型,结构体)上定义函数,同理这些接收者的函数在 Go 里面也叫做方法。

声明

方法(method)的声明和函数很相似, 只不过它必须指定接收者:

func (t T) F() {}

注意:

  • 接收者的类型只能为用关键字 type 定义的类型,例如自定义类型,结构体。
  • 同一个接收者的方法名不能重复 (没有重载),如果是结构体,方法名还不能和字段名重复。
  • 值作为接收者无法修改其值,如果有更改需求,需要使用指针类型。
package main

import "fmt"

type Person struct{
	name string
	age int
}
func (p Person)getName(){
	fmt.Println(p.name)
}
func (p Person)getAge(){
	fmt.Println(p.age)
}
func main() {
	p := Person{"Tom",15}
	p.getAge()
	p.getName()
}

接收者类型不是任意类型,只能为用关键字 type 定义的类型,例如:

package main

func (t int64) F()  {}

func main() {
    t := int64(10)
    t.F()
}

当运行以下代码会得到 cannot define new methods on non-local type int64 类似错误信息。

接收者可以同时为值和指针

package main

import "fmt"

type Person struct{
	name string
	age int
}
func (p Person)getName(){
	fmt.Println(p.name)
}
func (p *Person)getAge(){
	fmt.Println(p.age)
}
func main() {
	p := Person{"Tom",15}
	p.getName()
	p.getAge()

	p1 := &Person{"Tony",18}
	p1.getName()
	p1.getAge()
}

可以看到无论值类型 Person 还是指针类型 &Person 都可以同时访问 getName() 和 getAge() 方法。

值和指针作为接收者的区别

package main

import "fmt"

type Person struct{
	name string
	age int
}
func (p Person)getName(){
	fmt.Println(p.name)
}
func (p *Person)getAge(){
	fmt.Println(p.age)
}
func (p Person)updateName(){
	p.name = "Peter"
}
func (p *Person)updateAge(){
	p.age++
}

func main() {
	p := Person{"Tom",15}
	fmt.Println("p: before update")
	p.getName()
	p.getAge()

	p.updateName()
	p.updateAge()
	fmt.Println("after update")
	p.getName()
	p.getAge()

	p1 := &p
	fmt.Println("p1: before update")
	p1.getName()
	p1.getAge()

	p1.updateName()
	p1.updateAge()
	fmt.Println("after update")
	p1.getName()
	p1.getAge()
}

输出结果:

小结:值作为接收者(Person) 不会修改结构体值,而指针 *Person 可以修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值