函数是一个固定的、可重复使用的程序段(子程序)📃,它在实现单一或相关联功能的同时,还可以带有入口和出口,📘所谓的入口,就是函数参数即形参,通过这个入口把函数的参数值传递给子程序;📙所谓出口,就是指函数的返回值,通过这个出口可以获取子程序的计算结果。
Golang 中使用 func
关键字进行函数声明,声明语句中包含函数名,参数列表, 返回值列表和函数体四个部分,其中:
interface{}/any
)return
语句提供返回值。函数从第一条语句开始执行,直到执行 return
语句或者执行函数的最后一条语句。(不同于C/C++,Golang 函数支持多个返回值类型)// 声明语法
func函数名(参数列表)(返回值列表){
函数体
}
// example:
func func1(a, b int, s string) (int, string) {
res := a + b
return res, fmt.Sprintf("%s:%d\n", s, res)
}
func main() {
res, str := func1(2, 3, "res of func1")
fmt.Printf("result:%d\n%s", res, str)
}
/* output:
result:5
res of func1:5
*/
函数定义时有参数,该变量可称为函数的形参,形参就像定义在函数体内的局部变量;而在外部调用函数时传递的变量就是函数的实参。
1️⃣值传递与引用传递:值传递和引用传递都是传递的实参的副本,但值传递是值的拷贝,而引用传递则是地址的拷贝。
所以🅰️值传递中对参数修改并不会影响外部的实际参数,因为它们的值存储在不同的内存空间中;但🅱️引用传递传递的是实际值所存储的内存空间地址,当实参和形参指向相同的内存空间,所以当对形式参数修改将会影响外部的实际参数。
一般来说,地址拷贝更为高效,特别是传入对象很大时;而值拷贝取决于拷贝的对象大小,对象越大,则性能越低 ✈。
切片slice、键值对map、通道chan、指针、接口interface 这几种类型默认以引用的方式传递。
func func2(a int, b *int, sl []int) {
a++
*b--
if len(sl) != 0 {
sl[0] = -1
}
fmt.Printf("inner: a:%d, b:%d, ", a, *b)
fmt.Println("sl:", sl)
}
func main() {
s1 := []int{1, 2, 3}
a := 1
b := 1
fmt.Printf("outer(before): a:%d, b:%d, ", a, b)
fmt.Println("sl:", s1)
func2(a, &b, s1)
fmt.Printf("outer(after): a:%d, b:%d, ", a, b)
fmt.Println("sl:", s1)
}
/* output:
outer(before): a:1, b:1, sl: [1 2 3]
inner: a:2, b:0, sl: [-1 2 3]
outer(after): a:1, b:0, sl: [-1 2 3]
*/
2️⃣不定类型参数:Golang 中在 1.18 版本之前使用 interface{}
作为泛型,后又引入了 any
关键字作为空接口 interface{}
的别名,在泛型应用场景下使用 interface{}/any
可以表示任意类型。
func func3(a any) {
switch t := a.(type) {
case nil:
fmt.Printf("变量的类型 :%T\r\n", t)
case int:
fmt.Println("变量是 int 型")
case float64:
fmt.Println("变量是 float64 型")
case func(int) float64:
fmt.Println("变量是 func(int) 型")
case bool, string:
fmt.Println("变量是 bool 或 string 型")
default:
fmt.Println("未知型")
}
}
func main() {
var i int = 1
func3(i)
var s string = "hello"
func3(s)
}
/* output:
变量是 int 型
变量是 bool 或 string 型
*/
3️⃣数量可变参数:相较于 C/C++,Golang 支持可变参数,即函数在声明和调用时没有固定数量的参数。
args ...Type
,其本质就是一个切片,可以通过 args[index]
依次访问每个参数。interface{}/any
时,可变参数中每个参数的类型都可以是不固定的。func func4(str string, hash map[string]int, args ...any) {
fmt.Println("(Fixed) first param:", str)
fmt.Println("(Fixed) second param:", hash)
fmt.Println("(Variable) args:")
for _, arg := range args {
func3(arg)
}
}
func main() {
var i int = 1
var s string = "hello"
hash := map[string]int{
"one": 1,
"two": 2,
}
func4("testString", hash, i, s, 3.14, false)
}
/* output:
(Fixed) first param: testString
(Fixed) second param: map[one:1 two:2]
(Variable) args:
变量是 int 型
变量是 bool 或 string 型
变量是 float64 型
变量是 bool 或 string 型
*/
Golang 支持多返回值,能方便地获得函数执行后的多个返回结果;由于 Golang 在错误处理上的不足,经常会使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误。
func func5(a, b int) (int, int) {
sum := a + b
sub := a - b
return sum, sub
}
func main() {
sum5, sub5 := func5(1, 1)
fmt.Printf("func5 sum:%d, sub:%d\n", sum5, sub5)
}
1️⃣返回值作为函数实参:如果某个函数的参数列表与另一个函数的返回值列表匹配,那么可以直接将后者的多个返回值作为前者的实参。
func func6(a, b int) float32 {
res := float32(a / b)
return res
}
func func7(args ...int) int {
res := 1
for _, arg := range args {
res *= arg
}
return res
}
func main() {
res6 := func6(func5(2, 1))
res7 := func7(func5(4, 2))
fmt.Printf("func6:%f, func7:%d\n", res6, res7)
}
/* output:
func6:3.000000, func7:12
*/
2️⃣命名函数返回参数:返回值列表中还可以对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型,这种被命名的返回参数和函数的局部变量类似,他们具备类型对应的默认值,最后由 return 语句隐式返回。(这种方式允许 defer
延迟调用通过闭包读取和修改)
func func8(a, b int) (res int, test int) {
res = a + b
return
}
func main() {
res8, test8 := func8(1, 1)
fmt.Printf("res:%d, test:%d\n", res8, test8)
}
/* output:
res:2, test:0
*/
如果文章对你有帮助,欢迎一键三连 👍 ⭐️ 💬 。如果还能够点击关注,那真的是对我最大的鼓励 🔥🔥🔥 。