Golang 函数
函数,是将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集,可以加强代码的复用性,提高程序编写效率
Golang 中,函数是 “ 一等公民 ”,也就是说:参数、变量和返回值,都可以是函数
- 函数的定义和使用( 延迟调用 defer ) _ 高阶函数、匿名函数和闭包
- 内置函数
len 用来求长度,如:string、array、slice、map、channel new 用来分配内存,主要用来分配值类型,比如 int、struct,返回的是:指针 make 用来分配内存,主要用来分配引用类型,比如 chan、map、slice append 用来追加元素到数组、slice 中 panic 和 recover 用来做错误处理 close 主要用来关闭 channel
函数基础
(1)函数的定义和使用
// 定义函数,使用 func 关键字 func 函数名(参数)(返回值){ 函数体 } // 函数名:遵循命名规范,且,同一包中,函数名不能重复 // 参数:由参数变量和参数变量类型组成,多个参数之间使用 , 分隔 // 返回值:由返回值变量和返回值类型组成,也可以只写返回值的类型,多个返回值必须用 () 包裹,以 , 分隔 // 函数体:实现指定功能的代码块func main() { // 调用:使用 函数名() 进行调用 num := sum(5, 10) fmt.Println(num) // 15 sum(10, 20) // 调用有返回值的函数时,也可以不接收返回值 } // 定义:求和函数 func sum(x int, y int) int { fmt.Println("sum函数", x+y) return x + y }
「 延迟调用 defer 」
Golang 中,defer 语句总是将其后面跟随的语句,进行延迟处理,其规则如下:
- 在 defer 归属的函数,即将返回时,将延迟处理的语句,按 defer 定义的逆序进行
- 也就是说,先被 defer 的语句,最后被执行,最后被 defer 的语句,先执行
func main() { str := demo() fmt.Println(str) } func demo() string { fmt.Println("开始") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println("结束") return "返回值" } //开始 //结束 //3 //2 //1 //返回值
- 作用:defer 延迟调用的特性,能够非常方便的处理资源释放问题,如:资源清理、文件关闭、解锁及记录时间等
- 执行时机( 了解 ):Golang 函数中,return 语句在底层并不是原子操作,而是分为给返回值赋值和 RET 指令两步,defer 语句执行的时机,就在返回值赋值操作后,RET 指令执行前
(2)函数的参数
「 类型简写 」如果相邻参数变量的类型相同,则可以省略类型
func main() { // 调用:使用 函数名() 进行调用 sum(5, 10) } func sum(x, y int) { // 可以省略x的类型,因为y后面有类型说明,x也应该是这个 fmt.Println(x + y) // 15 }
「 可变参数 」可变参数,指的是,函数的参数,数量不固定,
- Golang 中,可变参数,本质上是通过切片来实现的,通过在参数名后面加 ... 来标识
- 可变参数,通常要作为函数的最后一个参数
func main() { num1 := sum1(1, 2, 3) fmt.Println(num1) // 6 sum2(1, 2, 3, 4, 5) } func sum1(args ...int) int { fmt.Println(args) // [1 2 3] 实际上就是一个切片 sum := 0 for _, v := range args { sum += v } return sum } // 固定参数,搭配可变参数使用时,可变参数,要放在固定参数的后面 func sum2(x int, args ...int) { fmt.Println(x) // 1 fmt.Println(args) // [2 3 4 5] }
「 使用值类型 / 指针类型,作为函数参数( 方法接收者 )的区别 」
- 对于一个函数( or 方法 ),如果函数参数( or 方法接收者 ),是指针类型,则表示,原数据是可以被修改的;
- 相反,如果是值类型,则表示,原数据不可修改 // 值传递:基本数据类型 & 复合类型( 数组、结构体 )
- 实际上,如果原数据是 slice、map、func、chan 等引用类型,也是可以修改的
需要特殊说明的是:
- 对于函数,在定义时,函数参数是值类型,还是指针类型,是有本质区别的
- 在使用值类型作为参数的函数中,不能传入指针类型,相反,亦如此,否则,编译器会报错
- 对于方法,方法接收者( 形式主义 ),定义为值类型还是指针类型,都可以被原数据 / 原数据指针调用
- 问题是:将一个值类型,传入接收者为指针类型的方法中 or 将一个指针类型,传入接收者为值类型的方法中,能不能修改原数据的值呢 ?
- — 答案是:由方法的定义( 方法接收者的类型 )决定,与方法的调用者( 原数据调用 / 原数据指针 )无关
type Person struct { name string sex string age int } func main() { p1 := Person{"小明", "男", 20} p2 := Person{"洛洛", "女", 18} //结构体调用 p1.demo1("小明同学") fmt.Println(p1) // {小明 男 20} p2.demo2("洛洛同学") fmt.Println(p2) // {洛洛同学 女 18} // 方法接收者,是指针类型,则,可以改变原数据 // 结构体指针调用 // 与方法的调用者无法 (&p1).demo1("小明同学1") fmt.Println(p1) // {小明 男 20} (&p2).demo2("洛洛同学1") fmt.Println(p2) // {洛洛同学1 女 18} } // 方法接收者,是一个值类型 func (p Person) demo1(name string) { p.name = name } // 方法接收者,是一个引用类型 func (p *Person) demo2(name string) { p.name = name }So,通常的做法是,方法的接收者,习惯性使用指针类型,而不是值类型,
一方面,可以在想修改对象时,进行修改;另一方面,也减少参数传递的拷贝成本
(3)函数的返回值
Golang 中,使用 return 关键字,向外输出返回值
「 多返回值 & 返回值命名 」
func main() { sum1, sub1 := calc1(10, 5) fmt.Println(sum1, sub1) // 15 5 sum2, sub2 := calc2(20, 10) fmt.Println(sum2, sub2) // 30 10 } // 写法1:Golang 中,函数支持多返回值,如果有多个返回值时,必须用()将所有返回值包裹起来 func calc1(x int, y int) (int, int) { sum := x + y sub := x - y return sum, sub } // 写法 2:返回值命名 func calc2(x int, y int) (sum, sub int) { // 定义返回值的时候,已经进行了声明,就不需要再次声明了 sum = x + y sub = x - y return sum, sub }
