栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Golang——秒懂函数、参数、可变参数、匿名函数、回调函数、内置函数

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Golang——秒懂函数、参数、可变参数、匿名函数、回调函数、内置函数

函数:

函数是结构化编程的最小模块单元。它将复杂的算法过程分解为若干较小任务,隐藏相关 细节,使得程序结构更加清晰,易于维护。函数被设计成相对独立,通过接收输入参数完成一段算法指令,输出或存储相关结果。因此,函数还是代码复用和测试的基本单元。
关键字func用于定义函数

  • 函数必须先定义,后调用,定义的过程成为函数定义,
  • 函数创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为函数调用

Go函数特点:

  • 无需前置声明
  • 不支持命名嵌套定义(nested)
  • 不支持同名函数重载(overload)
  • 不支持默认参数
  • 支持可变参数
  • 支持多返回值
  • 支持命名返回值
  • 支持匿名函数和闭包

函数只能判断是否为nil,不支持其他比较操作

func a() {}
func b() {}
func main() {
	println(a == nil)
	println(a == b) // 无效操作:a == b (func can only be compared to nil
}
参数:

函数传参都是值传递,实参将自己的地址值拷贝一份给形参。如果参数是引用类型,那么就会改变实参的值(A栈帧修改B栈帧的值)

定义格式:

参数:由数据类型和变量名组成 数据类型 变量名
例: int a

	func 函数名(参数1 数据类型,参数2 数据类型...) {}

普通格式调用:

  	函数名(参数1,参数2)

函数值格式调用:

  	变量 := 对象.函数名

函数表达式格式调用:

  	变量 := 类名.函数名

函数调用时,参数的数量与类型必须与函数定义中的设置相匹配,否则程序将报错。函数的返回值通常会使用变量接收,否则该返回值将无意义

演示:

func main() {
	person := Person{24}
	// 普通格式
	person.show()

	// 函数值格式
	p1 := person.show
	p1()

	// 函数表达式格式
	p2 := (*Person).show
	p2(&person)
}

type Person struct {
	age int
}

不支持有默认值的可选参数,不支持命名实参。调用时必须按签名顺序传递指定类型和数量的实参,就算以“_”命名也不能忽略。参数列表中,相邻的同类型参数可合并

func main() {
	test(1, 2, "abc") // 报错: not enough arguments in call to test   给bool变量赋值就可以了
}

func test(x, y int, s string, _ bool) *int {
	return nil
}

参数可视作函数的局部变量,因此不能在相同层次定义同名变量

func add(x, y int) int {
	x := 100  // 错误:':=' 的左侧没有新变量
	var y int // 错误:未使用的变量 'y'
	return x + y
}

不管是指针、引用类型,还是其他类型参数,都是值拷贝传递,无非是拷贝目标对象,还是拷贝指针自身而已。在函数调用时,会为形参和返回值分配内存空间, 并将实参数据拷贝到形参内存。

func main() {
	a := 0x100
	p := &a
	println(&p, p) //输出实参p的地址。
	test(p)
}

func test(x *int) {
	println(&x, x) //输出形参x的地址。
}

输出:
从输出结果可以看出,尽管实参和形参都指向同一目标,但指针自身依然被复制。

0x1400006cf60 0x1400006cf50
0x1400006cf58 0x1400006cf50

要实现传出参数,通常建议使用返回值。当然,二级指针也是常用的手法

func main() {
	var p *int
	test(&p)
	println(*p)
}

func test(p **int) {
	x := 100 * p = &x
}

如果函数参数过多,建议将其重构为一个复合类型参数,也算是变向实现可选参数和命名实参功能

func main() {
	opt := newOption()
	opt.port = 8085
	server(opt)
}

type serverOption struct {
	address string
	port    int
	path    string
	timeout time.Duration
	log     *log.Logger
}

func newOption() *serverOption {
	return &serverOption{
		address: "0.0.0.0",
		port:    8080,
		path:    "/var/test",
		timeout: time.Second * 5,
		log:     nil,
	}
}

func server(option *serverOption) {}
形参和实参:

形参是指函数定义中的参数,实参则是函数调用时所传递的参数。形参类同函数局部变量,而实参则函数外部对象,可以是常量、变量、表达式或函数等。

形参:

函数定义中的参数,顾名思义就是只有一个形式,没有具体的数值
等同于变量定义格式,例如:var num int

实参:

函数调用中的参数,顾名思义就是有实际的参数数值
等同于使用变量或常量,例如:var num int =10

传递的形参即使在函数中操作,也不会影响原来的数据值

func main() {
	a, b := 1, 2
	Demo12(a, b)
	fmt.Println("main:", a, b) //main: 1 2
}
func Demo12(a, b int) {
	a, b = b, a
	fmt.Println("Demo12:", a, b) //Demo12: 2 1
可变参数(不定参数):

写函数的时候如果不知道形参长度,可以使用可变参数传递
变参本质上就是一个slice,它只能接收相同类型的参数值,且必须放在参数列表的最后

图解:

演示:

func main() {
	//Demo01(1)
	Demo02(1, 2, 3, 4, 5)
}

func Demo01(num int) {
	for i := 0; i < num; i++ {
		fmt.Println("test")
	}
}

func Demo02(num ...int) {
	for i := 0; i < len(num); i++ {
		fmt.Println(num[i])
	}
}

// 如果可变参数和其它参数一起作为形参时,可变参数需要放在后面
func Demo03(aaa int , num ...int) {
	for i := 0; i < len(num); i++ {
		fmt.Println(num[i])
	}
}

变参是slice,那么参数复制的仅是slice struct自身,不包括底层数组,因此我们可以修改原数据。如果需要,可使用内置函数copy复制slice数据

func test(a ...int) {
	for i := range a {
		a[i] += 100
	}
}
func main() {
	a := []int{10, 20, 30}
	test(a...)
	fmt.Println(a)
}
返回值:

函数可以有返回值,也可以有返回值,也可以定义多个返回值。使用return关键字定义
如果没有定义返回值,但是写了return,那么就相当于终止了函数,跳出循环的意思

方式一:

func main() {
	// 定义变量接收返回值并执行打印
	r := Demo06(1, 2)
	println(r)
}

// 要在参数后面加上返回值的数据类型
func Demo06(a int, b int) int {
	// 将a+b的结果赋值给sum并返回,需要在函数里再定义返回值
	sum := a + b
	return sum
}

方式二:

func main() {
	// 定义变量接收返回值并执行打印
	r := Demo07(1, 2)
	println(r)
}

// 要在参数后面加上返回值和返回值的数据类型,不需要在函数里再定义返回值
func Demo07(a int, b int) (sum int) {
	sum = a + b
	return sum
}

方式三:

func main() {
	// 定义变量接收返回值并执行打印
	r := Demo08(1, 2)
	println(r)
}

// 要在参数后面加上返回值和返回值的数据类型,不需要在函数里再定义返回值
func Demo08(a int, b int) (sum int) {
	sum = a + b
	// 如果在函数上已经指定了返回值,在函数中可以省略返回值,直接return
	return
}

函数返回多个返回值:

func main() {
	// 定义变量接收返回值
	a, b := Demo09()
	fmt.Println("a = ", a, "n b = ", b)
}

// 在函数上只写返回值类型,在函数中写返回值
func Demo09() (int, int) {
	a := 1
	b := 2
	// 返回两个变量的值
	return a, b
}

有返回值的函数,必须有明确的return终止语句

func test(x int) int {
	if x > 0 {
		return -1
	} else if x < 0 {
		return -1
	}
} // 错误:missing return at end of function

特例情况是panic,或者无break的死循环

func test(x int) int {
	for {
		break
	}
}

借鉴自动态语言的多返回值模式,让函数得以返回更多状态,尤其是error模式。

import "errors"
func div(x, y int) (int, error) { //多返回值列表必须使用括号。
	if y == 0 {
		return 0, errors.New("division by zero")
	}
	return x / y, nil
}

相同类型的多返回值可用作调用实参,或直接返回

func main() {
	log(test()) //多返回值直接用作实参。
}
func test() (int, error) {
	return div(5, 0) //多返回值直接用作return结果。
}

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, errors.New("division by zero")
	}
	return x / y, nil
}

func log(x int, err error) {
	fmt.Println(x, err)
}
返回值命名:

从这个简单的示例就可看出,命名返回值让函数声明更加清晰,同时也会改善帮助文档和 代码编辑器提示。

func paging(sql string, index int) (count int, pages int, err error) {}

命名返回值和参数一样,可当做函数局部变量使用,最后由return隐式返回

func div(x, y int) (z int, err error) {
	if y == 0 {
		err = errors.New("division by zero")
		return
	}
	
	z = x / y
	return // 相当于 “return z, err”。
}

只是这些特殊的局部变量会被不同层级的同名变量遮蔽,造成潜在错误。好在编译器会自动检查此类状况,只需改为显式return返回即可

func add(x, y int) (z int) {
	{
		z := x + y //新定义的同名局部变量,同名遮蔽。
		return     // 错误:z is shadowed during return (改成 “return z” 即可)
	}
	return
}

除遮蔽外,我们还必须对全部返回值命名,否则编译器会稀里糊涂

func test() (int, s string, e error) {
	return 0, "", nil // 错误:cannot use 0 (type int) as type string in return argument
}
函数重写:

子类中出现与父类一模一样的函数时,会使用就近原则,子类优先使用子类的函数,要想通过子类的对象调用父类的函数,需要使用子类对象.父类.函数名

函数重写的应用场景:

当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的函数,这样,即沿袭了父类的功能,又定义了子类特有的内容

演示:

func main() {
	stu := Student{}
	stu.show()        // 子类的Show函数
	stu.Person.show() // 父类的Show函数
}

type Person struct {
	age int
}

type Student struct {
	Person
	name  string
	age   int
	score float64
}

func (student Student) show() {
	fmt.Println("子类的Show函数")
}

func (person *Person) show() {
	fmt.Println("父类的Show函数")
}
匿名函数:

匿名函数是指没有定义名字符号的函数
匿名函数除没有名字外,其他和普通函数完全相同。最大的区别是,我们可在函数内部定义匿名函数,形成类似嵌套函数的效果。匿名函数可直接调用,保存到变量,作为参数或返回值

格式:

	func(形参) {
		函数体
	}(实参)

直接执行:

func main() {
	func(s string) {
		fmt.Println(s)
	}("我是实参,上面的s,是形参,我会被打印不")
}

有返回值匿名函数:

func main() {
	add := test()
	println(add(1, 2))
}

func test() func(int, int) int {
	return func(x, y int) int {
		return x + y
	}
}

作为第一类对象,不管是普通函数,还是匿名函数都可作为struct字段,或通过channel传递

func testStruct() {
	type calc struct {
		mul func(x, y int) int
	}
	x := calc{
		mul: func(x, y int) int {
			return x * y
		},
	}
	println(x.mul(2, 3))
}
func testChannel() {
	c := make(chan func(int, int) int, 2)
	c <- func(x, y int) int {
		return x + y
	}
	println((<-c)(1, 2))
}
回调函数:

本质就是函数类型的指针,通过指针,在特定位置,满足条件后,系统会自动调用函数。

普通函数调用格式:

func aaa(){}

func main(){
	aaa()// 普通函数是通过函数名+括号调用的
}

回调函数调用格式:

func handler(w http.ResponseWriter, r *http.Request) {}

func main(){
	http.HandleFunc("/itzhuzhu", handler)// 只需要将函数名传入即可
}
内置函数:

len:用来计算长度的,string、arr、slice、map、channel都可以
new:用来分配值内存的,int、float32、struct返回值是指针
make:用来分配引用类型内存的,slice、map、channel

func main() {
	//num := 100
	num2 := new(int)
	*num2 = 100
	fmt.Printf("num2的类型:%T,num2的值:%v,num2的内存地址:%v,num2指向地址存储的数据:%v", num2, num2, &num2, *num2)
}

直接定义变量的流程是:开辟内存空间->将数据存储到内存空间
适用new定义变量的流程是:开启指针内存空间->指向数据的内存地址

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/944379.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号