Golang与函数

文章目录

      • 1. 概述
      • 2. 声明函数
        • 2.1 函数基本形式
        • 2.2 值传递与引用传递
      • 3. 函数变量
      • 4. 匿名函数
        • 4.1 定义形式1
        • 4.2 定义形式2
        • 4.3 定义形式3
        • 4.4 匿名函数作为回调函数
        • 4.5 匿名函数示例
      • 5. 闭包
      • 6. 函数的可变参数
      • 7.延迟调用defer
        • 7.1 defer的执行顺序
        • 7.2 defer 使用示例
      • 8. 错误处理
        • 8.1 自定义错误
        • 8.2 异常的处理
          • 8.2.1 触发panic
          • 8.2.2 recover 捕获异常

Golang与函数_第1张图片

1. 概述

函数是将实现单一或者相关联功能的代码组织起来,内部实现具有封闭性的代码集合,函数可以提高应用程序的模块性和功能代码的复用性.对大多数编程语言而言,函数是很重要的部分.

2. 声明函数

Go语言中函数声明用 func标识这是一个函数 后面跟着 函数名 参数列表 返回参数列表 函数主体内容

// 形式如下
func 函数名称(参数列表) (返回参数列表) {
	函数执行体
}

对函数简单说明 :

  • 函数名的命名和我们声明一个变量是一样的,由字母数字下划线组成.函数名不能以数字开头,同一个包中函数名不能重复

  • 函数名尽量做到见名知意,函数名中字母的大小写不同,会被认为是不同的函数

  • 一个包中的函数名首字母大写,表示该函数能被外部包访问到

    //函数add 和 函数 Add 是两个不同的函数
    func add(){}
    func Add(){}
    
  • 参数列表 : 表示可以存在多个形式参数, 每个形式参数都是由参数变量名参数数据类型 组成

    func demo(x int ,y float64, s string){}
    
  • 返回参数列表 : 可以是返回值的数据类型列表,也可以类似参数列表一样 返回参数变量名数据类型 的组成,函数声明了有返回值,那就必须用 return 语句 提供返回值列表

    func demo1(x int ,y float64)(int,error){
        ...
        ...
        return x+y error 
    }
    
  • 函数执行体 : 能复用的代码片段

2.1 函数基本形式
package main

import (
	"fmt"
	"math"
)
// 基本函数格式写法
func hypot(x float64 ,y float64) (res float64){
	res = math.Sqrt(x*x+y*y)
    // 因为返回值声明了变量res ,所以return 默认将res返回
	return 
}
// 如果参数列表中参数类型相同,可以简写
func swop(x,y int,s1,s2 string) (int,int,string,string){
	x,y = y,x
	s1 ,s2 = s1 ,s2
    // Go语言是支持多返回值得
    // 以为返回值列表中没有声明返回变量,所以return要将返回的值写清楚
    // 定义了多少返回值,就必须将它们都返回,否则编译报错`not enough arguments to return`
	return x,y,s1,s2
}
func main(){
    // 此处都是函数调用
	fmt.Println(hypot(7,9))
	fmt.Println(swop(10,99,"hello","golang"))
}

go run main.go

11.40175425099138
99 10 hello golang

示例:

package main

import "fmt"

const(
	M = 60
	H = M*60
	D = H*24
)
func parseTime(s float64) (m ,h,d float64){
	m = s/M
	h = s/H
	d = s/D
	return
}
func main(){
	s := 1797979
	m,h,d := parseTime(float64(s))
	fmt.Printf("%d秒约等于%f分钟,%f小时,%f天",s,m,h,d)
}

go run main.go

1797979秒约等于29966.316667分钟,499.438611小时,20.809942天
2.2 值传递与引用传递

基本数据类型和数组默认是值传递,在函数内部修改,不影响原有的值

希望函数函数内部修改,能影响到原有的变量,那就采用引用传递,将变量的地址 &传给函数,函数内部以指针 形式操作

⭐️ 不论是值传递还是引用传递,其实给函数的都是变量副本,不同的是,值传递的是值得拷贝副本,而引用传递则是地址的拷贝,通常地址拷贝因为数据量小,故效率更高.而值拷贝则是数据量越大,效率越差

值传递类型 : 所有int类型 , 所有 float类型 , bool类型 , 数组 , 结构体

引用类型 : 指针 , slice, map , chan , interface 等类型

值传递的示例:

package main

import "fmt"

//都是值传递,拷贝了一个副本数据
func passValue(n int, f float64, s string, arr [3]int) (int, float64, string,[3]int) {
	arr[1] = arr[1] * 100
	return n * 2, f * f, s + "~~~~" ,arr
}
func main() {
	n := 10
	f := 99.9
	s := "hello"
	arr := [3]int{22, 33, 44}
	// 调用函数
	n1, f1, s1 ,arr1:= passValue(n, f, s, arr)
	fmt.Println(n1, f1, s1, arr1)
	// 原来变量的值没有任何影响
	fmt.Println(n, f, s, arr)
}

go run main.go

20 9980.010000000002 hello~~~~ [22 3300 44]
10 99.9 hello [22 33 44]

引用传递示例 :

package main

import "fmt"

// 引用传递1 数据类型本身就时引用类型
func citePass(sli []string, m map[string]string) {
	sli[0] = "AAA"
	m["job"] = "programmer"
}

// 引用传递2 指针形式
func citePass2(n *int) {
	*n = *n +100
}
func main() {
	//定义一个slice
	var sli []string = []string{"aa", "bb", "cc", "dd"}
	//定义一个map
	var person = make(map[string]string)
	person["name"] = "tom"
	person["addr"] = "Provence"
	fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli)
	fmt.Println(person)
	//执行函数citePass之后
	citePass(sli, person)
	fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli)
	fmt.Println(person)
	n := 10
	citePass2(&n)
	fmt.Println(n)
}

go run main.go

sli的len = 4,cap = 4 value = [aa bb cc dd]
map[addr:Provence name:tom]
sli的len = 4,cap = 4 value = [AAA bb cc dd]
map[addr:Provence job:programmer name:tom]
110

3. 函数变量

在Go语言中,函数像值一样拥有类型,可以赋值给变量,传递给函数,从函数中返回

package main

import (
	"fmt"
	"strings"
)

func demo1(n int) int {
	return n * n
}
func demo2(s string) string{
	return strings.ToUpper(s)
}
func main(){
	// 定义一个函数变量
	f := demo1
	fmt.Printf("f type = %T\n",f)
	fmt.Println(f(9))
	// 声明一个函数类型的变量 s
	var s func(string)string
	s = demo2
	fmt.Printf("s type = %T\n s = %s",s,s("hello golang"))
}

go run main.go

f type = func(int) int
81
s type = func(string) string
 s = HELLO GOLANG

函数作为一个数据类型在其他函数中作为参数的数据类型

package main

import "fmt"

func getSum(x, y int) int {
	return x + y
}
// demo1 的参数类型是func()类型
func demo1(f func(int,int)int, a int,b int) int {
	res := f(a,b)
	return a+res

}
func main() {
	// 定义一个函数类型
	f := getSum
	// 函数类型作为实参传递
	res := demo1(f,20,80)
	fmt.Println(res)
}

go run main.go

120

函数作为返回类型

package main

import "fmt"

func getSum(x, y int) int {
	return x + y
}
// 函数作为返回类型
func demo2() (f func(int,int)int){
	return  getSum
}
func main() {
	rf := demo2()
    fmt.Printf("rf type = %T\n",rf)
	fmt.Println(rf(20,100))
}

go run main.go

rf type = func(int, int) int
120

4. 匿名函数

匿名函数就是没有函数名的函数,只有函数体,匿名函数具有函数的一般特性.可以被传递,赋值给变量等

4.1 定义形式1

定义匿名函数时直接使用

package main

import "fmt"

func main() {
	res := func(x,y int) int {
		return x+y
	}(90,10)
	fmt.Println(res)
}

4.2 定义形式2

将匿名函数赋值给一个变量

package main

import "fmt"

func main() {
	sum := func(x,y int) int {
		return x+y
	}
	fmt.Printf("sum type is %T\n",sum)
	fmt.Println(sum(90,90))
}
4.3 定义形式3

定义成全局匿名函数

package main

import "fmt"

var func1 = func(x,y int) int {
	max :=0
	if x>y{
		max = x
	}else if x<y{
		max = y
	}
	return max
}
func main() {
	fmt.Printf("func1 type is %T\n",func1)
	fmt.Println(func1(90,190))
}

4.4 匿名函数作为回调函数
package main

import "fmt"

func demo(s []int,f func(int)int) []int{
	for k,v:=range s{
		 s[k] = f(v)
	}
	return s
}
func main() {
 	res := demo([]int{1,2,3,4,5}, func(i int) int {
		return i*i
	})
 	fmt.Println(res)
}

4.5 匿名函数示例
package main

import (
	"flag"
	"fmt"
)
var skillString = flag.String("skill","","programmer have skill")
func main(){
	flag.Parse()
	var skill = map[string]func(){
		"sing": func() {
			fmt.Println("会唱歌")
		},
		"dance":func(){
			fmt.Println("会跳舞")
		},
		"rap": func() {
			fmt.Println("会rap")
		},
		"code": func() {
			fmt.Println("会写code")
		},
	}
	if f,ok := skill[*skillString];ok{
		f()
	}else{
		fmt.Println("这个真没有...")
	}
}

5. 闭包

闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使己经离开了
自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。因此,简
单的说 :
函数+引用环境=闭包

package main

import "fmt"

func counter(n int) func() int{
	// 返回一个匿名函数,该匿名函数内引用了函数外的变量n,因此匿名函数和n构成了一个闭包
	return func() int {
		n++
		return n
	}
}
func main(){
	f := counter(1)
	fmt.Printf("%p\n",f)
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Printf("%p\n",f)
	f1 := counter(10)
	fmt.Printf("%p\n",f1)
	fmt.Println(f1())
	fmt.Println(f1())
	fmt.Println(f1())
	fmt.Printf("%p\n",f1)

}

go run main.go

0x4932a0
2
3
4
0x4932a0
0x4932a0
11
12
13
0x4932a0

另外一个示例

package main

import (
	"fmt"
	"strings"
)

func demo(s string) func(string) string {
	return func(n string) string {
		if !strings.HasSuffix(n, s) {
			return n + s
		}
		return n
	}
}
func main() {
	d := demo(".png")
	fmt.Println(d("aaa"))
	fmt.Println(d("bbb.png"))
}

go run main.go

aaa.png
bbb.png

6. 函数的可变参数

函数的参数列表没有固定的个数的参数

定义格式如下 :

func 函数名(固定参数列表,V ...T)(返回参数列表){
    函数执行体
}
  • V表示可变参数变量,T表示参数的数据类型, 那么V的类型是 []T,数据类型为T的切片
  • ... 固定语法表示参数个数不确定
  • 可变参数一般放在参数列表最尾部

示例 1:

package main

import (
   "fmt"
)

func demo1(s string, sliString ...string) {
   // 可变参数是数据类型为string的切片
   fmt.Printf("%T\n",sliString)
   for _,v:= range  sliString{
   	fmt.Println(s+" "+v)
   }
}
func demo2(s ...string) string{
   res := ""
   for _,v:= range s{
   	res += v
   }
   return res
}
func main(){
   demo1("hello","ok","nice","yes","good")
   fmt.Println(demo2("Let ","Us ","Go"))
}

go run main.go

[]string
hello ok
hello nice
hello yes
hello good
Let Us Go

示例2: 可变参数函数之间参数传递

package main

import "fmt"

func pri (s ...interface{}){
	for _,v := range s{
		fmt.Println(v)
	}
}
func dump(s ...interface{}){
	pri(s...)
}
func main(){
	dump("golang",2.0)
}

示例3: (获取数据类型)

package main

import (
	"bytes"
	"fmt"
)

func getType(s ...interface{}) string {
	var b bytes.Buffer
	for _, v := range s {
		str := fmt.Sprintf("%v", v)
		var typeS string
		switch v.(type) {
		case bool:
			typeS = "bool"
		case int:
			typeS = "int"
		case string:
			typeS = "string"
		case float64:
			typeS = "float64"
		case []string:
			typeS = "slice"
		default:
			typeS = "unknow"
		}
		b.WriteString("value : ")
		b.WriteString(str)
		b.WriteString(" , type : ")
		b.WriteString(typeS)
		b.WriteString("\n")
	}
	return b.String()
}
func main() {
	res := getType(90, "golang", true, 123.234, []string{"demo"})
	fmt.Println(res)
}

go run mian.go

value : 90 , type : int
value : golang , type : string
value : true , type : bool
value : 123.234 , type : float64
value : [demo] , type : slice

7.延迟调用defer

Go语言中关键字 defer 表示延迟跟在其后面的语句的执行,被延迟的语句按照defer调用的顺序逆向执行,既:最后调用的defer语句,优先执行

  • defer 语句调用遵照先进后出的原则

defer延迟机制,通常讲是为了释放资源,一些常见的创建资源的操作 打开文件,连接数据库,枷锁 等在最后都需要关闭资源句柄,断开连接 释放锁 等操作,这些都可以交给defer来做

7.1 defer的执行顺序
package main

import "fmt"
func demo(s string) {
	fmt.Println(s)
}
func main(){
	fmt.Println("program start")
	defer fmt.Println("one")
	defer demo("two")
	defer func() {
		fmt.Println("three")
	}()
	fmt.Println("program over")
}

go run main.go

program start
program over
three
two
one
7.2 defer 使用示例

示例1 : 文件复制

从A文件复制到B文件,B文件不存在就创建

package main

import (
	"fmt"
	"io"
	"os"
)

func copyFile(dstName, srcName string) (written int64, err error) {
	// open打开一个文件用于读取,读取成功,返回文件对象
	src, err := os.Open(srcName)
	// open错误就直接返回错误
	if err != nil {
		return
	}
	// defer操作关闭文件
	defer src.Close()
	//openFile也是打开文件的函数,可以使用指定项(os.O_RDONLY 只读 os.O_CREATE 不存在就创建新文件 )和指定模式打开文件
	dst, err := os.OpenFile(dstName, os.O_RDONLY|os.O_CREATE, 0644)
	if err != nil {
		return
	}
	// defer操作关闭文件
	defer dst.Close()
	// io.copy()函数将src的数据拷贝到dst,直到src上到达EOF或者错误
	return io.Copy(dst, src)
}
func main() {
	// 调用自定义函数copyFile
	// Args 保存用户命令行参数 []string 类型
	copyFile(os.Args[1], os.Args[2])
	fmt.Println("赋值完成~~")
}

go run main.go "demo.txt" "english.log"

|_main.go
|_demo.txt
|_english.log

示例2 : 获取文件大小

package main

import (
	"fmt"
	"os"
)

func getFileSize(filename string) int64{
	f,err :=os.Open(filename)
	defer f.Close()
	if err != nil{
		return 0
	}
	info ,err := f.Stat()
	if err != nil{
		return 0
	}
	size := info.Size()
	return size

}
func main(){
	// 获取文件的大小(byte)
	fmt.Println(getFileSize(os.Args[1]))
}

8. 错误处理

错误处理是几乎所有编程语言都必须考虑的,Go语言中的错误就是可能出现错误的地方真就出现了错误,我们需要对这样的错误进行处理,这么看错误也是我我们业务代码的一部分,错误处理也将是开发中必须中的必要环节.正确的处理错误,能保障程序更加健壮.

Go语言中引入错误处理的标准模式 ,error 接口,是Go语言内建的接口类型

Go语言中错误处理有如下的特点

  • 可能造成错误的函数,在返回值列表中应该返回一个错误接口 error 如果函数调用成功,错误接口返回的是nil
  • 函数在调用之后需要检查错误,如果有错误,需要进行错误处理
package main

import (
	"fmt"
	"os"
)

func getFileSize(name string) (size int64, err error) {
	size = 0
	f, err := os.Open(name)
	if err != nil {
		return size, err
	}
	info, err := f.Stat()
	if err != nil {
		return size, err
	}
	size = info.Size()
	return size, nil
}
func main() {
	// aa.log 文件不存在
	s, e := getFileSize("aa.log")
	if e != nil {
		fmt.Println(e)
	} else {
		fmt.Println(s)
	}
	fmt.Println("go on ...")
}

go run main.go

open aa.log: The system cannot find the file specified.
go on ...
8.1 自定义错误

Go语言中支持自定义错误,使用 errors.Newpanic 内置函数

  • errors.New("错误说明") 返回一个error类型的值,表示一个错误
  • panic() 内置函数 可以接收任意类型的值,作为参数,输出错误信息,且退出程序
package main

import (
	"errors"
	"fmt"
)

func readConfig(name string) (err error) {
	if name == "config.ini" {
		return nil
	} else {
		return errors.New("读取配置文件错误..")
	}
}
func test() {
	err := readConfig("config2.ini")
	if err != nil {
        // 输出错误信息,并退出程序
		panic(err)
	}
	fmt.Println("test() go on ..")
}
func main() {
	test()
	fmt.Println("client process go on ...")
}

go run main.go

panic: 读取配置文件错误..

goroutine 1 [running]:
main.test()
	/Go/src/GoNote/chapter6/panic-operate/main.go:18 +0x79
main.main()
	/Go/src/GoNote/chapter6/panic-operate/main.go:23 +0x29
8.2 异常的处理

Go语言程序有些错误只能在运行时检查,像数组访问越界,空指针引用等,这些在运行会引起panic异常,一旦发生panic异常,程序就会中断执行,并输出日志,日志包括panic value和函数调用的堆栈跟踪信息

Go语言中处理异常的关键函数是 defer panic recover

简单描述是 : Go语言在运行的时候抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常的处理掉

8.2.1 触发panic

panic 触发一般有三种

  1. 手动触发
  2. 主动触发
  3. 延迟触发

示例 : 手动触发panic

package main

import "fmt"

func main(){
	fmt.Println("program start ...")
	panic("手动触发panic")
	fmt.Println("program end ...")
}

go run main.go

panic: 手动触发panic

goroutine 1 [running]:
main.main()
	E:/Go/src/GoNote/chapter6/demo2/main/main.go:7 +0x9d

示例 : 自动触发panic

package main

import "fmt"

func main() {
	defer fmt.Println("")
	var arr = [...]string{"zero", "one", "two", "three", "four"}
	length := len(arr)
	for i := 0; i <= length; i++ {
		fmt.Println(arr[i])
	}
}

go run main.go

zero
one
two
three
four

panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
	E:/Go/src/GoNote/chapter6/demo3/main/main.go:10 +0x177

示例 : panic 在defer之后执行

package main

import (
	"fmt"
	"time"
)

func demo1() {
	defer func() {
		fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
	}()
}

func main() {
	demo1()
	defer fmt.Println("first ...")
	defer fmt.Println("second ...")
	panic("really over")
}

go run main.go

2019-08-24 16:25:06
second ...
first ...
panic: really over

goroutine 1 [running]:
main.main()
	E:/Go/src/GoNote/chapter6/demo4/main/main.go:18 +0xfd
8.2.2 recover 捕获异常

无论是运行时抛出的panic 还是其他形式的panic,我们都可以配合defer和recover实现异常的捕获和恢复,让程序继续运行下去

本质上Go语言中没有异常系统,使用panic和recover是模拟了其他编程语言中的应对异常的机制

package main

import (
	"fmt"
	"runtime"
)

func divZero(){
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}
func pointCite(){
	var s *string
	*s = "hello world"
}
func Run(f func()){
	defer func() {
		err := recover()
		switch err.(type) {
		case runtime.Error:
			fmt.Println("runtime error : ",err)
		default:
			fmt.Println(err)
		}
	}()
	f()
}
func main(){
	fmt.Println("start===>")
	Run(divZero)
	fmt.Println("go on===>")
	Run(pointCite)
	fmt.Println("end===>")

}

go run main.go

start===>
runtime error :  runtime error: integer divide by zero
go on===>
runtime error :  runtime error: invalid memory address or nil pointer dereference
end===>

panic 和 recover 的关系

  • 有panic 没有recover 程序直接中断
  • 有panic 和recover捕获,程序不会中断,从报panic的点退出后,程序继续执行后面的流程

你可能感兴趣的:(Golang,Go,Golang,函数,defer,错误处理)