第2篇:Go快速上手

内容概要

  • 初识包管理,知道项目中文件和文件、文件和文件夹之间关系。
  • 输出,写代码,在go编译器运行时会在屏幕显示内容。
  • 初识数据类型
    • 整型,数字。例如:1、2、3、4
    • 字符串,表示文本信息。例如:“如家” “锦江之星”
    • 布尔类型,真假。例如: 1>2 、 “如家” == “家”
  • 变量 & 常量,当做是昵称。
  • 输入,让咱们用户输入内容。
  • 条件语句,开发一个猜数次程序,用户输入数字与咱们定义的数字进行比较。

Go语言思想

Less can be more 大道至简,小而蕴真 让事情变得复杂很容易,让事情变得简单才难 深刻的工程文化

1. 初识包管理

关于包管理的说明:

  • 一个文件夹可以称为一个包。
  • 在文件夹(包)中可以创建多个文件,各文件以.go结尾。
  • 在同一个包下的每个为文件中必须指定包名称且相同

重点:关于包的分类

  • main包,如果是main包,则必须写一个main函数,此函数就是项目的入口(main主函数)。 编译生成的就是一个可执行文件。
  • 非main包,用来将代码进行分类,分别放在不同的包和文件中。

注意:

  • 调用其他包的功能,需要先 import 导入,然后再使用;调用自己包中的功能时,直接调用即可(无需导入)

  • 文件中的函数首字母是小写,表示此函数只能被当前包内部文件调用。首字母是大写,则意味着任何包都可以调用。

2. 输出

在终端将想要展示的数据显示出来,例如:欢迎登录、请输入用户名等。。。

  • 内置函数
    • print
    • println
  • fmt包(推荐)
    • fmt.Print
    • fmt.Println

示例:

package main

import "fmt"

func main() {
    // 基于内置函数(不推荐)
    //print("好吃不过饺子 \n")
    //print("好玩不过脑子 \n")
    //println("好吃不过饺子")
    //println("好玩不过脑子")

    // fmt包(推荐)
    //fmt.Print("好吃不过饺子 \n")
    //fmt.Print("好玩不过脑子 \n")
    //fmt.Println("好玩不过脑子")
    //fmt.Println("好玩不过脑子")
    //fmt.Println("我叫", "Michael", "我媳妇", "是个", "....")
    //fmt.Println("我叫Michael我媳妇是个....")

    // fmt包 扩展:格式化输出
    // %s,占位符 "文本"
    // %d,占位符 整数
    // %f,占位符 小数(浮点数)
    // %.2f,占位符 小数(保留小数点后2位,四舍五入)
    // 百分比
    fmt.Printf("我叫%s,我的年龄是%d,我是一名%s,我的爱好是%s\n", "张亚飞", 18, "学生", "篮球")

通用占位符:

格式 含义
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%+v 添加字段名(如结构体)
%#v 相应值的Go语法表示
%% 字面上的百分号,并非值的占位符
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示

3. 注释

  • 单行注释, //
  • 多行注释, /* */
package main

func main(){
    // 这是单行注释
    fmt.Println("hello goer!")
    /*
    这是多行注释
    这是多行注释
    */
}

快键键:

  • windows: ctrl+/

  • mac: control+ ?

4. 声明

声明语句定义了程序的各种实体对象以及部分或全部的属性。Go语言主要有四种类型的声明语句:varconsttypefunc,分别对应变量、常量、类型和函数实体对象的声明。

一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件。每个源文件中以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、变量、常量、函数的声明语句,包一级的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之后才能使用)。例如,下面的例子中声明了一个常量、一个函数和两个变量:

package main

// Boiling prints the boiling point of water.

import "fmt"

const boilingF = 212.0

func main() {
    var f = boilingF
    var c = (f - 32) * 5 / 9
    fmt.Printf("boiling point = %g°F or %g°C\n", f, c)
    // Output:
    // boiling point = 212°F or 100°C
}

其中常量boilingF是在包一级范围声明语句声明的,然后f和c两个变量是在main函数内部声明的声明语句声明的。在包一级声明语句声明的名字可在整个包对应的每个源文件中访问,而不是仅仅在其声明语句所在的源文件中访问。相比之下,局部声明的名字就只能在函数内部很小的范围被访问。

一个函数的声明由一个函数名字、参数列表(由函数的调用者提供参数变量的具体值)、一个可选的返回值列表和包含函数定义的函数体组成。如果函数没有返回值,那么返回值列表是省略的。执行函数从函数的第一个语句开始,依次顺序执行直到遇到return返回语句,如果没有返回语句则是执行到函数末尾,然后返回到函数调用者。

我们已经看到过很多函数声明和函数调用的例子了,在第五章将深入讨论函数的相关细节,这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑,这样它只需要被定义一次,就可以在多个地方多次被使用。在这个例子中,main函数就调用了两次fToC函数,分别使用在局部定义的两个常量作为调用函数的参数。

package main

// Ftoc prints two Fahrenheit-to-Celsius conversions.

import "fmt"

func main() {
    const freezingF, boilingF = 32.0, 212.0
    fmt.Printf("%g°F = %g°C\n", freezingF, fToC(freezingF)) // "32°F = 0°C"
    fmt.Printf("%g°F = %g°C\n", boilingF, fToC(boilingF))   // "212°F = 100°C"
}

func fToC(f float64) float64 {
    return (f - 32) * 5 / 9
}

5. 变量

5.1 变量的来历

程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。可以简单理解为昵称。

5.2 变量类型

变量(Variable)的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

5.3 变量声明

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用。

标准声明

Go语言的变量声明格式为:

var 变量名 变量类型

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号。 举个例子:

var name string
var age int
var isOk bool
批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
    a string
    b int
    c bool
    d float32
)

5.4 变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil

当然我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:

var 变量名 类型 = 表达式

举个例子:

var name string = "pprof.cn"
var sex int = 1

或者一次初始化多个变量

var name, sex string = "pprof.cn", 1

变量初始化的两种方式

  • 声明 + 赋值

    var sd string = "篮球之神Michael"
    fmt.Println(sd)
    
    var age int = 73
    fmt.Println(age)
    
    var flag bool = true
    fmt.Println(flag)
  • 先声明后赋值

    // 声明了一个字符类型变量 sd
    var sd string
    // 给sd变量赋值
    sd = "篮球之神Michael"
    fmt.Println(sd)
类型推导

有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。

var name = "pprof.cn"
var sex = 1

var site, address string = "pprof.cn", 1
变量简写

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

package main

import (
    "fmt"
)
// 全局变量m
var m = 100

func main() {
    n := 10
    m := 200 // 此处声明局部变量m
    fmt.Println(m, n)
}

变量声明和初始化的几种方式

  • 声明+赋值

    var name string = "zhangyafei"
    var name = "zhangyafei"
    name := "zhangyafei"  // 推荐
  • 先声明再赋值

    var name string
    var message string
    var data string
    
    var name,message,data string
    
    name = "zhangyafei"
    message = "中奖了"
    data = "中了5000w"
  • 因式分解,例如:声明5个变量,分别有字符串、整型

    var (
        name   = "zhangyafei"
        age    = 18
        hobby  = "生命在于运动"
        salary = 1000000
        gender string  // 只声明但不赋值,有一个默认: ""
        length int     // 只声明但不赋值,有一个默认: 0
        666 bool     // 只声明但不赋值,有一个默认: false
    )
    fmt.Println(name, age, hobby, salary, gender,length,666)

    扩展:go编译器会认为声明变量不使用 就是耍流氓。

匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
    return 10, "Q1mi"
}
func main() {
    x, _ := foo()
    _, y := foo()
    fmt.Println("x=", x)
    fmt.Println("y=", y)
}

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (在Lua等编程语言里,匿名变量也被叫做哑元变量。)

注意事项:

  • 函数外的每个语句都必须以关键字开始(var、const、func等)。
  • :=不能使用在函数外。
  • _多用于占位,表示忽略值。

5.5 声明变量的意义

  • 编写代码省事

    // 文本,请将文本输出3次:"伤情最是晚凉天,憔悴斯人不堪怜。"
    var message string = "伤情最是晚凉天,憔悴斯人不堪怜。"
    fmt.Println(message)
    fmt.Println(message)
    fmt.Println(message)
  • 存储结果,方便之后使用

    // 存储结果,方便之后使用
    var v1 int = 99
    var v2 int = 33
    var v3 int = v1 + v2
    var v4 int = v1 * v2
    var v5 int = v1 + v2 + v3 + v4
    fmt.Println(v1, v2, v3, v4, v5)
  • 存储用户输入的值

    var name string
    fmt.Scanf("%s", &name) // 用户输入字符串并赋值给name变量
    if name == "Michael" {
        fmt.Println("用户名输入正确")
    } else {
        fmt.Println("用户名输入失败")
    }

5.6 变量名要求

  • 【要求】变量名必须只包含:字母、数字、下划线

    var %1 string,错误
    var $ string,错误
  • 【要求】数字不能开头

    var 1 string  错误
    var 1name string  错误
    var _ string 正确
  • 【要求】不能使用go语言内置的关键字

    break      default       func     interface   select
    case       defer         go       map         struct
    chan       else          goto     package     switch
    const      fallthrough   if       range       type
    continue   for           import   return      var
    var var string = "南通州北通州南北通州通南北"  错误
  • 此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。

    内建常量: true false iota nil
    
    内建类型: int int8 int16 int32 int64
              uint uint8 uint16 uint32 uint64 uintptr
              float32 float64 complex128 complex64
              bool byte rune string error
    
    内建函数: make len cap new append copy close delete
              complex real imag
              panic recover

    这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。

  • 建议

    • 变量名见名知意:name/age/num ; v1、v2、v3
    • 驼峰式命名:myBossName / startDate

练习题:判断下列变量名符不符合规范

var n1 int         // 符合
var data bool    // 符合
var _9 string   // 不符合

5.7 作用域

如果我们定义了大括号,那么在大括号中定义的变量。

  • 不能被上级使用。
  • 可以在同级使用。
  • 可以在子级使用。
package main

import "fmt"

func main() {
    name := "zhangyafei"
    fmt.Println(name)
    if true {
        age := 18
        name := "Michael"
        fmt.Println(age)
        fmt.Println(name)
    }
    fmt.Println(name)
}

全局变量和局部变量

  • 全局变量,未写在函数中的变量称为全局变量;不可以使用v1:=xx方式进行简化;可以基于因式分解方式声明多个变量;项目中寻找变量时最后一环。
  • 局部变量,编写在{}里面的变量;可以使用任意方式简化;可以基于因式分解方式声明多个变量;
package main

import "fmt"

// 全局变量(不能以省略的方式)
var school string = "篮球之神人生教育" // 可以
//var school = "篮球之神人生教育"      // 可以
//school := "篮球之神人生教育"           // 不可以

var (
    v1 = 123
    v2 = "你好"
    v3 int
)

func main() {

    name := "zhangyafei" // 局部变量
    fmt.Println(name)
    if true {
        age := 18      // 局部变量
        name := "Michael" // 局部变量
        fmt.Println(age)
        fmt.Println(name)
    }
    fmt.Println(name)
    fmt.Println(school)
    fmt.Println(v1, v2, v3)
}

5.8 赋值及内存相关

示例1:

name := "zhangyafei"

image-20200601222221725

示例2:

name := "zhangyafei"
nickname := name

image-20200601222404306

注意:这一点与python不同。

package main

import "fmt"

func main() {
    name := "zhangyafei"
    nickname := name

    fmt.Println(name, &name)
    fmt.Println(nickname, &nickname)
}

示例3:

name := "zhangyafei"
nickname := name

name = "Michael"

image-20200601222800118

package main

import "fmt"

func main() {
    name := "zhangyafei"
    nickname := name
    fmt.Println(name, &name)
    fmt.Println(nickname, &nickname)

    name = "Michael"
    fmt.Println(name, &name)
    fmt.Println(nickname, &nickname)
}

注意事项

使用intstringbool这三种数据类型时,如果遇到变量的赋值则会拷贝一份。【值类型】

name := "zhangyafei"
nickname := name

提示:后续在数据类型中会详细再讲解。

6. 常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。

const pi = 3.1415
const e = 2.7182

声明了pie这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

多个常量也可以一起声明:

const (
    pi = 3.1415
    e = 2.7182
)

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:

const (
    n1 = 100
    n2
    n3
)

上面示例中,常量n1、n2、n3的值都是100

常量定义示例

package main

import "fmt"

func main() {
    // 定义变量
    //var name string = "zhangyafei"
    //var name = "zhangyafei"
    name := "zhangyafei"
    name = "Michael"
    fmt.Println(name)

    // 定义常量
    //const age int = 98
    const age = 98
    fmt.Println(age)
}

6.1 因式分解

package main

import "fmt"


func main() {
    // 定义变量
    //var name string = "zhangyafei"
    //var name = "zhangyafei"
    name := "zhangyafei"
    name = "Michael"
    fmt.Println(name)

    // 定义常量
    //const age int = 98
    const age = 98
    fmt.Println(age)

    // 常量因式分解
    const (
        v1 = 123
        v2 = 456
        pi = 9.9
    )
    fmt.Println(v1, v2, pi, gender)
}

6.2 全局

package main

import "fmt"

const Data = 999
const (
    pi     = 3.1415926
    gender = "男"
)

func main() {
    // 定义变量
    //var name string = "zhangyafei"
    //var name = "zhangyafei"
    name := "zhangyafei"
    name = "Michael"
    fmt.Println(name)

    // 定义常量
    //const age int = 98
    const age = 98
    fmt.Println(age)

    // 常量因式分解
    const (
        v1 = 123
        v2 = 456
        //pi = 9.9
    )
    fmt.Println(v1, v2, pi, gender)
}

6.3 iota

iotago语言的常量计数器,只能在常量的表达式中使用。 iotaconst关键字出现时将被重置为0const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

const (
    n1 = iota //0
    n2        //1
    n3        //2
    n4        //3
)

几个常见的iota示例

  • 使用_跳过某些值

    const (
        n1 = iota //0
        n2        //1
        _
        n4        //3
    )
  • iota声明中间插队

    const (
        n1 = iota //0
        n2 = 100  //100
        n3 = iota //2
        n4        //3
    )
    const n5 = iota //0
  • 定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)

    const (
        _  = iota
        KB = 1 << (10 * iota)
        MB = 1 << (10 * iota)
        GB = 1 << (10 * iota)
        TB = 1 << (10 * iota)
        PB = 1 << (10 * iota)
    )
  • 多个iota定义在一行

    const (
        a, b = iota + 1, iota + 2 //1,2
        c, d                      //2,3
        e, f                      //3,4
    )

代码示例

package main

const (
    monday = iota + 1
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday
)

const (
    n1 = iota
    n2
    n3
)

func main() {

    // iota
    // 示例1
    /*
        const (
            v1 = 1
            v2 = 2
            v3 = 3
            v4 = 4
            v5 = 5
        )
        fmt.Println(v1, v2, v3, v4, v5)
    */

    // 示例2
    /*
        const (
            v1 = iota
            v2
            v3
            v4
            v5
        )
        fmt.Println(v1, v2, v3, v4, v5)
    */

    // 示例3
    /*
        const (
            v1 = iota + 2
            v2
            v3
            v4
            v5
        )
        fmt.Println(v1, v2, v3, v4, v5)
    */

    // 示例4:
    /*
        const (
            v1 = iota + 2
            _
            v2
            v3
            v4
            v5
        )
        fmt.Println(v1, v2, v3, v4, v5)
    */
}

7. 输入

让用户输入数据,完成项目交互。

  • fmt.Scan
  • fmt.Scanln
  • fmt.Scanf
package main

import "fmt"

func main() {
    // 示例1:fmt.Scan
    /*
        var name string
        fmt.Println("请输入用户名:")
        fmt.Scan(&name)
        fmt.Printf(name)
    */

    // 示例2:fmt.Scan
    var name string
    var age int

    fmt.Println("请输入用户名:")
    // 当使用Scan时,会提示用户输入
    // 用户输入完成之后,会得到两个值:count,用户输入了几个值;err,用输入错误则是错误信息
    _, err := fmt.Scan(&name, &age)
    if err == nil {
        fmt.Println(name, age)
    } else {
        fmt.Println("用户输入数据错误", err)
    }
    // 特别说明:fmt.Scan 要求输入两个值,必须输入两个,否则他会一直等待。
}
package main

import "fmt"

func main() {
    // 示例1:fmt.Scanln
    /*
        var name string
        fmt.Print("请输入用户名:")
        fmt.Scanln(&name)
        fmt.Printf(name)
    */

    // 示例2:fmt.Scanln

    var name string
    var age int
    fmt.Print("请输入用户名:")
    // 当使用Scanln时,会提示用户输入
    // 用户输入完成之后,会得到两个值:count,用户输入了几个值;err,用输入错误则是错误信息
    count, err := fmt.Scanln(&name, &age)
    fmt.Println(count, err)
    fmt.Println(name, age)

    // 特别说明:fmt.Scanln 等待回车。
}
package main

import "fmt"

func main() {
    var name string
    var age int

    fmt.Print("请输入用户名:")
    _, _ = fmt.Scanf("我叫%s 今年%d 岁", &name, &age)
    fmt.Println(name, age)
}

无法解决的难题?

image-20200602112623611
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    // line,从stdin中读取一行的数据(字节集合 -> 转化成为字符串)
    // reader默认一次能4096个字节(4096/3)
    //    1. 一次性读完,isPrefix=false
    //       2. 先读一部分,isPrefix=true,再去读取isPrefix=false
    line, _, _ := reader.ReadLine()
    data := string(line)
    fmt.Println(data)
}

8. 运算符

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符

8.1 算数运算符

运算符 描述
+ 相加
- 相减
* 相乘
/ 相除
% 求余

注意: ++(自增)和–(自减)在Go语言中是单独的语句,并不是运算符。

8.2 关系运算符

运算符 描述
== 检查两个值是否相等,如果相等返回 True 否则返回 False。
!= 检查两个值是否不相等,如果不相等返回 True 否则返回 False。
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False。
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False。
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

8.3 逻辑运算符

运算符 描述
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。
ll 逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。
! 逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。

8.4 位运算符

位运算符对整数在内存中的二进制位进行操作。

运算符 描述
& 参与运算的两数各对应的二进位相与。(两位均为1才为1)
l 参与运算的两数各对应的二进位相或。(两位有一个为1就为1)
^ 参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。(两位不一样则为1)
<< 左移n位就是乘以2的n次方。“a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0。
>> 右移n位就是除以2的n次方。“a>>b”是把a的各二进位全部右移b位。

8.5 赋值运算符

运算符 描述
= 简单的赋值运算符,将一个表达式的值赋给一个左值
+= 相加后再赋值
-= 相减后再赋值
*= 相乘后再赋值
/= 相除后再赋值
%= 求余后再赋值
<<= 左移后赋值
>>= 右移后赋值
&= 按位与后赋值
l= 按位或后赋值
^= 按位异或后赋值

8.6 运算符优先级

上述所有运算符优先级从高到低总结:算数运算符 > 位运算符 > 比较运算符 > 身份运算符 > 成员运算符 > 逻辑运算符 > 赋值运算符。

9. 下划线

“_”是特殊标识符,用来忽略结果。

9.1. 下划线在import中

在Golang里,import的作用是导入其他package。

   import 下划线(如:import hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import* 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。 示例:

代码结构

src 
|
+--- main.go            
|
+--- hello
        |
        +--- hello.go

main.go

package main

import _ "./hello"

func main() {
    // hello.Print() 
    //编译报错:./main.go:6:5: undefined: hello
}

hello.go

package hello

import "fmt"

func init() {
    fmt.Println("imp-init() come here.")
}

func Print() {
    fmt.Println("Hello!")
}

输出结果:

imp-init() come here.

9.2 下划线在代码中

package main

import (
    "os"
)

func main() {
    buf := make([]byte, 1024)
    f, _ := os.Open("/Users/***/Desktop/text.txt")
    defer f.Close()
    for {
        n, _ := f.Read(buf)
        if n == 0 {
            break    
        }
        os.Stdout.Write(buf[:n])
    }
}

解释1:

下划线意思是忽略这个变量.
比如os.Open,返回值为*os.File,error
普通写法是f,err := os.Open("xxxxxxx")
如果此时不需要知道返回的错误值
就可以用f, _ := os.Open("xxxxxx")
如此则忽略了error变量

解释2:

占位符,意思是那个位置本应赋给某个值,但是咱们不需要这个值。
所以就把该值赋给下划线,意思是丢掉不要。
这样编译器可以更好的优化,任何类型的单个值都可以丢给下划线。
这种情况是占位用的,方法返回两个结果,而你只想要一个结果。
那另一个就用 "_" 占位,而如果用变量的话,不使用,编译器是会报错的。

补充:

import "database/sql"
import _ "github.com/go-sql-driver/mysql"

第二个import就是不直接使用mysql包,只是执行一下这个包的init函数,把mysql的驱动注册到sql包里,然后程序里就可以使用sql包来访问mysql数据库了。

10. init和main函数

10.1 init函数

go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。

有下面的特征:

  • init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
  • 每个包可以拥有多个init函数
  • 包的每个源文件也可以拥有多个init函数
  • 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
  • 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
  • init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

10.2 main函数

Go语言程序的默认入口函数(主函数)func main()
函数体用{}一对括号包裹。

func main(){
//函数体
}

10.3. init函数和main函数的异同

相同点:
两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:
init可以应用于任意包中,且可以重复定义多个。
main函数只能用于main包中,且只能定义一个。

两个函数的执行顺序:

  • 对同一个go文件的init()调用顺序是从上到下的。
  • 对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。
  • 对于不同的package,如果不相互依赖的话,按照main包中”先import的后调用”的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。
  • 如果init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。

文章作者: 张亚飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张亚飞 !
评论
 上一篇
第26篇:闭包 第26篇:闭包
学习目标:掌握闭包相关知识点 在之前的课程中,我们讲到过关于函数的知识,接下来的两篇文章来学函数的两个高阶应用,闭包和装饰器,这两个也属于Python的核心特色,学了这两个高阶功能,我们就可以做更多事情了。本节为闭包的介绍。 一、闭包的定义
2020-12-26
下一篇 
第1篇:Go语言入门及环境搭建 第1篇:Go语言入门及环境搭建
一、Go语言介绍1. Go语言诞生背景最近十年来,C/C++在计算领域没有很好得到发展,并没有新的系统编程语言出现。对开发程度和系统效率在很多情况下不能兼得。要么执行效率高,但低效的开发和编译,如C++;要么执行低效,但拥有有效的编译,如.
2020-12-26
  目录