开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情
函数:将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集。
就比如说:一个程序里,频繁地出现某些相同的功能,这些功能可能就是改个变量,其他一模一样,那么我就可以把这些功能封装起来成为一个整体,再给这个整体起个名字,我以后用我就通过名字在主程序里调用就行了,没必要再重复去写相同功能,这个整体就叫函数。
1.函数的基本定义与使用
基本语法:
func 函数名() {
函数体
}
调用:在主函数main里直接函数名()即可
比如我现在随便定义两个函数,并在主函数里调用
func play() {
fmt.Println("玩游戏")
}
func eat() {
fmt.Println("吃饭")
}
func main() {
eat()
play()
}
上面的代码就会输出 吃饭 玩游戏 ;
因为主函数main()是程序的入口,往下执行看到eat()时,程序就会去找eat()函数在哪,找到了就执行eat()内部函数,执行完之后再回主函数接着往下执行,发现是play()函数,再去找play()去执行play()里的函数体。
2.函数指定参数个数
- 基本语法:
func 函数名(参数) {
函数体
}
- 调用:在主函数main里直接
函数名(参数)即可
其实就是在函数调用时在()里加参数
- 形参和实参:
- 在主函数调用其他自定义函数时传的参数叫实参
- 自定义函数()里的叫形参
- 可以在自定义函数()里声明多个形参,比如
func Add(num1 int , num2 int){},就声明了两个整型参数,调用时也要传俩实参 - 实参传给形参时一定要注意类型,类型一定要对得上
练习:用函数求1-100数字和
首先思路就是定义一个函数,在函数内实现并打印,在主函数调用即可,代码如下:
func SumAdd() {
var sum int
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println(sum)
}
func main() {
SumAdd()
}
但我的需求可能会改变,比如我要求1-200,那么可以把SumAdd()函数里的100改成200,那我要求1-300,那么又会更改一次;那如果一直在函数内部更改,函数的灵活性就变差了,我们写函数就是希望写完内部功能之后,在主函数调用它,并不想改变它,那么此时我们就可以采用传参数行为。
把定义的函数更改如下:
func SumAdd(num int) {
var sum int
for i := 1; i <= num; i++ {
sum += i
}
fmt.Println(sum)
}
此时在括号内定义了一个int类型的num变量,把 i <= 100 改成 i <= num,这样循环的次数由num决定
那num是什么呢?我们在主函数调用这个SumAdd函数时,就可以指明num是多少。
func main() {
SumAdd(100)
}
这里就表示num的值为100,我要是写
SumAdd(100),那么num值为200
3.函数不定参数列表
在上面我们是指明了函数括号()里的参数有多少个,主函数调用时就要传多少个实参。
但可能会出现,有时候我们暂时不清楚要传多少个参数,我们就可以使用不定参数列表。
- 基本语法
func 函数名(参数名...类型) {
函数体
}
使用不定参数时一定要加那三个点!
- 基本使用
比如我现在定义一个函数,采用不定参数列表的形式
func TestSum(args ...int) {
}
在主函数调用此函数时,就可以传任意个数的参数给args,类型必须是int。
func main() {
TestSum(1, 3, 5)
}
此时我就传了1,3,5给形参,那么args就可以看做一个集合,里面装了1,3,5这几个数
那如何在args这个集合内区分这三个数呢?
Go语言给这些参数搞了编号,比如第一个参数它的编号就是0,第二个的编号就是1
比如我想取第一个数字1,那就可以通过
args[0]得到它。
现在演示把args里的数通过循环打印出来
func TestSum(args ...int) {
for i := 0; i < len(args); i++ {
fmt.Println(args[i])
}
}
func main() {
TestSum(1, 3, 5)
}
len()函数可以获取一些数组集合的长度,有多少个数据,长度就是几。
- 可以在参数列表中,声明一些具体参数,和一个不定参数,但不定参数一定要放在最后!因为不定的含义就是不确定你会传多少个。比如
func TestA(num int , args...int){}
for range循环遍历
-
for range结构是一个增强for循环。常用来遍历数组,切片,集合。
-
for range会返回两个值,可以自定义两个变量接收,第一个是下标(编号),第二个是下标对应的数据。有时候我们不打算使用下标,或不用数据,就用下划线_代替变量
同样是上面的代码,用for range进行遍历args
func TestSum(args ...int) {
for a, v := range args {
fmt.Println("a = ", a) //下标
fmt.Println("v = ", v) //集合内的值
}
}
func main() {
TestSum(1, 3, 5)
}
如果不想打印下标,就用下划线代替,代码如下
func TestSum(args ...int) {
for _, v := range args {
fmt.Println("v = ", v)
}
}
func main() {
TestSum(1, 3, 5)
}
为什么非要用下划线代替?有人可能会觉得我不写那个接收下标的变量,或者我写了不用不就行了吗?
- 首先,如果不写接收下标的变量,比如只写
for v := range args,那么v里面存的是下标,而不是数据。- 第二,你写了接收下标的变量但不用,也会报错,你以前随便声明个变量不用,编译器运行还会报
a declared but not used错误,既然声明就一定要用,这是Go语言的特点。
4.函数返回值
返回值就是可以在函数声明时指定返回值类型。
那为什么要有返回值?比如我通过一个函数完成两个数相加,如果此时我想在主函数里通过一个变量拿到两数相加的结果,此时就要用到返回值。
跟着下面的代码来, 先写一个两数相加并打印的函数,在主函数传参。
func Add(num1 int, num2 int) {
fmt.Println(num1 + num2)
}
func main() {
Add(3, 4)
}
此时我打算在主函数接收两数相加的结果,并除以2,代码如下
func Add(num1 int, num2 int) int {
var sum int
sum = num1 + num2
return sum
}
func main() {
s := Add(3, 4)
fmt.Println(s/2)
}
在函数Add()里,用sum变量接收num1和num2相加的值,然后返回sum给主函数就行,因为sum是整型,所以在自定义函数的参数列表后面声明返回值类型为int,并在主函数要有变量接收这个函数的返回值,这里我用的是变量s
注意的点:
- 返回值关键字是return
- 返回值类型要写在参数列表后面
- 要在主函数通过变量接收这个函数的返回值
- Add()函数内部也可以直接省去第一行sum变量的定义,直接写
sum := num1 + num2
🎈第二种写法:就是在返回值类型时直接指明要返回哪个变量的值,顺便也帮你把变量创建好了。
func Add(num1 int, num2 int) (sum int) {
sum = num1 + num2
return sum
}
func main() {
s := Add(3, 4)
fmt.Println(s / 2)
}
注意:
- 要用小括号把要返回值的那个变量及它的类型括起来
- 如果采用这种写法,就表示最终会返回整型变量sum中的值
- 在函数体中也没有必要再重新创建sum变量
🎈第三种写法:在第二种写法的基础上,省略return后面需要返回的变量名
func Add(num1 int, num2 int) (sum int) {
sum = num1 + num2
return
}
func main() {
s := Add(3, 4)
fmt.Println(s / 2)
}
也就是说,如果已经指定了返回的变量的名称,那么return后面可以不用再加上变量的名称。
返回多个值
比如现在我写一个Get()函数,返回两个值,并在主函数接收
func Get() (int, int) {
num1 := 10
num2 := 20
return num1, num2 //表明返回两个变量的值
}
func main() {
s1, s2 := Get()
fmt.Println("s1 =", s1, "s2 =", s2)
}
5.函数作用域
局部变量
定义在函数内部的变量。
比如我在main()函数里定义了一个变量a,那么这个a就是一个局部变量,因为它是声明在函数内部,并且只在函数内部有效。
看下面一段代码
func Test() {
a := 1
a++
}
func main() {
a := 10
Test()
fmt.Println(a)
}
在main()函数和Test()函数里都声明了一个a,但两个a都在函数内部,所以是两个不同的a,互不影响。
比如你看我在main()函数调用了Test()函数,但打印出来的a还是10,而不是2(Test函数内部进行了a++操作)。所以我们可以得出局部变量只在函数内部有效
全局变量
在函数外部声明,任何一个函数都可以获取和更改此变量。
var a int
func main() {
a = 20
Test()
}
func Test() {
fmt.Println(a)
}
在函数外部声明了一个a变量,在main()函数内赋值,在Test()函数内打印,都是对同一个变量进行操作。
由此可见,全局变量可以在整个程序内进行调用。
🎈注意:如果局部变量的名字和全局变量的名字相同时,优先使用的是局部变量。
6.延迟调用defer
我们发现当我们调用一个函数时,程序执行到它时会立即去此函数内部执行完,但有时候我们并不想让程序马上执行它。我们就可以用defer。defer调用的函数一定会执行!
-
基本使用:关键字defer用于延迟一个函数的执行。
比如我打印两句话:
func main(){ fmt.Println("China") fmt.Println("good") }那么此时程序一定会先打印China再打印good,但如果我用延迟defer
func main(){ defer fmt.Println("China") fmt.Println("good") }那它就会先打印good,再打印China
-
执行顺序:如果一个函数中有多个defer语句,他们会以后进先出的顺序执行。即谁在后谁先执行
func main() { defer fmt.Println("China") defer fmt.Println("good") defer fmt.Println("up") }
其实实际开发中,用到defer最多的地方就是文件操作。
7.递归函数
简介
最常用来解决“树形结构”的问题,比如查看电脑中某一个文件夹下面有什么其他文件夹,其他文件夹又会包含其他文件或文件夹,就是一种典型的树形结构。
语法
给个情景:比如现在自己在看电影,想知道自己是第几排,那么可以问问前一排的人是第几排,如果前一排的人不知道就再问他们前一排的观众,一直问到第一排的观众为止。
那么可以这么写
func main() {
c := Test(3) //假如自己在第三排
fmt.Println(c)
}
func Test(n int) int {
// 只有第一排的人才知道自己的排数
if n == 1 {
return 1
}
// 如果不是第一排,问一下前一排的人
r := Test(n - 1)
fmt.Println("前一排的排数:", r)
// 把前一排人的排数+1,计算出自己的排数
return r + 1
}
执行流程:
案例
计算一个数的阶乘
var s = 1
func main() {
Test(5)
fmt.Println(s)
}
func Test(n int) {
if n == 1 {
return //终止函数
}
s *= n
Test(n - 1)
}