Go语言函数语法上篇

Go语言函数语法上篇,第1张

文章目录 一、Go语言函数声明1.1Go语言里面拥三种类型的函数1.2普通函数声明(定义)1.3函数的返回值 二、函数变量三、Go语言字符串的链式处理四、匿名函数五、把函数作为接口来调用六、Go语言闭包,引用外部变量的匿名函数七、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([]int{1, 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([]int{1,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.WriteString(parm)
		buffer.WriteString(" ")
		buffer.WriteString("type:")
		buffer.WriteString(strType)
		buffer.WriteString("\n")
	}
	return buffer.String() //将连接好的字符串转化为字符串进行输出

}

func main() {
	str := testParm(123, "abc")
	fmt.Println(str)

	/* 	value:123
	 	type:int
	 value:abc
	 type:string */
}

在多个可变参数函数中传递参数

可变参数变量是一个包含所有参数的切片,如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后面添加...,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身

可变参数使用...进行传递与切片间使用 append 连接是同一个特性

// 实际打印的函数
func rawPrint(rawList ...interface{}) {

	// 遍历可变参数切片
	for _, a := range rawList {

		// 打印参数
		fmt.Println(a)
	}
}

// 打印函数封装
func print(slist ...interface{}) {

	// 将slist可变参数传递给下一个函数
	rawPrint(slist...)

	//将slist整个切片进行传递
	rawPrint(slist)
}

func main() {

	print(1, 2, 3)
/* 	
	//全部的值进行传递
		1
		2
		3

	//整个切片进行传递
	[1 2 3] */

}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/996066.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存