Golang 函数

函数,是将具有独立功能的代码块组织成为一个整体,使其具有特殊功能的代码集,可以加强代码的复用性,提高程序编写效率


Golang 中,函数是 “ 一等公民 ”,也就是说:参数、变量和返回值,都可以是函数

  • 内置函数
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
}