函数
函数是语句序列的集合,能够将一个大的工作分解为小的任务,对外隐藏了实现细节。
函数组成
- 函数名
- 参数列表(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
可以修改。