Golang学习之路—函数

函数的基本概念

为完成某一功能的程序指令(语句)的集合,称为函数。
在Go中,函数分为:自定义函数、系统函数。

函数的基本语法

func 函数名(形参列表)(返回值列表){
    
    
   执行语句...
   return 返回值列表
}
  • 形参列表:表示函数的输入
  • 函数中的语句:表示为了实现某一功能代码块。
  • 函数可以有返回值,也可以没有返回值。

案例演示

package main
import "fmt"
 
func getCal(n1 int , n2 int)(int, int){
    
    
	sum := n1 + n2
	sub := n1 - n2

	return sum, sub
}

func main(){
    
    
	 var n1 int  = 10
	 var n2 int  = 20

	 //调用函数getCal计算两个数的和和差
	 sum, sub := getCal(n1,n2)
	 fmt.Println("sum =",sum,"sub =",sub)
	
}

运行结果:
在这里插入图片描述

函数的调用机制

介绍:为了更好的理解函数的调用过程,看两个案例,并画出示意图。

案例演示:

看下列代码,输出是什么?

package main
import "fmt"
 
func test(n1 int){
    
    
	n1 = n1 + 1
	fmt.Println("test() n1 =",n1)//输出结果=?
}
func main(){
    
    
	
	n1 := 10
	//调用函数test
	test(n1)
	fmt.Println("main() n1 =",n1)//输出结果=?
	
}

运行结果:
在这里插入图片描述
底层示意图:
在这里插入图片描述
说明:

  • 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈空间区分开来。
  • 在每个函数对应的栈中,数据空间是独立的,不会混淆
  • 当一个函数调用完毕后,程序会销毁这个函数对应的栈空间。

return 语句

基本语法和说明

Go支持返回多个值,这一点是其它编程语言没有的。
基本语法:

func 函数名(形参列表)(返回值列表){
    
    
   执行语句...
   return 返回值列表
}

说明:

  • 如果返回多个值是,在接收时,希望忽略某个返回值,可以使用_ 符号来表示占位忽略。
package main
import "fmt"
 
func getCal(n1 int , n2 int)(int, int){
    
    
	sum := n1 + n2
	sub := n1 - n2
	return sum, sub
}

func main(){
    
    
	 var n1 int  = 10
	 var n2 int  = 20

	 //调用函数getCal计算两个数的和和差
	 sum, sub := getCal(n1,n2)
	 //接收两个数的和,
	 sum1, _ := getCal(n1,n2)
	 fmt.Println("sum =",sum,"sub =",sub)
	 fmt.Println("sum1 =",sum1)
}
  • 如果返回值只有一个,(返回值列表)可以不写()
package main
import "fmt"
 
func test(n1 int) int{
    
    
	n1 = n1 + 1
    return  n1
}
func main(){
    
    
	
	n1 := 10

	res := test(n1)
	fmt.Println("res =",res)
	
}

函数的递归调用

基本介绍

一个函数在函数体内又调用了本身,我们称为递归调用。

快速入门

案例

package main
import "fmt"
 
func test(n int){
    
    
	 if n > 2{
    
    
		 n--
		 test(n)
	 }
	 fmt.Println("n =",n)
}

func main(){
    
    
	
	test(4)//通过分析来看函数递归调用的特点
	
}

分析图

在这里插入图片描述
输出结果:
在这里插入图片描述

说明

栈是一种先进后出的数据结构,当n的值为2时,不满足条件,执行输出语句,输出2,然后返回(出栈),此时n=3,,满足条件,n–后,为2,输出2,在次出栈,n–后的值为3,则输出3.

递归调用总结

  1. 执行一个函数时,就创建一个新的受保护的独立空间。
  2. 函数的局部变量时独立的,不会相互影响。
  3. 递归必须向退出递归条件逼近,否则就是无限递归了。
  4. 当一个函数执行完毕,或者遇到return,就是返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁。

函数使用的注意事项

  1. 函数的形参列表可以是多个,返回值列表也可以是多个。
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似private。
  4. 函数中的变量时局部的,函数外不生效。
  5. 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响。
package main
import "fmt"
 
func test(n string){
    
    
	 n = n + "321"
	 fmt.Println("n =",n)
}

func main(){
    
    
	var name string = "casey"
	test(name)
	fmt.Println("name =",name)
}

运行结果:
在这里插入图片描述

  1. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。
package main
import "fmt"
 
func test(n *string){
    
    
	 *n = *n + "321"
	 fmt.Println("*n =",*n)
}

func main(){
    
    
	var name string = "casey"
	test(&name)
	fmt.Println("name =",name)
}

运行结果:
在这里插入图片描述
示意图:
在这里插入图片描述

  1. Go函数不支持函数重载
    在这里插入图片描述
  2. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量。通过该变量可以对函数调用。
package main
import "fmt"
 
func test(n1 int,n2 int) int {
    
    
	  return n1 + n2
}


func main(){
    
    
	a := test
	fmt.Printf("a的类型为%T,test的类型为%T\n",a ,test)

	res := a(10,40)//等价 res := test(10, 40)
	fmt.Println("res =",res)
}

在这里插入图片描述

  1. 函数是一种数据类型,在Go中,函数可以作为形参,并且调用。
package main
import "fmt"
 
func test(n1 int,n2 int) int {
    
    
	  return n1 + n2
}

func myFunc(funcvar func(int,int) int,num1 int ,num2 int) int{
    
    
	return funcvar(num1, num2)
}


func main(){
    
    
	a := test
	fmt.Printf("a的类型为%T,test的类型为%T\n",a ,test)

	res := myFunc(a,100,20)
	fmt.Println("res =",res)
}

在这里插入图片描述

  1. 为了简化数据类型定义,Go支持自定义数据类型。
基本语法:
type 自定义数据类型名 数据类型//相当于一个别名
例:type myInt int
   type myFunc func(int,int) int
package main
import "fmt"

func main(){
    
    
	type myInt int
	var num1 myInt
	var num2 int
	num1 = 20
	//注意:num2 = num1 会报错,go认为myInt和int是两个不同类型
	num2 = int(num1)
	fmt.Println("num1 =",num1,"num2 =",num2)
}

运行结果:
在这里插入图片描述

  1. 支持对函数返回值命名
package main
import "fmt"
 
func getSumAndSub(n1 int,n2 int)(sum int, sub int) {
    
    
	sum = n1 + n2
	sub = n1 - n2
	return 
}

func main(){
    
    
    a1, b1 := getSumAndSub(15,30)
	fmt.Printf("sum = %v,sub = %v\n",a1,b1)
}

运行结果:
在这里插入图片描述

  1. 使用_标识符,忽略返回值
func main(){
    
    
    a1, _ := getSumAndSub(15,30)
	fmt.Printf("sum = %v\n",a1)
}
  1. Go支持可变参数
package main
import "fmt"
 
func getSum(n1 int,args...int) int {
    
    
	sum := n1 
	for i := 0; i < len(args); i++{
    
    
		sum += args[i]
	}
	return sum
}


func main(){
    
    
    sum := getSum(10,0,5,20,-5)
	fmt.Printf("sum = %v\n",sum)
}

运行结果:
在这里插入图片描述
说明:

  • args是slice切片,通过args[index]可以访问到各个值。
  • 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。

函数的defer

为什么需要defer

在函数中,程序员经常需要创建资源(比如:数据库连接,文件句柄等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制

案例

package main
import "fmt"
 
func getSum(n1 int,n2 int) int {
    
    
 
	//当执行到defer是,暂不执行,会将defer后面的语句压入到独立的栈中,
	//当函数执行完毕后,再从defer栈,按照先进后出的方式出栈
	defer fmt.Println("ok1 n1 =",n1)
	defer fmt.Println("ok2 n2 =",n2)
	sum := n1 + n2
	fmt.Println("ok3 sum =",sum)
	return sum
}


func main(){
    
    
    sum := getSum(10,-5)
	fmt.Printf("sum = %v\n",sum)
}

运行结果:
在这里插入图片描述

defer的注意事项

  • 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到栈中(为了方便理解,暂时称该栈为defer栈),然后继续执行函数的下一个语句。
  • 当函数执行完毕后,在从defer栈中,依次从栈顶按照先进后出的方式取出语句执行。

defer应用场景

defer最主要的价值是在当函数执行完毕后,可以及时的释放函数创建的资源。看下例模拟代码:
在这里插入图片描述
这种机制非常简洁,程序员不用再为什么时机关闭资源而烦心。

函数参数传递方式

两种传递方式

函数参数传递的两种方式为:值传递和引用传递,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。

值类型和引用类型

请参考这篇博客
1、值类型默认是值传递:变量直接存储值,内存通常在栈中分配。
在这里插入图片描述
2、引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由Gc来回收。
在这里插入图片描述
3、如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44736475/article/details/113936224