1、golang函数基本定义与使用
func 函数名 (形参列表) (返回值类型列表) {
函数体
return + 返回值列表
}
其中func用于表明这是一个函数,剩下的东西与其他语言的函数基本一致,在定义与使用的时候注意函数名、参数、返回值书写的位置即可。下面使用一个例子说明:求两个整数的和
func main() {
fmt.Println("10 + 20 = ", getSum(10, 20))
}
func getSum (num1, num2 int) int {
//这里的返回值类型列表如果只有一个返回值,那么() 可以省略
return num1 + num2
}
一些函数的使用细节:
- 函数与函数是并列的关系,因此不能嵌套定义
- 函数名的定义需要遵循标识符命名规范,
即 首字符不能是数字
函数名首字母大写表明该函数可以被本包文件和其他包文件使用
函数名首字母小写表明只能被本包文件使用 其他包文件不能使用 - 形参列表 可以是若干个参数 0,1,2 ····
- 返回值类型列表:可以返回若干个,取决于函数的实现以及应用场景。对于返回值有多个的情况,如果在某一特定场景下,有些返回值不想使用,那么可以选择使用 "_"来告诉编译器这个返回值自动丢弃,不接收
2、golang函数传递参数相关细节
经典案例引入:实现一个函数exchangeNum函数,交换两个数的值
func exchangeNum(n1, n2 int) {
var temp int = n1
n1 = n2
n2 = temp
}
func main() {
var num1 int = 100
var num2 int = 200
fmt.Printf("exchange before num1 is %v, num2 is %v \n", num1, num2)
exchangeNum(num1, num2)
fmt.Printf("exchange after num1 is %v, num2 is %v \n", num1, num2)
}
函数exchangeNum并没有将两个数完成交换。写出这样的代码根本原因是对参数的传递不够了解。下面展开说明。
我们都知道一个程序在运行时会为其分配一块内存空间,这块内存空间在逻辑上被划分为堆区、栈区、代码区等区块。其中函数执行时会在栈区上为该函数分配一个栈帧,该栈帧在函数调用完毕后会被自动销毁。所以这个栈帧的生命周期是跟随函数的。
用此例来讲,main函数运行时,会产生main函数的栈帧,内部有两个变量num1=100, num2=200,调用exchangeNum的时候,会产生该函数的栈帧,然后该函数接收到两个参数n1=100, n2=200,在exchangeNum函数内,确实完成了这两个数值的交换,函数执行完n1, n2的值交换了。但是函数执行完exchangeNum函数栈帧也一同被销毁了,反观main函数的栈帧中num1 和num2的值依旧是没有发生变化。这也就是为什么执行完exchangeNum函数后两个变量的值并没有被交换的原因。
如何成功交换?
func exchangeNum(n1, n2 *int) {
var temp int = *n1
*n1 = *n2
*n2 = temp
}
func main() {
var num1 int = 100
var num2 int = 200
fmt.Printf("exchange before num1 is %v, num2 is %v \n", num1, num2)
exchangeNum(&num1, &num2)
fmt.Printf("exchange after num1 is %v, num2 is %v \n", num1, num2)
}
//方法2
func main() {
var num1 int = 100
var num2 int = 200
num1, num2 = num2, num1
}
注意:
golang函数不支持函数重载
golang中支持可变参数 “…”
func test(args... int) { //test函数可以接受多个int类型的参数
//在函数内部处理可变参数时将其当作切片来处理
for(index := range args) {
//相关处理,如打印
}
}
基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改不会影响到原来的值
以值传递方式的数据类型,如果希望在函数内的改动能都影响到函数外,可以传入变量的地址&,函数内以指针的方式操作变量,从效果上看类似于引用传递
3、Go中函数可以当作数据类型
在Go语言中,函数也是一种数据类型,可以赋值给一个变量,那么该变量就是一个函数类型的变量。通过该变量可以对函数进行调用
既然函数是一种数据类型,因此在go中,函数可以作为形参并且被调用
func exchangeNum(n1, n2 *int) {
var temp int = *n1
*n1 = *n2
*n2 = temp
}
// 定义一个函数,能够接收函数作为形参
func forTest(flag bool, funcExNum func(*int, *int), num1, num2 *int) {
if flag {
funcExNum(num1, num2)
}
}
func main() {
var num1 int = 100
var num2 int = 200
fmt.Printf("exchange before num1 is %v, num2 is %v \n", num1, num2)
myexchange := exchangeNum
fmt.Printf("myexchange 对应的类型是:%T, exchangeNum对应的类型是: %T \n", myexchange, exchangeNum)
//此时使用 myexchange(&num1, &num2)等价于exchangeNum(&num1, &num2)
myexchange(&num1, &num2)
fmt.Printf("exchange after num1 is %v, num2 is %v \n", num1, num2)
//此时使用forTest函数再次进行交换 并测试 函数雷总最为形参的使用
forTest(true, myexchange, &num1, &num2)
fmt.Printf("exchange after forTest: num1 is %v, num2 is %v \n", num1, num2)
}
为了简化数据类型定义,Go语言支持自定义数据类型
基本语法:type 自定义数据类型 数据类型
func main() {
type myInt int
var num1 myInt = 100
var num2 int = 40
//注意:虽然是别名,但是在编译器看来,
//myInt和int并不是同一种数据类型
num2 = int(num1)
}
func exchangeNum(n1, n2 *int) {
var temp int = *n1
*n1 = *n2
*n2 = temp
}
type myFun func(*int, *int)
// 定义一个函数,能够接收函数作为形参
func forTest(flag bool, funcExNum myFun, num1, num2 *int) {
if flag {
funcExNum(num1, num2)
}
}
支持对函数返回值命名
//定义一个函数,返回两数之和 和 差
func dealNums(num1 int, num2 int) (int, int) {
sum := num1 + num2
sub := num1 - num2
return sum, sub
}
这样的写法要求返回值与函数返回值类型列表需要一一对应
还有一种写法,显示地指出返回值
func dealNums(num1 int, num2 int) (sum int, sub int) {
sub := num1 - num2
sum := num1 + num2
return
}
注意:这样写在函数内部代码执行完毕后,会将sub的最终值返回给函数返回值类型列表中的sub,sum也是如此!