Go语言基础、实战 -- 2. 函数、变参函数和包

一、函数

    1. 函数语法

        func FuncName(ParamName Type) ReturnType ,如下:

func FuncName(param int) int {
    return 1
}

        如果有多个相同类型的参数,不必每个变量后面都写类型,可以写在一起,如下:

func FuncName(price, volume int) int {
    return price*volume
}

        Go允许函数有两个返回值,如下:

func GetPrice() (string, int) {
    return "name", 555
}

        具名返回值:可以给函数的返回值指定名字,如果指定了,则视为在该函数的第一行定义了该名字的变量,而在返回时

                              无需再带变量名或者常量值, 直接return即可。如下:

func GetInfo(width, height int) (area, perimeter int) {
    area = width*height                //具名返回值,无需再声明此变量,直接使用
    perimeter = (height + width) * 2
    return
}

    2. 函数类型

            在Go语言中,函数也是一种数据类型,我们可以通过type关键字来定义它,它的类型就是所有拥有相同参数,相同返

        回值的函数的类型。

            我们可以为一个函数声明一种类型,然后用这个类型去声明变量;也可以直接不声明函数类型,直接用速记声明去声

        明变量。利用Go的函数类型,我们可以很简单的就实现回调函数,如下:

type CB func(info string)

func Show(info string) {
	fmt.Printf("[Show]: [%s]\n", info)
}

func GetTitle(year int, name string) string {
	return fmt.Sprintf("[%d NBA Final MVP]: [%s]", year, name)
}

func main() {
	var cb CB                            //先声明函数类型,再用这个类型去声明变量
	cb = Show
	cb("This is a Callback")

	cb2 := GetTitle                      //直接用速记声明来声明变量
	title := cb2(2016, "LeBron James")
	fmt.Println(title)
}

    3. 匿名函数

            在Go语言中,我们不给函数写名字,这样就叫做匿名函数,注意:此时匿名只能被执行,不能在别处被调用,所以匿

        名函数应该写在函数应该被执行的地方,而不是在函数声明的地方。

            而在实际的使用中,匿名函数多半都和defer、go等搭配使用,只是单单为了写一个匿名函数没有实际的意义。

func main() {
    fmt.Println("Anonymous Function Test")

    func(info string) {
        fmt.Printf("[Info]: [%s]\n", info)
    }("This is a Anonymous Function")
}

        匿名函数写法如上,需注意以下几点:

            -- func后直接写参数列表,不需要再写函数名,如果没有参数,中括号内为空

            -- 参数列表后的花括号所包含的为函数体,在里面写函数的实现

            -- 函数体后一定要再跟上一对中括号,这是表示传入的参数 (如果参数列表为空,这里也为空,但是一定要有中括号)

    4. defer调用

        Go语言为我们提供了defer关键字,它的作用是延迟一个函数或者方法的执行:

            -- defer后面只能是函数调用语句

            -- defer的作用是将defer后的函数调用延迟到本函数结束前执行

            -- defer语句只能出现在函数或方法的内部

            -- 当一个函数内有多个defer语句,它们会以LIFO(后进先出)的顺序执行

        下面是一个defer与匿名函数结合、多defer执行顺序的例子:

func main() {
	// defer + lambda
	defer func(info, info2 string) {                            //后被执行
		fmt.Printf("[%s]: [%s]\n", info, info2)
	}("Second defer", "This is a lambda")
	
	defer fmt.Println("[First defer] [I'm the First defer]")    //先被执行
}

            实际应用中,defer多用来做成对的操作,打开、关闭;连接、断开;加锁、释放锁等等。

            比如在一个函数的开始,打开了一个网络连接,在函数结束时应该要关闭连接来释放资源,而函数内的逻辑又很复

        杂,不知道何时来写关闭的语句,这时我们就可以用defer,在打开连接后,紧接着就写defer+关闭连接的函数调用,

        这样就不用再操心它应该何时关闭了。

    5. 作用域

        1) 局部变量

            在函数体内声明的变量、参数和返回值就是局部变量,作用域只在函数体内。

        2) 全局变量

            在函数体外声明的变量是全局变量(包级别的),可以被属于该包的所有文件使用,大写开头命名的还能被别的包使用。

        3) 同名变量问题

            在不同作用域可以声明同名的变量,访问原则为就近访问:

                -- 如果此作用域声明了此变量,就用该作用域内声明的变量

                -- 如果该作用域没有声明此变量,就访问全局变量

                -- 如果全局变量也没有声明此变量,就会报错

二、变参函数

       变参函数是指接受可变数量参数的函数。

    1. 语法

        由 ...T 表示,T 表示类型,如下:

func IsNumExist(num int, num_list ...int) bool {
    for _, v := range num_list {
        if num == v {
            return true
        }
    }
    return false
}

func main() {
    flag := IsNumExist(1, 2, 3, 4, 5, 6, 7, 8, 9, 1)
    if flag == true {
        fmt.Println("Num Exist")            //此句被执行
    } else {
        fmt.Println("Num Doesn't Exist")
    }
}

        上面的例子中,用到了变长的参数num_list。关于这个for循环,如果不理解先不用管,后面的章节会讲到。 

        特点:1) 只有函数的最后一个参数可以被指定为可变参数

                   2) 如果函数最后一个参数由 ...表示,那么该函数就是变参函数,可以接受任意数量类型为的参数

    2. 传递切片给变参函数

        Go还允许将切片传递给变参函数(切片后面也会讲到,不理解先不用管) ,如下:

func main() {
    num_slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    flag2 := IsNumExist(10, num_slice...)
    if flag2 == true {
        fmt.Println("Num Exist")
    } else {
        fmt.Println("Num Doesn't Exist")    //此句被执行
    }
}

        注意:1) 调用时在切片名后面必须写上 ... ,这样会将切片内的所有元素传递给变参函数,不写会报错

                   2) 只有切片能这样用,数组不行。如果是数组,编译会报错

三、包

    1. 简介

            包用于组织Go的源代码,以获取更好的重要性和可读性(可以理解为C++的头文件,但是没有分.h和.cpp文件这样声明和

        实现分开的写法)。包提供了代码封装的机制从而使得Go代码易于维护。

    2. 包的声明

        包的声明一般写在一个go文件的开头第一行,声明语法为:package pkgName。 如下:

package main

    3. 导入包

        导入包的语法如下:

import (
    "fmt"            // 如果导入了包而没有使用,那么编译会报错
    timer "time"     // 可以给包另起一个名字,之后使用包里的函数和变量都用这个别名
    _ "os"           // 如果导入包了而不没有用到,为避免编译出错,可以用 _
}

        -- 切记,如果导入了某个包,而在函数中又没有用到,那么编译会出错

        -- 可以给包起个别名,如上中,给time包起名timer,之后使用时就要写timer (比如写timer.Second 而不是 time.Second)

        -- 有时候我们导入包只是为了初始化这个包而不使用它,那么可以向第三行那样,在包名前加一个下划线 _

        -- 以上的几个包都是Go提供的,我们还可以自定义包,然后导入

    4.  init函数

        关于init函数,有以下几个特点:

          -- Go中每个包都可以包含一个init函数

          -- init函数不应该有任何参数和返回值,并且在代码中不能显示调用它

          -- init函数的作用是执行初始化任务

          -- 一个包可以有一个或多个init函数 (多个的情况是分别在一个包的多个文件中)

          -- 在一个工程中,不管某个包被多少个文件导入了多少次,它始终只会初始化一次

    5. 包的初始化顺序

        1) 被导入的包最先初始化

        2) 其次是包级别的变量 (可以理解成包的全局变量) 被初始化

        3) 最后是init函数被调用 (如果有多个init函数,调用顺序为编译器解析它们的顺序)

    6. 创建自定义包

        1) 创建包的目录

            首先要创建一个目录,注意:包名必须与此目录名一致,所以这个目录名就是你要创建的包名

        2) 创建文件

            然后在上面创建的目录下创建go文件,可以是一个,可以是多个,它们的包名都一样,与目录名一致

            如果一个包有多个文件,那么这几个文件中声明的包级别的变量和函数不能重名

        3) 写文件

            在go文件中实现相关的函数和变量,如下:

            

        4) 包的封装性

                在Go语言中,任何以大写字母开头的变量名、函数名,都是被导出的名字(exported name),只有被导出的名字才能被

            其它包访问和使用。

                利用这一特性,我们可以将我们希望暴露给外部的函数和变量的名字以大写字母开头,而不希望被外部的包调调用和

            使用的,我们可以用小写字母开头。

        5) 导入自定义包

            语法:import path (path为相对于GOPATH/src的相对路径)。如下:

            

            如上图,导入了GOPATH/src/Gogogo/19_grpc目录下的myProto包 :

            

发布了16 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/zhounixing/article/details/103243993