Golang 基础-包、变量和函数(第一章)

1. 包

1. main 函数和 main 包

所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。

2. 导入包和导出名字

  • 按照约定,包名与导入路径的最后一个元素一致。比如使用 “math/rand” 包时,用 rand 来调用
  • 导入可以直接导入或者使用圆括号分组导入,圆括号内每个包占一行
  • 任何以大写字母开头的变量或者函数,都是被导出的名字,其它包只能访问被导出的函数和变量,任何未导出的名字在该包外均无法访问
  • 导入了包,却不在代码中使用它,在 Go 中是非法的,此时编译器是会报错
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main

    import "math/rand" // 直接导入
    import ( // 分组导入
    "fmt"
    "math"
    )

    func main() {
    fmt.Println(math.pi) // 报错 :./prog.go:9:14: cannot refer to unexported name math.pi
    // ./prog.go:9:14: undefined: math.pi
    } // 正确的是 math.Pi

3. 自定义包

属于某一个包的源文件都应该放置于一个单独命名的文件夹里。按照 Go 的惯例,应该用包名命名该文件夹。

4. 初始化函数

所有包都可以包含一个 init 函数。

  • 该函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它
  • 该函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性

包的初始化顺序:

  • 首先初始化被导入的包,按照顺序全部导入
  • 然后初始化包级别(Package Level)的变量
  • 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。
  • 调用 main 函数

5. 特殊操作

  • 别名:可以重新命名使用包时候的名字
  • 空白操作:由于导入不使用是非法的,这种情况就可以使用空白标识符 _ 屏蔽报错。
  • 点操作:表示导入包之后可以省略包前缀使用它
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package main 

    import (
    sql "database/sql" // 可以使用 sql 来代替包前缀
    _ "geometry/rectangle" // 需要初始化,但不使用
    . "fmt" // 可以直接调用
    )

    var _ = rectangle.Area // 错误屏蔽器

    func main() {
    sql.open()
    Println("hello world")
    }

2. 变量

1. 声明

  • 通用语法:var 变量名 变量类型,语句用于声明一个变量列表,类型在最后,可出现在包和函数级别。
  • 变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型。
  • 在函数中,简短声明语句 := 可在类型明确的地方代替 var 声明,不能在函数外使用。
    • := 操作符左边的所有变量必须都有初始值
    • := 操作符的左边至少有一个变量是尚未声明
  • const一般用来声明常量,常量不能用 := 语法声明。
    • 常量不允许重新赋值
    • iota 是常量计数器,只能在常量的表达式中使用,在const关键字出现时将被重置为0(const 内部的第一行之前),const中每新增一行常量声明将使iota计数一次
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      // 声明单个变量
      var aa int // 直接声明,不赋初始值则其为零值
      var bb = 100 // 类型推断

      // 声明多个变量
      var i, j int = 1, 2 // 声明多个变量并初始化
      var x, y = 100, "hello"
      var (
      vv int = 10
      jj string = "world"
      )

      // 声明常量
      const name = "gp" // 单个常量
      const ( // 可以用来定义枚举
      a = iota // 0
      b // 1
      c // 2
      )
      const (
      Apple, Banana = iota + 1, iota + 2 // 0+1=1, 0+2=2
      Cherimoya, Durian // 1+1=2, 1+2=3
      Elderberry, Fig // 2+1=3, 2+2=4

      Blackberry, Blueberry // 4+1=5, 4+2=6
      Cherry, Lemon = iota*1,iota*10 // 5*1=5, 5*10=50
      Pear, Peach // 6*1=6, 6*10=60
      )

      func main() {
      var c, python = true, false
      fmt.Println(a, b, i, j, x, y, vv, jj, c, python)

      // 简短声明
      name, age := "naveen" // 报错 age 没有被赋值。要求 := 操作符左边的所有变量必须都有初始值
      a1, b1 := 20, 30 // 声明变量a和b
      b1, c1 := 40, 50 // b已经声明,但c尚未声明。要求 := 操作符的左边至少有一个变量是尚未声明
      a1, b1 := 60, 70 // 报错,没有尚未声明的变量
      }

2. 基本类型

  • bool
  • string
  • 数字类型
    • 有符号整型:int8, int16, int32, int64, int
      • int8 :8 位有符号整型,-128~127
      • int16:16 位有符号整型,-32768~32767
      • int32:32 位有符号整型,-2147483648~2147483647
      • int64:64 位有符号整型,-9223372036854775808~9223372036854775807
      • int:无特殊需求,通常使用它,根据不同的底层平台,表示 32 或 64 位有符号整型
    • 无符号整型:uint8, uint16, uint32, uint64, uint
      • uint8 :8 位无符号整型,0~255
      • uint16:16 位无符号整型,0~65535
      • uint32:32 位无符号整型,0~4294967295
      • uint64:64 位无符号整型,0~18446744073709551615
      • uint:无特殊需求,通常使用它,根据不同的底层平台,表示 32 或 64 位无符号整型
    • 浮点型:float32, float64,32 位浮点数和 64 位浮点数
    • 复数类型:complex64, complex128,实部和虚部都是 float32 和 float64 类型的的复数
    • byte:uint8 的别名。
    • rune:int32 的别名,表示一个 Unicode 码点

3. 注

3.1. 零值(Zero Value)

没有明确初始值的变量声明会被赋予它们的零值。数值类型为 0,布尔类型为 false,字符串为 “”(空字符串)。

3.2. 同类型转换

golang 没有自动类型提升或类型转换表达式,可以用 T(v) 将值 v 转换为类型 T,主要在同一基础类型不同精度范围转换时使用。

3. 函数

1. 语法

  • 通用语法:func 函数名(参数 类型) 返回值类型 { }
  • 无参无返回值可省略参数列表和返回值 func 函数名() { }
  • 函数有多个返回值,那么返回值必须用 () 括起来
  • _ 在 Go 中被用作空白符,可以用作表示任何类型的任何值
  • 可变参数函数:如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最后一个参数。

2. 函数参数

  • 类型在参数之后 x int, y string
  • 多个同类参数,可只留最后一个。当连续两个或多个函数的已命名形参类型相同时,可只在最后写一个类型,即x int, y int 等于 x, y int

3. 函数特点

3.1. 返回值可以是任意数量

任意数量返回值,可分为匿名得和有形参名的,当返回值被命名,它们会被视作定义在函数顶部的局部变量,一般仅用在短函数中,否则会影响阅读性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 无形参名
func swap(x, y string) (string, string) {
return y, x
}

// 有形参名
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}

// 空白符
func rectProps(length, width float64) (float64, float64) {
var area = length * width
var perimeter = (length + width) * 2
return area, perimeter
}

func main() {
a, b := swap("world", "hello") // a="hello", b="world"
fmt.Println(split(9)) // 结果:4 5
area, _ := rectProps(10.8, 5.6) // 返回值周长被丢弃
}

3.2. defer

含有 defer 语句的函数,会在该函数将要返回之前,调用该语句后定义的函数。类似于 java 的 finally 内的语句。其又称为延迟函数。

  • 并非在调用延迟函数的时候才确定实参,而是当执行 defer 语句的时候,就会对延迟函数的实参进行求值
  • 当一个函数内多次调用 defer 时,Go 会把 defer 调用放入到一个栈中,随后按照后进先出(Last In First Out, LIFO)的顺序执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func printA(a int) {  
    fmt.Println("value of a in deferred function", a)
    }
    func main() {
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a) // value of a before deferred function call 10
    // value of a in deferred function 5
    }

4. 头等函数

头等函数机制,指该语言可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。

4.1. 匿名函数

两种调用方式:

  • 把函数赋值给变量
  • 在定义之后使用 () 立即调用,因此只能使用一次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func main() {
    a := func() { // 把函数赋值给变量
    fmt.Println("hello world first class function")
    }
    a()

    func(n string) { // 定义之后立即调用
    fmt.Println("Welcome", n)
    }("Gophers")
    }

4.2. 自定义的函数类型

类似自定义结构体,可以定义自己的函数类型

1
2
3
4
5
6
7
8
9
type add func(a int, b int) int             // 自定义函数类型 add 

func main() {
var a add = func(a int, b int) int { // 给 add 类型的变量,赋值一个符合 add 类型签名的函数
return a + b
}
s := a(5, 6) // 调用该函数
fmt.Println("Sum", s)
}

4.3. 高阶函数

高阶函数可以定义为,满足下列条件之一的函数:

  • 接收一个或多个函数作为参数
  • 返回值是一个函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 函数作为参数
    func simple(a func(a, b int) int) {
    fmt.Println(a(60, 7))
    }

    func main() {
    f := func(a, b int) int {
    return a + b
    }
    simple(f) // 将定义的匿名函数 f 作为参数传入
    }

    // 函数作为返回值
    func simple() func(a, b int) int {
    f := func(a, b int) int {
    return a + b
    }
    return f
    }

    func main() {
    s := simple() // 调用 simple 函数,并把返回函数赋值给了 s
    fmt.Println(s(60, 7))
    }

4.4. 闭包

闭包(Closure)是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func appendStr() func(string) string {  
t := "Hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}

func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("World")) // Hello World
fmt.Println(b("Everyone")) // Hello Everyone

fmt.Println(a("Gopher")) // Hello World Gopher
fmt.Println(b("!")) // Hello Everyone !
}