zoukankan      html  css  js  c++  java
  • Golang基础之函数

    0、前言

    函数存在的意义?

    函数是一段代码的封装
    把一段逻辑抽象出来封装到一个函数中,给它起个名字,每次用到它的时候直接用函数名调用即可
    使用函数能够让代码结构更清晰,更简洁。

    1、函数基本概念

    函数是Go语言里的核心设计,它通过关键字func来声明,它的格式如下:

    func funcName(input1 type1,input2 type2) (output1 type1,output2 type2) {
     	// 这里是处理逻辑代码
      // 返回多个值
      return value1,value2
    }
    
    • 关键字func用来声明一个函数funcName。
    • 函数可以有一个或者多个参数,每个参数后面带有类型,通 过","分隔函数可以返回多个值
    • 返回值声明了两个变量output1和output2,如果你不想声明也可以,就保留两个类型声明。
    • 如果只有一个返回值且不声明返回值变量,那么你可以省略“包括返回值”的括号
    • 如果没有返回值,就直接省略最后的返回信息
    • 如果有返回值,那么必须在函数的外层添加return语句。

    实际应用函数的例子:

    package main
    import "fmt"
    
    // 返回a、b中最大值
    func max(a,b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    
    func main() {
    	x := 3
    	y := 4
    	z := 5
    	max_xy := max(x,y) // 调用函数 max(x,y)
    	max_xz := max(x,z) // 调用函数 max(x,z)
    	fmt.Printf("max(%d,%d) =%d
    ",x,y,max_xy)
    	fmt.Printf("max(%d,%d) =%d
    ",x,z,max_xz)
    }
    

    我们从中看到,max函数有两个参数,它们的类型都是int,那么第 一个变量的类型可以省略(即a,b int,而非a int,b int),默认为离它 最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到 它的返回值就是一个类型,这个就是省略写法。

    没有返回值

    func f1(x int,y int){
    	fmt.Println(x + y) // 直接处理
    }
    

    没有参数没有返回值

    func f2(){
    	fmt.Println("f2")
    }
    

    没有参数但有返回值

    func f3() int{
    	return 3 // 需要变量接收返回值
    }
    

    可变长参数
    可变长参数必须放在函数参数的最后

    func f7(x string,y ...int){
    	fmt.Println(x)
    	fmt.Println(y) // y的类型是切片 []int。能接受多个值	
    }
    
    func main(){
    	f7("下雨了",1,2,3,4,5,6) 
           // 下雨了
           // [1 2 3 4 5 6]
    }
    

    2、函数命名返回值

    命名返回值就相当于在函数中已经声明了一个变量,并且在return的时候不需要再指定这个变量了

    func f4(x int,y int)(ret int){
    	ret = x + y 
    	return 
    }
    

    3、函数多返回值

    package main
    import "fmt"
    
    // 返回a、b中最大值
    func SumAndProduct(A,B int)(int,int){
    	return A+B,A*B
    }
    
    func main() {
    	x := 3
    	y := 4
    	xPLUSy,xTIMESy := SumAndProduct(x,y)
    	fmt.Println(xPLUSy)  // 7
    	fmt.Println(xTIMESy) // 12
    
    }
    

    上面的例子中,我们可以看到函数直接返回了两个参数,当然我们 也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们改 成如下定义,这样返回的时候不用带上变量名,因为直接在函数里面初 始化了。但如果你的函数是导出的(首字母大写),官方建议,最好命 名返回值,因为不命名返回值,虽然使代码更加简洁,但是会造成生成 的文档可读性差。

    func SumAndProduct(A,B int)(add int,Multiplied int){
    	add = A+B
    	Multiplied = A*B
    	return 
    }
    

    4、函数变参

    Go语言函数支持变参。接受变参的函数有不定数量的参数。为了 做到这点,首先需要定义函数使其接受变参。

    arg ...int告诉我们Go语言这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice。

    func myfunc(arg ...int){
    	for _,n := range arg{
    		fmt.Println(n) // 1,2,3,4,5
    	}
    }
    
    func main() {
    	myfunc(1,2,3,4,5)
    }
    

    5、defer

    Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数 中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序 执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇 到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造 成资源泄露等问题。我们打开一个资源操作如下所示。

    func ReadWrite() bool{
    	file.Open("file")
    
    	// 做一些工作
    	if failureX {
    		file.Close()
    		return false
    	}
    	if failureY {
    		file.Close()
    		return false
    	}
    	file.Close()
    	return true
    }
    

    我们看到上面有很多重复的代码,Go语言的defer有效解决了这个 问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在 defer后指定的函数会在函数退出前调用。

    func ReadWrite() bool{
    	file.Open("file")
    	defer file.Close()
    	if failureX {
    		return false
    	}
    	if failureY {
    		return false
    	}
    	return true
    }
    

    6、作用域

    package main
    
    import "fmt"
    // 变量作用域
    
    var x = 100 // 定义一个全局变量
    func f1(){
    	// x := 10
    	name := "jack"
    	// 函数中查找变量的顺序
    	// 1.先在函数内部查找
    	// 2.找不到就往函数的外面查找
    	fmt.Println(x,name)
    }
    
    func main(){
    	f1()
    	// fmt.Println(name) // 函数内部定义的变量只能在函数内部使用
    }
    

    7、匿名函数

    没有名字的函数称为匿名函数,而它通常用于函数内部。

    全局匿名函数

    var f1 = func(x,y int){
    	fmt.Println(x+y)
    }
    
    func main(){
    	f1(10,20)
    }
    

    局部匿名函数
    我们可以将匿名函数定义到函数内部,我们都知道函数内部没有办法声明带名字的函数

    func main(){
    	var f1 = func(x,y int){
    		fmt.Println(x+y)
    	}
    	f1(10,20)
    }
    

    如果只是调用一次的函数,还可以简写成立即执行函数

    func main(){
    	// 如果只是调用一次的函数,还可以简写成立即执行函数
    	func(x,y int){
    		fmt.Println(x+y)
    		fmt.Println("Hello world!")
    	}(100,200)
    }
    

    8、内置函数介绍

    内置函数 描述
    close 主要用来关闭channel
    len 用来求长度,比如string、array、slice、map、channel
    new 用来分配内存,主要用来分配值的类型,比如int、string,返回对应类型的指针
    make 用来分配内存,主要用来分配引用类型,比如chan、map、slice,返回具体的类型
    append 用来追加元素到数组、slice中
    panic和recover 用来做错误处理

    panic/recover
    在Go语言中目前是没有异常机制,但是使用panic/revocer模式来处理。panic可以在任何地方引发,但recover只有在defer调用的函数中有效,首先来看一个例子:

    package main
    
    import "fmt"
    
    // panic 和 recover
    func funcA(){
    	fmt.Println("a")
    }
    
    func funcB(){
    	panic("出现了严重的错误!!!") // 程序奔溃退出
    	fmt.Println("b")
    }
    
    func funcC(){
    	fmt.Println("c")
    }
    
    
    func main(){
    	funcA()
    	funcB()
    	funcC()
    }
    

    输出结果

    a
    panic: 出现了严重的错误!!!
    
    goroutine 1 [running]:
    main.funcB(...)
            /Users/xiongminghao/Documents/golang/GoProjects/src/go_dev/day3/panic_recover/main.go:15
    main.main()
            /Users/xiongminghao/Documents/golang/GoProjects/src/go_dev/day3/panic_recover/main.go:26 +0x98
    exit status 2
    

    程序运行期间funcB中引发了panic导致程序奔溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行

    package main
    
    import "fmt"
    
    // panic 和 recover
    func funcA(){
    	fmt.Println("a")
    }
    
    func funcB(){
    	// 刚刚打开数据库连接
    	defer func(){
    		err := recover()
    		fmt.Println(err)
    		fmt.Println("释放数据库连接...")
    	}()
    	panic("出现了严重的错误!!!") // 程序奔溃退出
    	fmt.Println("b")
    }
    
    func funcC(){
    	fmt.Println("c")
    }
    
    func main(){
    	funcA()
    	funcB()
    	funcC()
    }
    

    输出结果:

    a
    出现了严重的错误!!!
    释放数据库连接...
    c
    

    注意:
    1.recover() 必须搭配defer使用
    2.defer一定要在可能引发panic的语句之前定义

    9、指针类型接受者

    指正类型的接受者由一个结构体的指针组成,由于指针的特性,调用方法时修改接受者的任意成员变量,在方法结束后,修改都是有效的,这种方式就十分接近于其它语言中面向对象中的this或者self,例如我们为Person添加指针类型

    package main
    
    import "fmt"
    
    type person struct{
    	name,gender string
    	age int
    }
    
    func newPerson(name,gender string,age int) *person {
    	return &person{
    		name: name,
    		gender: gender,
    		age: age,
    	}
    }
    
    
    // 使用值接受者:传拷贝进去
    func (p person) guonian(){
    	p.age++ 
    }
    
    // 指针接受者:传内存地址进去
    func (p *person) zhenguonian() {
    	p.age++ 
    }
    
    func (p *person) dream() {
    	fmt.Println("不上班也能挣钱!")
    }
    
    
    func main(){
    	
    	p1 := newPerson("xander","男",20)
    
    	fmt.Println("过年之前",p1.age) // 20
    	p1.zhenguonian()
    	fmt.Println("过年之后",p1.age) // 18
    	p1.dream()
    }
    

    什么时候应该使用指针类型接受者
    1.需要修改接受者的值
    2.接受者是拷贝代价比较大的大对象
    3.保证一致性,如果有某个方法使用了指针接受者,那么其他的方法也应该使用指针接受者

    10、任意类型添加方法

    在Go语言中,接受者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。举个例子:我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加新的方法

    package main
    
    import "fmt"
    
    // 给自定义类型加方法
    // 不能给别的包里面的类型添加方法,只能给自己的包里添加方法
    
    type MyInt int 
    func (m MyInt)hello(){
    	fmt.Println("我是一个int")
    }
    
    func main(){
    	m := MyInt(100)
    	m.hello() // 我是一个int
    }
    
  • 相关阅读:
    浅谈网络语音技术(转)
    常用的XMPP服务器
    双码流 主码流子码流(转)
    C++ 程序员必读书目清单
    Error c3876: function call missing argument list; use '' to create a pointer to member
    C#, C++, Java性能对比
    error LNK2001: unresolved external symbol __imp
    error LNK2005: _DllMain@12 already defined in MSVCRTD.lib
    【转】无缝世界网游服务器架构的设计思路
    VS2010生成exe在别的机子上运行提示“丢失MSVCR100D.dll”
  • 原文地址:https://www.cnblogs.com/jasonminghao/p/12295412.html
Copyright © 2011-2022 走看看