Go:函数

目录

前言

1. 基本函数定义

2. 使用函数        

3. 函数变量

4. 可变参数

5. 匿名函数和闭包

6. 延迟执行语句

7. 函数参数传递的本质

8. 内置函数


前言

        在编程中经常会调用相同或者类似的操作,这些相同或者类似的操作由同一段代码完成,函 数的出现,可以避免重复编写这些代码。函数的作用就是把相对独立的某个功能抽象出来,使之 成为一个独立的实体。

        例如,开发一个支持人与人之间进行对话的社交网站,对话这个功能比较复杂,可以将它封装为一个函数,每次调用该函数就可以发起对话;大型网站都有日志功能,对所有重要操作都会记录日志,而日志处理需要由多行Go文件操作相关代码组成,将这些代码组装为函数,则每次写日志时调用此函数即可

1. 基本函数定义

Go语言中,函数的声明以关键字func为标识,具体格式如下

func 函数名(参数列表) (返回参数列表){
   函数体
}
  • 使用 func 关键字定义函数。
  • 函数名:函数名由字母、数字和下划线构成,但是函数名不能以数字开头;在同一个包内,函数名不可重复。(可简单地先将一个包理解为一个文件夹)
  • 参数列表中指定参数名称和类型。
  • 返回参数列表:返回类型中的每个参数由返回的(参数名称,可选)和参数类型组成,也可称,返回类型
  • 函数体:函数体指函数的主体代码逻辑,若函数有返回参数列表,则函数体中必须有return 语句返回值列表。
// 1. 基本函数定义
func add(x int, y int) (sum int) {
	//sum := x + y //重复定义
	sum = x + y
	return sum
}

在参数列表中,如果相邻的变量为同类型,则不必重复写出类型。

多返回值:Go 函数可以返回多个值,这在错误处理中特别有用。

// 2. 多返回值
func divAndRemainder(x, y int) (int, int) {
	return x / y, x % y
}

命名返回值:可以在函数签名中命名返回值。允许使用"裸"return 语句,会自动返回命名的返回值。

// 3. 命名返回值
func rectangleProps(length, width float64) (area, perimeter float64) {
	area = length * width
	perimeter = 2 * (length + width)
	return // 裸返回
}

2. 使用函数        

        在定义函数后,可通过对函数的调用使用函数,函数体内的代码逻辑执行完毕后,程序将继 续执行被调用函数后的代码。

        我们以add函数为例,函数的实参是1,2,函数的形参是x,y。add函数的形参x和y作用域仅限于函数体内。函数体内定义的变量作用域仅限于函数体内。

        若不想接收函数的某个返回值,可用匿名变量“_”,但是不能所有返回值都用匿名变量代替

package main

import 	"fmt"

// 1. 基本函数定义
func add(x int, y int) (sum int) {
	//sum := x + y //重复定义
	sum = x + y
	return sum
}

// 2. 多返回值
func divAndRemainder(x, y int) (int, int) {
	return x / y, x % y
}

// 3. 命名返回值
func rectangleProps(length, width float64) (area, perimeter float64) {
	area = length * width
	perimeter = 2 * (length + width)
	return // 裸返回
}

func main() {
	// 基本函数调用
	fmt.Println("1 + 2 =", add(1, 2))

	// 多返回值
	quotient, remainder := divAndRemainder(10, 3)
	fmt.Printf("10 / 3 = %d remainder %d\n", quotient, remainder)

	// 命名返回值
	area, perimeter := rectangleProps(5, 3)
	fmt.Printf("Rectangle area: %.2f, perimeter: %.2f\n", area, perimeter)
}
//result
1 + 2 = 3
10 / 3 = 3 remainder 1
Rectangle area: 15.00, perimeter: 16.00

3. 函数变量

Go语言中,函数也是一种类型,我们可以将其保存在变量中。

函数声明格式:var 变量名称 func()

var add func(x int, y int) (sum int)
package main

import "fmt"

func addSub(x int, y int) (sum int, sub int) {
	sum = x + y
	sub = x - y
	return sum, sub
}

func main() {
	a := 1
	b := 2
	var add func(x int, y int) (sum int, sub int)
	add = addSub
	sum, sub := add(a, b)
	fmt.Println(a, "+", b, "=", sum)
	fmt.Println(a, "-", b, "=", sub)
}
//result
1 + 2 = 3
1 - 2 = -1

函数变量也可用短变量格式进行声明和初始化:

package main

import "fmt"

func addSub(x int, y int) (sum int, sub int) {
	sum = x + y
	sub = x - y
	return sum, sub
}

func main() {
	a := 1
	b := 2
	//var add func(x int, y int) (sum int, sub int)
	add := addSub
	sum, sub := add(a, b)
	fmt.Println(a, "+", b, "=", sum)
	fmt.Println(a, "-", b, "=", sub)
}
//result
1 + 2 = 3
1 - 2 = -1

4. 可变参数

可变参数函数:使用 ... 语法允许函数接受任意数量的参数。

func 函数名 (固定参数列表,arg ...int ) (返回参数列表) {
   函数体
}

注意:固定参数列表可省,

arg ...int,如果还存在其他参数,可变参数一定要放到最后

package main

import "fmt"

func add(slice ...int) int {
	sum := 0
	for _, value := range slice {
		sum = sum + value
	}
	return sum
}

func main() {
	fmt.Println("1+2+...+9+10=", add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
}
//result
1+2+...+9+10= 55

可变参数与内置函数

Go语言中许多内置函数的参数都用了可变参数,比如最常用的fmt包中的Println函数和Printf 函数。 Println函数所有的参数都为可变参数:

​// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

Printf函数源码如下,第一个参数指定了需要打印的格式:

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...any) (n int, err error) {
	return Fprintf(os.Stdout, format, a...)
}

可变参数的传递

可变参数本质上是一个切片,如果要在多个函数中传递可变参数,可在传递时添加“...”。

package main

import "fmt"

func addall(slice ...int) {
	sum := 0
	for _, value := range slice {
		sum = sum + value
	}
	fmt.Println(sum)
}

func add(num ...int) {
	addall(num...)
}

func main() {
	add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
//result
55

5. 匿名函数和闭包

匿名函数: 匿名函数是没有名字的函数。它们可以在声明的同时被调用,也可以赋值给变量,或者作为参数传递给其他函数。

匿名函数即在需要函数时定义函数,匿名函数能以变量方式传递,它常常被用于实现闭包。 首先,我们来了解什么是匿名函数以及它的调用方式。

func (参数列表) (返回参数列表) {
   函数体
}

1. 定义并同时调用匿名函数,可以在匿名函数后添加“()”直接传入实参:

package main

import "fmt"

func main() {
	func(data string) {
		fmt.Println("Hello " + data)
	}("world!")
}
//result
Hello world!

2. 将匿名函数赋值给变量,之后再进行调用:

package main

import "fmt"

func main() {
	f := func(data string) {
		fmt.Println("Hello, " + data)
	}
	f("World!")
}
//result
Hello, World!

闭包: 闭包是一个函数值,它引用了其外部作用域中的变量。闭包可以访问并操作其外部函数中的变量,即使外部函数已经返回。

闭包就如同有“记忆力”一般,可对作用域内的变量的引用进行修改。

package main

import "fmt"

func main() {
	num := 1
	fmt.Printf("%p\n", &num)
	func() {
		num++
		fmt.Println(num)
		fmt.Printf("%p\n", &num)
	}()
	func() {
		num++
		fmt.Println(num)
		fmt.Printf("%p\n", &num)
	}()
}
//result
0xc00000a0b8
2
0xc00000a0b8
3
0xc00000a0b8

        以上程序中的匿名函数由于在函数体内部引用了外部的自由变量num而形成了闭包。闭包每次 对num变量的加1操作都是对变量num引用的修改。

package main

import "fmt"

func AddOne(i int) func() int {
	return func() int {
		i++
		return i
	}
}

func main() {
	a1 := AddOne(0)   //这创建了一个闭包 a1,其中 i 初始化为 0。
	fmt.Println(a1()) //调用闭包 a1,它将 i 从 0 增加到 1 并打印 1。
	fmt.Println(a1()) //再次调用闭包 a1,它将 i 从 1 增加到 2 并打印 2。
	a2 := AddOne(10)  //创建另一个闭包 a2,其中 i 初始化为 10。
	fmt.Println(a2()) //调用闭包 a2,它将 i 从 10 增加到 11 并打印 11。
	fmt.Print("a1闭包的地址为:")
	fmt.Printf("%p\n", &a1)
	fmt.Print("a2闭包的地址为:")
	fmt.Printf("%p\n", &a2)
}

//result
1
2
11
a1闭包的地址为:0xc00006c028
a2闭包的地址为:0xc00006c038

        addOne函数返回了一个闭包函数,通过定义a1和a2变量,创建了两个闭包的实例(引用环境 不同导致)。         

        每次调用闭包实例,i的值都会在原有的基础上加1。从打印的结果可以看到,两个闭包实例的 地址完全不同,两个闭包的调用结果互不影响

6. 延迟执行语句

Go语言中存在一种延迟执行的语句,由defer关键字标识,使用 defer 关键字可以延迟函数的执行直到周围的函数返回。格式如下:

defer 任意语句

      多个 defer 语句按照 LIFO(后进先出)顺序执行。defer后的语句不会被马上执行,在defer所属的函数即将返回时,函数体中的所有defer语句将 会按出现的顺序被逆序执行,即函数体中的最后一个defer语句最先被执行

package main

import "fmt"

func main() {
	fmt.Println("start")
	defer fmt.Println("这是第一句defer语句")
	defer fmt.Println("这是第二句defer语句")
	defer fmt.Println("这是第三句defer语句")
	fmt.Println("end")
}
//result
start
end
这是第三句defer语句
这是第二句defer语句
这是第一句defer语句

由于defer语句是在当前函数即将返回时被调用,所以defer常常被用来释放资源。

package main

import (
	"fmt"
	"net"
)

// tcpSend establishes a TCP connection to www.baidu.com:80 and prints the remote address.
func tcpSend() {
	conn, err := net.Dial("tcp", "www.baidu.com:80")
	if err == nil {
		defer conn.Close()
		fmt.Println("remote address:", conn.RemoteAddr())
	}
	fmt.Println("error:", err)
}

// main function calls tcpSend to initiate the TCP connection.
func main() {
	tcpSend()
}
//result
remote address: 44.0.2.250:80
error: <nil>

7. 函数参数传递的本质

在讲述参数传递前,我们首先要了解两个基本概念:值传递和引用传递。

  • 值传递的本质:Go 中所有的参数传递都是通过值传递。这意味着函数接收的是参数的副本,而不是原始值的引用。
  • 引用传递:将变量的内存地址传递给函数,函数中操作变量时会找到保存在该地址的变量, 对其进行操作,会改变原变量的值。

不同类型的行为:

  • 基本类型(如 int, float, string):传递的是值的完整副本。修改不会影响原始值。
  • 复合类型(如 slice, map, channel):传递的是包含指针的结构体的副本,该副本指向了原变量地址,因此对该副本的操作会影响原变量,从而达到了其他编程语言中类似于引用传递的效果。在Go中,切片、映射、通道、指针和接口默认使用这种效果。
package main

import "fmt"

func PassByValue(numPara int) { //值传递函数
	fmt.Printf("PassByValue函数中变量numPara地址为: %p\n", &numPara)
	numPara = 100
}

func PassByReference(numPara *int) { //引用传递函数
	fmt.Printf("PassByReference函数中指针变量numPara地址为: %p\n", &numPara)
	fmt.Printf("PassByReference函数中指针变量numPara指向的地址为: %p\n", numPara)
	*numPara = 100
}

func main() {
	num := 1
	fmt.Printf("main函数中变量num地址为: %p\n", &num)
	PassByValue(num)
	fmt.Printf("num变量值为: %d\n", num)
	PassByReference(&num)
	fmt.Printf("num变量值为: %d\n", num)
}
//result
main函数中变量num地址为: 0xc00000a0b8
passByValue函数中变量numPara地址为: 0xc00000a0e0
num变量值为: 1
passByReference函数中指针变量numPara地址为: 0xc00006c030
passByReference函数中指针变量numPara指向的地址为: 0xc00000a0b8
num变量值为: 100
  • 值传递 (PassByValue函数):
    • 当把num传递给passByValue函数时,会创建一个num的副本。
    • 函数内部对numPara的修改不会影响main()中的原始num变量。
    • 函数调用后,main()中的num值保持不变(仍为1)。
  • 引用传递 (PassByReference函数):
    • 当把&num传递给passByReference函数时,传递的是num的内存地址。
    • 函数内部通过指针*numPara可以直接访问和修改原始的num变量。
    • 函数调用后,main()中的num值会被改变(变为100)。

来一个更全面的比较

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 基本类型
	x := 5
	modifyInt(x)
	fmt.Println("After modifyInt:", x) // 输出: 5

	// 切片
	slice := []int{1, 2, 3}
	modifySlice(slice)
	fmt.Println("After modifySlice:", slice) // 输出: [100 2 3]

	// 映射
	m := map[string]int{"a": 1, "b": 2}
	modifyMap(m)
	fmt.Println("After modifyMap:", m) // 输出: map[a:1 b:2 c:3]

	// 结构体
	p := Person{"Alice", 30}
	modifyPerson(p)
	fmt.Println("After modifyPerson:", p) // 输出: {Alice 30}

	// 指针
	modifyPersonPtr(&p)
	fmt.Println("After modifyPersonPtr:", p) // 输出: {Bob 31}
}

func modifyInt(n int) {
	n = 10
}

func modifySlice(s []int) {
	s[0] = 100
}

func modifyMap(m map[string]int) {
	m["c"] = 3
}

func modifyPerson(p Person) {
	p.Name = "Charlie"
	p.Age = 35
}

func modifyPersonPtr(p *Person) {
	p.Name = "Bob"
	p.Age = 31
}

8. 内置函数

    append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
    close           -- 主要用来关闭channel
    delete            -- 从map中删除key对应的value
    panic            -- 停止常规的goroutine  (panic和recover:用来做错误处理)
    recover         -- 允许程序定义goroutine的panic动作
    imag            -- 返回complex的实部   (complex、real imag:用于创建和操作复数)
    real            -- 返回complex的虚部
    make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
    new                -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
    cap                -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
    copy            -- 用于复制和连接slice,返回复制的数目
    len                -- 来求长度,比如string、array、slice、map、channel ,返回长度
    print、println     -- 底层打印函数,在部署环境中建议使用 fmt 包
package main

import "fmt"

func main() {
	c1 := complex(1, 2)
	fmt.Println(c1, "实部为:", real(c1))
	fmt.Println(c1, "虚部为:", imag(c1))
}
//result
(1+2i) 实部为: 1
(1+2i) 虚部为: 2

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值