1.1 什么是函数

函数是执行特定任务的代码块。

1.2 函数的声明

首先,go语言至少有一个main函数
函数声明语法:

  1. func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
  2. //这里是处理逻辑代码
  3. //返回多个值
  4. return value1, value2
  5. }
  • func:函数有func开始声明
  • funcName:函数名称,函数名和参数列表一起构成-函数签名
  • parametername type:参数是可选的,也就是说函数也可以不包含参数,如果多个参数的类型都相同,可以只在最后写一次类型
  • output1 type1,output2 type2:返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。
  • 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型
  • 入股只有一个返回值且不声明返回值变量,那么你可以省略包括返回值的括号(即一个返回值可以不声明返回类型)
  • 函数体:函数定义的代码集合

    1.3 函数的使用

    示例代码: ```go // 不带返回值 func outPutNowTime() { unix := time.Now().Unix() timeObj := time.Unix(unix, 0) format := timeObj.Format(“2006-01-02 15:04:05”) fmt.Println(format) }

// 带返回值 func stringReverse(str string) string { // 变量值交换 runes := []rune(str) for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 { runes[from], runes[to] = runes[to], runes[from] } return string(runes) }

  1. <a name="oWa03"></a>
  2. ## 2.1 参数的使用
  3. 形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。<br />实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。<br />函数调用:
  4. - 函数名称必须匹配
  5. - 实参和形参必须对应--顺序,个数,类型
  6. <a name="BvpKd"></a>
  7. ## 2.2 可变参数
  8. Go函数支持可变参数。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
  9. ```go
  10. func myfunc(arg ...int){}

arg ..int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型必须全部是int。在函数体中,变量arg是一个对应类型的切片(slice)

  1. for -,n:=range arg{
  2. fmt.Printf("And the number is:%d\n",n)
  3. }

示例:

  1. func sum(arg ...int) {
  2. sum := 0
  3. for i, num := range arg {
  4. sum = sum + num
  5. fmt.Printf("索引是%d,值是%d\n", i, num)
  6. }
  7. fmt.Printf("和是%d\n", sum)
  8. }
  9. /*
  10. 索引是0,值是1
  11. 索引是1,值是2
  12. 索引是2,值是3
  13. 索引是3,值是4
  14. 索引是4,值是5
  15. 和是15
  16. */

2.3 参数传递

go语言函数 的参数也是存在值传递和引用传递
函数运用场景
值传递

  1. // 值传递
  2. func validDeliver() {
  3. // 声明函数变量
  4. getSquareRoot := func(x float64) float64 {
  5. return math.Sqrt(x)
  6. }
  7. // 使用函数
  8. fmt.Println(getSquareRoot(9))
  9. }

引用传递
下面两段代码,演示引用传递的使用
首先是不用引用传递

  1. func main() {
  2. x := 3
  3. fmt.Println("x = ", x) // 应该输出 x=3
  4. x1 := add(x)
  5. fmt.Println("x1 = ", x1)
  6. fmt.Println("x = ", x)
  7. }
  8. func add(a int) int {
  9. a = a + 1
  10. return a
  11. }
  12. /*
  13. x = 3
  14. x1 = 4
  15. x = 3
  16. */

可以看出,没有使用引用传递之前,x调用add()方法后,x的值仅仅是调用的时候起作用,调用完后,还是原先的值。

下面是引用传递

  1. /*
  2. 引用传递需要牵扯到所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际上是修改变量地址处的内存。
  3. 只有add函数知道x的变量所在的地址,才能修改x变量的值。所以我们需要将x所在地址&x传入函数,并将函数的参数类型由int改为*int
  4. 即改为指针类型,才能在函数中修改x变量的值。此时参数仍然是按copy的是一个指针。
  5. */
  6. func main() {
  7. x := 3
  8. fmt.Println("x = ", x) // 应该输出 x=3
  9. x1 := add(&x)
  10. fmt.Println("x1 = ", x1)
  11. fmt.Println("x = ", x)
  12. }
  13. func add(a *int) int {
  14. *a = *a + 1
  15. return *a
  16. }
  17. /*
  18. x = 3
  19. x1 = 4
  20. x = 4
  21. */

可以看出,引用传递后,x的值调用后,值也相应的改变了。

2.3.1 数组作为参数传递给函数

将一个大数组传递给函数会消耗很多内存,有两种方法可以避免这种现象:

  • 传递数组的指针
  • 使用数组的切片** ```go arr := [3]float64{7.0, 8.5, 9.1} x := Sum(&arr) fmt.Printf(“The sum of the array is :%f”, x)

// 针对类型,位a置的函数或者返回值,可以使用interface{} func Sum(a *[3]float64) interface{} { var sum float64 = 0 for _, v := range a { sum += v } return sum }

/ The sum of the array is :24.600000 /

  1. <a name="NPvA9"></a>
  2. ### 2.3.2 切片作为参数传递给函数
  3. 数组作为参数在Go中并不常用,通常还是使用下面的切片方式:
  4. ```go
  5. arr := [5]int{10, 9, 8, 7, 6}
  6. x := subtract(arr[:])
  7. fmt.Printf("The subtraction of the array is :%d", x)
  8. // 如果明确返回值,可以直接使用返回值return无需多写
  9. func subtract(a []int) (result int) {
  10. for i, v := range a {
  11. if i == 0 {
  12. result = v
  13. } else {
  14. result = result - v
  15. }
  16. }
  17. return
  18. }
  19. /*
  20. The subtraction of the array is :-20
  21. */

3.1 函数的返回值

一个函数被调用后,返回给调用处的执行结果,就是所谓的返回值

3.1.1 一个函数可以有多个返回值

  1. func main() {
  2. x := "hello"
  3. y := "world"
  4. fmt.Printf("未转换前的数据:x=%s,y=%s\n", x, y)
  5. x, y = swap(x, y)
  6. fmt.Printf("转换后的数据:x=%s,y=%s", x, y)
  7. }
  8. // 多个返回值
  9. func swap(x, y string) (string, string) {
  10. return y, x
  11. }
  12. /*
  13. 未转换前的数据:x=hello,y=world
  14. 转换后的数据:x=world,y=hello
  15. */

3.1.2 空白标识符

_是Go中的空白标识符。它可以代替任何类型的任何值。
比如range遍历slice的时候,可以不需要索引,示例代码:

  1. countryCapitalMap := make(map[string]string)
  2. countryCapitalMap["France"] = "Paris"
  3. countryCapitalMap["Italy"] = "Rome"
  4. countryCapitalMap["Japan"] = "Tokyo"
  5. countryCapitalMap["India"] = "new Delhi"
  6. for k, v := range countryCapitalMap {
  7. fmt.Println(k, v)
  8. }
  9. fmt.Println("----------------------")
  10. // 不需要键名
  11. for _, v := range countryCapitalMap {
  12. fmt.Println(v)
  13. }
  14. /*
  15. France Paris
  16. Italy Rome
  17. Japan Tokyo
  18. India new Delhi
  19. ----------------------
  20. Paris
  21. Rome
  22. Tokyo
  23. new Delhi
  24. */

4.1 延迟函数defer

延迟语句被用于定义defer函数出函数执行结束后执行的语句。

  1. func main() {
  2. nums := []int{24, 166, 2, 56, 20}
  3. largest(nums)
  4. }
  5. func largest(nums []int) {
  6. // 延迟执行finished()函数
  7. defer finished()
  8. fmt.Println("started finding largest")
  9. max := nums[0]
  10. for _, v := range nums {
  11. if v > max {
  12. max = v
  13. }
  14. }
  15. fmt.Println("Largest number in", nums, "is", max)
  16. }
  17. func finished() {
  18. fmt.Println("Finished finding largest")
  19. }
  20. /*
  21. started finding largest
  22. Largest number in [24 166 2 56 20] is 166
  23. Finished finding largest
  24. */

延迟并不仅仅局限于函数,延迟一个方法调用也是合法的,示例:

  1. // 定义结构体
  2. type person struct {
  3. name string
  4. age int
  5. }
  6. // 定义一个只有person可以调用的函数
  7. func (p person) fullInfo() {
  8. fmt.Printf("姓名:%s,年龄:%d", p.name, p.age)
  9. }
  10. func main() {
  11. p := person{
  12. name: "zhaoyy",
  13. age: 15,
  14. }
  15. defer p.fullInfo()
  16. fmt.Println("个人信息: ")
  17. }
  18. /*
  19. 个人信息:
  20. 姓名:zhaoyy,年龄:15
  21. */

4.2 延迟参数

延迟函数的参数在执行延迟语句时被执行,而不是在执行实际的函数调用时执行。可以用于参数调用完后,回调得到刚开始的参数。
示例:

  1. func printA(a int) {
  2. fmt.Println("原始值是:", a)
  3. }
  4. func main() {
  5. a := 5
  6. defer printA(a)
  7. a = 10
  8. fmt.Println("修改值后:", a)
  9. }
  10. /*
  11. 修改值后: 10
  12. 原始值是: 5
  13. */

4.3 堆栈的延迟

当一个函数有多个延迟调用时,它们被添加到一个堆栈中,并在Last In First Out(后进先出)的顺序执行。
示例:

  1. func main() {
  2. alphabet := "ABCDEFG"
  3. fmt.Println("原始值是:", alphabet)
  4. fmt.Println("反转:")
  5. for _, v := range alphabet {
  6. defer fmt.Printf("%c\t", v)
  7. }
  8. }
  9. /*
  10. 原始值是: ABCDEFG
  11. 反转:
  12. G F E D C B A
  13. */

4.4 defer注意点

defer函数:
当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行。
当执行外围函数的return时,只有其中所有的延迟函数都执行完毕后,外围函数才会这正的返回。
当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。

5.1 使用函数调试

有的时候需要知道程序运行到哪里出错,需要知道程序运行的位置,那么可以通过下面两种方式获取程序所在位置

  1. func where() {
  2. // 参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,....
  3. _, file, line, _ := runtime.Caller(1)
  4. /*
  5. 1.pc是uintptr这个返回的是函数指针
  6. 2.file是函数所在文件名目录
  7. 3.line所在行号
  8. 4.ok 是否可以获取到信息
  9. */
  10. log.Printf("%s%d", file, line)
  11. }
  12. func where2(){
  13. log.SetFlags(log.Llongfile)
  14. log.Print("")
  15. }

上面两种函数都可以,只需要在需要调用的地方直接调用绗棉的函数即可。

5.2 计算函数执行时间

有时候需要进行基准测试,计算函数执行时间,可以使用time包中的Now和Sub函数

  1. func calcTime() {
  2. start := time.Now()
  3. sum := 0
  4. for i := 1; i <= 3000; i++ {
  5. sum = sum + i
  6. fmt.Println(sum)
  7. }
  8. fmt.Println("计算结果:", sum)
  9. end := time.Now()
  10. delta := end.Sub(start)
  11. fmt.Printf("计算1-3000累加的和花费时间为:%s", delta)
  12. }
  13. /*
  14. ...
  15. ...
  16. ...
  17. 计算结果: 4501500
  18. 计算1-3000累加的和花费时间为:4.9616ms
  19. */