Go语言基础(函数、方法)
一、函数
1.1 函数简介
- 定义:函数是将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集。
Go
语言的函数可以分为:自定义函数和系统函数。 - 作用:使用函数可以加强代码的复用性,提高程序编写的效率。
1.2 函数基本定义与使用
1.2.1 函数定义
- 在
Go
语言中,使用函数前,必须先声明与定义函数。Go
语言的函数由 关键字func
、函数名、参数列表、返回值、函数体 和 返回语句return
组成。 Go
语言是编译型语言,所以函数编写的顺序是无关紧要的,鉴于可读性的需求,最好把main()
函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如:函数被调用的顺序)。Go
语言函数定义格式如下:func funcName([formal_parameter_list]) ([return_types]) { // 函数体... // return valuelist }
func
:定义函数所使用的关键字,所有的函数前面都必须使用该关键字。funcName
:函数的名称。formal_parameter_list
:形式参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个被传递的值称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。- 如果参数列表中若干个相邻的参数类型相同,则可以在参数列表中省略前面变量的类型声明,只需要写最后一个参数的类型即可。例如:
func add(a, b int) (ret int, err error) { }
- 如果参数列表中若干个相邻的参数类型相同,则可以在参数列表中省略前面变量的类型声明,只需要写最后一个参数的类型即可。例如:
return_types
:返回类型,函数返回一列值。return_types
是该列值的数据类型。有些功能不需要返回值,这种情况下return_types
不是必须的。函数体
:函数定义的代码集合。return
:函数返回值使用的关键字。valuelist
:函数返回值列表。
1.2.2 函数调用
- 当创建函数时,定义了函数需要做什么,而调用函数则是执行指定任务。
- 调用函数需要向函数传递参数(如果形式参数列表
formal_parameter_list
存在),并返回值(如果返回值类型return_types
存在)。 Go
语言调用函数格式如下:[return_valuelist] = funcName([actual_parameter_list])
return_valuelist
:函数返回值列表。actual_parameter_list
:实际参数列表。
- 调用函数,可以通过两种方式来传递参数(默认情况下,
Go
语言使用的是值传递,即在调用过程中不会影响到实际参数):- 值 传 递 \pmb{值传递} 值传递值传递值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引 用 传 递 \pmb{引用传递} 引用传递引用传递引用传递:引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
- 注意:在定义函数时,形参与实参的个数与类型都要保持一致。
1.2.3 函数可变参数
- 可变参数就是一个占位符,你可以将
1
个或者多个参数赋值给这个占位符,这样不管实际参数的数量是多少,都能交给可变参数来处理。 - 可变参数使用
name ...Type
的形式声明在函数的参数列表中,而且需要是参数列表的最后一个参数。 - 可变参数在函数中将转换为对应的
[]Type
类型,所以我们可以像使用切片(slice
)时一样来获取传给函数的可变参数,即通过编号获取集合中存储的具体数据。 Golang
的可变参数不需要强制绑定参数的出现,即不需要先指定至少一个固定的形参(num
)才能使用...
可变参数。- 传递参数给带有可变参数的函数有两种形式:
- 第一种与通常的参数传递没有什么区别。
- 第二种形式是使用
...
运算符以变量...
的形式进行参数传递,这里的变量必须是与可变参数类型相同的切片(slice
),而不能是其他类型(没错,数组也不可以)。示例如下:
输出:func sum(nums ...int) { for key, value := range nums { fmt.Printf("key:%d, value:%d\n", key, value) } } func main() { numbers := []int{2, 4, 6, 8, 10} sum(numbers...) }
key:0, value:2 key:1, value:4 key:2, value:6 key:3, value:8 key:4, value:10
1.2.4 函数变量作用域
- 变量作用域表示已声明的标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。
- 函数内定义的变量称为局部变量,形式参数和返回值变量也是局部变量。局部变量的作用域在函数内部。
- 函数外定义的变量称为全局变量,全局变量可以在任何函数中使用。全局变量声明必须以
var
关键字开头。 - 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量。
1.2.5 函数返回值
Go
语言中函数可以不返回任何值,也可以返回一个或者多个值。其他的编程语言的函数一般只可以不返回值或返回一个值,这是Go
语言函数与其他编程语言函数的区别。当返回值是多个时,需要将return_types
的列表使用小括号()
括起来,不然语法会报错。- 如果相邻的几个返回值的类型相同,那么我们可以省略前几个返回值的类型,只需要写最后一个返回值的类型即可。例如:
func arithmetic(a, b int) (add, sub, mul int, div float64) { }
。 - 在函数返回多个值时,调用函数时,也必须使用相对于的参数个数来接受返回值,如果不需要的返回值,我们可以使用匿名变量来接受保存。
- 匿名变量的特点是一个下画线“
_
”,“_
”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。
1.2.6 defer 关键字
- 在我们编写函数时,经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,
Go
语言设计者提供了defer
(延时机制)关键字。 - 如果一个函数里面有多个
defer
语句,那么这些defer
语句将会按照书写的逆序进行,即后进先出(LIFO
),也就是说,最先被defer
的语句最后被执行,最后被defer
的语句最先被执行。 Go
语言的defer
语句一般都是用来处理需要关闭的资源。如果同一个函数中,既有defer
语句,同时也有return
语句,那么defer
语句会在return
语句的后面执行,即return
最先给返回值赋值,接着defer
开始执行一些收尾工作。
1.2.7 匿名函数(闭包)
Go
语言匿名函数,就是只有函数体没有函数名的函数。格式如下:func ([formal_parameter_list]) ([return_types]) { // 函数体... // return valuelist }
Go
语言的匿名函数可以作为一种类型直接赋值给变量,也可以作为函数的参数,传递给函数。格式如下:f = func([formal_parameter_list])([return_types]){ // 函数体... // return valuelist }
- 匿名函数调用:
- 定义的同时调用匿名函数:
(直接在大括号{ }
的后面写小括号( )
,在小括号里面传入实参即可)func ([formal_parameter_list]) ([return_types]) { // 函数体... // return valuelist }(actual_parameter_list)
- 使用变量调用匿名函数:
f = func([formal_parameter_list])([return_types]){ // 函数体... // return valuelist } f(actual_parameter_list)
- 定义的同时调用匿名函数:
1.2.8 函数作为实参
Go
语言可以很灵活的创建函数,并作为另外一个函数的实参。示例如下:func double(sum func(int, int) int) int { return sum(2, 2) * 2 } func main() { add := func(x, y int) int { return x + y } fmt.Println(double(add)) }
1.3 案例
- 模拟用户注册,当用户输入完用户名、密码和邮箱后,进行校验。如果发现用户名、密码或邮箱是空的,则给出“信息不能为空,用户注册为失败”的提示,否则,进行邮件发送,并给出“用户:XXX,注册成功!”的提示。
func main() { // 1. 用户注册 username, password, email := register() // 2. 校验信息 b := checkInfo(username, password, email) // 3. 发送邮件 sendEmail(username, b) } func register() (username string, password string, email string) { fmt.Print("请输入用户名:") fmt.Scanf("%s\n", &username) fmt.Print("请输入密码:") fmt.Scanf("%s\n", &password) fmt.Print("请输入邮箱:") fmt.Scanf("%s\n", &email) return username, password, email } func checkInfo(username string, password string, email string) bool { if username == "" || password == "" || email == "" { return false } else { return true } } func sendEmail(username string, b bool) { if b { fmt.Printf("用户:%s,注册成功!\n", username) } else { fmt.Println("信息不能为空,用户注册失败!") } }
1.4 递归函数
- 函数递归就是一个函数在函数体内又调用了自身,我们称为函数的递归调用。
Go
语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。
1.4.1 案例1:斐波那契数列
- 计算斐波那契数列,即前两个数为
1
,从第三个数开始每个数均为前两个数之和。func main() { for i := 1; i <= 20; i++ { fmt.Printf("%d\t", fibonacci(i)) if i%5 == 0 { fmt.Println() } } } func fibonacci(num int) int { if num < 2 { return num } return fibonacci(num-1) + fibonacci(num-2) }
1.4.1 案例2:阶乘
- 使用递归函数实现阶乘
func main() { var num = 10 fmt.Printf("%d的阶乘是%d", num, factorial(num)) } func factorial(num int) int { if num > 1 { return num * factorial(num-1) } return 1 }
1.5 预声明函数
close
:关闭channel
。delete
:用于在map
中删除实例。len
:用于返回字符串、切片长度。cap
:返回切片的最大容量。new
:用于各种类型的内存分配。make
:用于内建类型的内存分配。append
:向slice
追加零值或其他值,并返回追加后新的与slice
同类型的值。copy
:从源slice
复制元素到目标,并返回复制元素的个数。panic
、recover
:用于异常处理机制。print
、println
:底层打印函数,不用引入fmt
包。complex
、real
、imag
:用于处理复数。
二、方法(后续详解)
- 方法的声明和函数类似,他们的区别是:方法在定义的时候,会在
func
和方法名
之间增加一个参数,这个参数就是接收者
,这样我们定义的这个方法就和接收者绑定在了一起,称之为这个接收者的方法。格式如下:func (variable_name variable_data_type) funcName([formal_parameter_list]) ([return_types]) { // 函数体... // return valuelist }
variable_name
:接收者名称。variable_data_type
:接收者类型。可以是 命名类型 / 结构体类型 / 指针。
- 示例:下面定义一个结构体类型和该类型的一个方法
package main import ( "fmt" ) /* 定义结构体 */ type Circle struct { radius float64 } func main() { var c1 Circle c1.radius = 10.00 fmt.Println("圆的面积 = ", c1.getArea()) } //该 method 属于 Circle 类型对象中的方法 func (c Circle) getArea() float64 { // c.radius 即为 Circle 类型对象中的属性 return 3.14 * c.radius * c.radius }