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
    }
    
  • 相关阅读:
    HDU 5835 Danganronpa 贪心
    HDU 5842 Lweb and String 水题
    HDU 5832 A water problem 水题
    Codeforces Beta Round #14 (Div. 2) A. Letter 水题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem K. UTF-8 Decoder 模拟题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem I. Alien Rectangles 数学
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem H. Parallel Worlds 计算几何
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem F. Turning Grille 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem C. Cargo Transportation 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem G. k-palindrome dp
  • 原文地址:https://www.cnblogs.com/jasonminghao/p/12295412.html
Copyright © 2011-2022 走看看