Go语言函数语法上篇

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言函数语法上篇相关的知识,希望对你有一定的参考价值。

文章目录

一、Go语言函数声明

1.1Go语言里面拥三种类型的函数

  • 普通的带有名字的函数
  • 匿名函数或者 lambda 函数
  • 方法

1.2普通函数声明(定义)

  • 函数声明包括函数名、形式参数列表、返回值列表(如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的)以及函数体

    func 函数名(形式参数列表)(返回值列表)
        函数体
    
    
  • 返回值也可以像形式参数一样被命名,在这种情况下,每个返回值被声明成一个局部变量,并根据该返回值的类型,将其初始化为 0

  • 如果一个函数在声明时,包含返回值列表,那么该函数必须以 return 语句结尾,除非函数明显无法运行到结尾处

  • 如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型,下面 2 个声明是等价的

    func f(i, j, k int, s, t string)  /* ... */ 
    func f(i int, j int, k int, s string, t string)  /* ... */ 
    
  • 函数的类型被称为函数的标识符,如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符,形参和返回值的变量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示

  • 在函数调用时**,Go语言没有默认参数值**,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义

  • 在函数中,实参通过值传递的方式进行传递,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改

  • 下面,我们给出 4 种方法声明拥有 2 个 int 型参数和 1 个 int 型返回值的函数,空白标识符_可以强调某个参数未被使用

    func add(x int, y int) int    return x + y 
    func sub(x, y int) (z int)    z = x - y; return z 
    func first(x int, _ int) int  return x 
    func zero(int, int) int       return 0 
    
    func main() 
    	fmt.Printf("%T\\n", add)   // "func(int, int) int"
    	fmt.Printf("%T\\n", sub)   // "func(int, int) int"
    	fmt.Printf("%T\\n", first) // "func(int, int) int"
    	fmt.Printf("%T\\n", zero)  // "func(int, int) int"
    
    

1.3函数的返回值

  • Go语言支持多返回值,多返回值能方便地获得函数执行后的多个返回参数,Go语言经常使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误

    value, err := funcA()
    
  • 同一种类型返回值

    • 如果返回值是同一种类型,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型

    • 使用 return 语句返回时,值列表的顺序需要与函数声明的返回值类型一致

      func typedTwoValues() (int, int) 
          return 1, 2
      
      func main() 
          a, b := typedTwoValues()
          fmt.Println(a, b) //1 2
      
      
  • 带有变量名的返回值

    • Go语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型

      func namedRetValues() (a, b int) 
          a = 1
          b = 2
          return
      
      
    • 当函数使用命名返回值时,可以在 return 中不填写返回值列表

      func namedRetValues() (a, b int) 
          a = 1
          return a, 2
      
      
    • 同一种类型返回值和命名返回值两种形式只能二选一,混用时将会发生编译错误

      func namedRetValues() (a, b int, int) //命名和非命名参数混合,使用发生编译错误
      

二、函数变量

  • 在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中

    func fire() 
        fmt.Println("fire")
    
    func main() 
        var f func() //将变量f声明为func()类型
        f = fire //将 fire() 函数作为值,赋给函数变量 f,此时 f 的值为 fire() 函数
        f() //实际调用的fire函数
    
    

三、Go语言字符串的链式处理

  • 对数据的操作进行多步骤的处理被称为链式处理

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    // 字符串处理函数,传入字符串切片和处理链
    func StringProccess(list []string, chain []func(string) string) 
    
    	for index, str := range list  //获取字符串
    		result := str
    		for _, proc := range chain  //获取func函数
    			result = proc(result)
    		
    		list[index] = result //将处理结果放回到切之中
    	
    
    
    // 自定义的移除前缀的处理函数
    func removePrefix(str string) string 
    	return strings.TrimPrefix(str, "go") //去除go前缀
    
    
    func main() 
    
    	// 待处理的字符串列表
    	list := []string
    		"go scanner",
    		"go parser",
    		"go compiler",
    		"go printer",
    		"go formater",
    	
    
    	// 处理函数链
    	chain := []func(string) string
    		removePrefix,      //移除go前缀,只能移除一个
    		strings.TrimSpace, //移除空格,只能移除一次,可以移除多个顶部空格
    		strings.ToUpper,   //将字符串转换成大写
    	
    
    	// 处理字符串
    	StringProccess(list, chain)
    
    	// 输出处理好的字符串
    	for _, str := range list 
    		fmt.Println(str)
    		/* 	SCANNER
    		PARSER
    		COMPILER
    		PRINTER
    		FORMATER */
    	
    
    

四、匿名函数

  • 匿名函数的定义就是没有名字的普通函数定义

    func(参数列表)(返回参数列表)
        函数体
    
    
    • 在定义时调用匿名函数
    func(data int) 
        fmt.Println("hello", data)
    (100) //(100),表示对匿名函数进行调用,传递参数为 100
    
    • 将匿名函数赋值给变量
    // 将匿名函数体保存到f()中
    f := func(data int) 
        fmt.Println("hello", data)
    
    // 使用f()调用
    f(100)
    
  • 匿名函数作回调函数

    // 遍历切片的每个元素, 通过给定函数进行元素访问
    func visit(list []int, f func(int)) 
        for _, v := range list 
            f(v)
        
    
    
    func main() 
        // 使用匿名函数打印切片内容
        // 匿名函数作为参数传入进去
        visit([]int1, 2, 3, 4, func(v int) 
            fmt.Println(v)
        )
    
    
  • 匿名函数实现操作封装

    package main
    
    import (
    	"flag"
    	"fmt"
    )
    
    //定义命令行参数 skill,从命令行输入 --skill 可以将=后的字符串传入 skillParam 指针变量
    var skillParam = flag.String("skill", "", "skill to perform")
    
    func main() 
    	//解析命令行参数,解析完成后,skillParam 指针变量将指向命令行传入的值
    	flag.Parse()
    
    	//将匿名函数封装在map之中
    	var skill = map[string]func()
    		"fire": func() 
    			fmt.Println("chicken fire")
    		,
    		"run": func() 
    			fmt.Println("soldier run")
    		,
    		"fly": func() 
    			fmt.Println("angel fly")
    		,
    	
    
    	//通过k获取map中保存的func(),取出后就是f
      //ok为一个bool值,标识这个map之中有没有这个值
    	if f, ok := skill[*skillParam]; ok 
    		f()
    	 else 
    		fmt.Println("skill not found")
    	
    
    //执行命令
    /* go run main.go --skill=fly
    angel fly
    
    go run main.go --skill=run
    soldier run */
    

五、把函数作为接口来调用

  • 总体演示

    package main
    
    import "fmt"
    
    //调用器接口
    type nozzle interface 
    	//定义一个方法
    	Call(interface)
    
    
    //结构体类型
    type Base struct 
    
    
    //实现Base的Call
    func (s *Base) Call(p interface) 
    	fmt.Println("from base", p)
    
    
    //函数类型的定义
    type testFunc func(interface)
    
    //实现函数的Call
    func (f testFunc) Call(p interface) 
    	//调用函数本体
    	f(p)
    
    
    func main() 
    	//声明接口变量
    	var no nozzle
    	//实例化一个结构体
    	b := new(Base)
    	//将实例化的结构体赋值到接口
    	no = b
    	//使用接口调用实例化结构体中的方法
    	no.Call("i am a struct") //from base i am a struct
    
    	//-----下面对函数进行操作
    	//将匿名函数转化为testFunc类型在赋值给接口
    	no = testFunc(func(v interface) 
    		fmt.Println("i am a testFunc", v)
    	)
    
    	//使用接口调用testFunc.Call
    	no.Call("i am a func") //i am a testFunc i am a func
    
    
    
  • 结构体实现接口

    package main
    
    import (
    	"fmt"
    )
    
    // 调用器接口
    type Invoker interface 
    	// 需要实现一个Call方法
    	//调用时会传入一个 interface 类型的变量,这种类型的变量表示任意类型的值
    	Call(interface)
    
    
    type Base struct 
    
    
    //实现调用接口
    func (b *Base) Call(p interface) 
    	fmt.Println("from Base", p)
    
    
    //将定义的 Struct 类型实例化,并传入接口中进行调用
    func main() 
    	// 声明接口变量
    	var invoker Invoker
    
    	// 实例化结构体
    	s := new(Base)
    
    	// 将实例化的结构体赋值到接口
    	invoker = s
    
    	// 使用接口调用实例化结构体的方法
    	invoker.Call("hello")
    
    
  • 函数体接口实现

    • 函数的声明不能直接实现接口,需要将函数定义为类型后,使用类型实现结构体,当类型方法被调用时,还需要调用函数本体

      package main
      
      import "fmt"
      
      // 调用器接口
      type Invoker interface 
      	// 需要实现一个Call方法
      	//调用时会传入一个 interface 类型的变量,这种类型的变量表示任意类型的值
      	Call(interface)
      
      
      //函数定义为类型
      type FuncCaller func(interface)
      
      //实现Call
      func (f FuncCaller) Call(p interface) 
      	//调用f()函数本体
      	f(p)
      
      
      func main() 
      	// 声明接口变量
      	var invoker Invoker
      
      	// 将匿名函数转为FuncCaller类型, 再赋值给接口
      	invoker = FuncCaller(func(v interface) 
      		fmt.Println("from function", v)
      	)
      	
      	// 使用接口调用FuncCaller.Call, 内部会调用函数本体
      	invoker.Call("hello")
      
      
  • 作用

    • 通过一个调用器接口,实现不同类型的同名方法的调用,来达到不同的效果

六、Go语言闭包,引用外部变量的匿名函数

  • 闭包是什么

    • 闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量

    • 简单来说,函数 + 引用环境 = 闭包

    • 闭包在C++之中也叫做Lambda表达式

  • 在闭包内部修改引用的变量

    func main() 
    	str := "hello word!"
    
    	//匿名函数
    	f := func() 
    		//在引用环境之内修改str变量
    		str += "hello Go!!"
    	
    
    	//调用匿名函数
    	f()
    
    	fmt.Println(str) //hello word!hello Go!!
    
    
  • 闭包具有记忆效应

    • 被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应

      func selfAdd(num int) func() int 
      	//返回一个闭包
      	return func() int 
      		num++ //自增
      		return num
      	
      
      
      func main() 
      	//创建一个自增器,初始值为1
      	sA := selfAdd(1) //获取了一个闭包
      	fmt.Println(sA()) //2 -> 证明了闭包中的变量一直存在
      	fmt.Println(sA()) //3
      	fmt.Println(&sA)  //0xc0000ac018
      
      	//创建另外一个自增器,初始值为2
      	sA2 := selfAdd(2)
      	fmt.Println(sA2()) //3
      	fmt.Println(sA2()) //4
      	fmt.Println(&sA2)  //0xc0000ac028 -> 地址不一样表示得到的是不同的闭包
      
      
  • 闭包的作用

    • 假设现在开发一款小游戏,每增加一个玩家都需要赋予一个状态

      //玩家生成器函数
      //返回值是一个闭包,这个闭包会返回玩家的名字和初始血量
      func playerGen(name string) (func()(string,int))
      	hp:=999 //玩家的初始血量
      	
      	//创建返回的闭包
      	return func()(string,int)
      		//将变量引用到闭包之中
      		return name,hp
      	
      
      
      func main()
      	//创建一个新玩家,获取闭包
      	newPlayer:=playerGen("Jim")
      	//获取新创建的名字和玩家初始血量
      	name,hp:=newPlayer()
      
      	fmt.Println(name,hp) //Jim 999
      
      
    • 闭包还有一定的封装性,playerGen之中的hp变量,在外部是无法访问到的

七、Go语言变参函数

  • 可变参数类型

    • 可变参数是指函数传入的参数个数是可变的,为了做到这点,首先需要将函数定义为可以接受可变参数的类型

      func variableParm(args ...int)  //函数 variableParm() 接受不定数量的参数,这些参数的类型全部是 int
      	for _, v := range args 
      		fmt.Printf("%d ", v)
      	
      
      
      func main() 
      	variableParm()
      	fmt.Println()
      
      	variableParm(1, 2, 3) //1 2 3
      	fmt.Println()
      
      	variableParm(4, 5) //4,5
      
      
      • 从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数 args 可以用 for 循环来获得每个传入的参数
      • 如果没有这个语法糖,那么我们调用的时候就不得不这样写:variableParm([]int1,2,3)
  • 任意类型的可变参数

    • 如果传任意类型,可以指定类型为 interface

    • switch s.(type) 可以对 interface 类型进行类型断言,也就是判断变量的实际类型

      func variableParm(args ...interface) 
      	for _, v := range args 
          switch v.(type) //v是interface,v.(type)取其类型
      		case int:
      			fmt.Println(v,"is int")
      		case string:
      			fmt.Println(v,"is stromg")
      		case int64:
      			fmt.Println(v,"is int64")
      		
      	
      
      
      func main() 
      	intv:=10
      	strv:="string"
      	var intv64 int64=231
      
      	variableParm(intv,strv,intv64)
      	
      /* 	10 is int
      	string is stromg
      	231 is int64 */
      
      
  • 演示

    • 遍历参数列表,获取每一个参数的值

      • 可变参数列表的数量不固定,传入的参数是一个切片,如果需要获得每一个参数的具体值时,可以对可变参数变量进行遍历

        package main
        
        import (
        	"bytes"
        	"fmt"
        )
        
        func testParm(args ...string) string 
        	var buffer bytes.Buffer //定义一个字节缓冲,快速的连接字符串
        
        	for _, str := range args 
        		buffer.WriteString(str) //将遍历出来的字符串写入到buffer之中
        	
        	return buffer.String() //将连接好的字符串转化为字符串进行输出
        
        
        func main() 
        	str := testParm("123", "456", "end")
        	fmt.Println(str) //123456end
        
        
    • 获得可变参数类型——获得每一个参数的类型

      • 当可变参数为 interface 类型时,可以传入任何类型的值,此时,如果需要获得变量的类型,可以通过 switch 获得变量的类型

      • 使用 fmt.Sprintf 配合%v动词,可以将 interface 格式的任意值转为字符串

      • bytes.Buffer 字节缓冲作为快速字符串连接

        package main
        
        import (
        	"bytes"
        	"fmt"
        )
        
        func testParm(args ...interface) string 
        	var buffer bytes.Buffer //定义一个字节缓冲,快速的连接字符串
        
        	for _, str := range args 
        		// 将interface类型的值,格式化为字符串
        		parm := fmt.Sprintln(str)
        
        		var strType string //描述字符串是什么类型
        
        		switch str.(type) 
        		case int:
        			strType = "int"
        		case string:
        			strType = "string"
        		
        		buffer.WriteString("value:")
        		buffer.go语言调度gmp原理

        每天一点Go语言——Go语言语法基础及基本数据类型

        go语言语法(基础语法篇)

        Go语言文件操作

        Go语言函数语法下篇

        Go语言函数语法下篇