跳转至

类型系统

内置类型

内置类型分为了两大类:

  • 值类型: 包括 bool, int 系列, uint 系列, float 系列, string, complex (复数) 以及 array (固定长度数组).
  • 引用类型 (指针类型): 包括 slice (切片), map (映射) 和 chan (管道). 这些是Go中非常重要且常用的数据结构.

内置函数

这些函数无需导入任何包即可直接使用, 例如:

  • append: 向数组或切片追加元素.
  • lencap: 分别用于获取长度和容量.
  • makenew: 都用于内存分配. make 专用于 slice, map, chan 的初始化, 返回的是类型本身; new 用于分配值类型的内存, 返回的是指向该类型的指针.
  • panicrecover: 用于Go语言的异常处理机制.
  • delete: 用于从 map 中删除键值对.
  • close: 用于关闭 chan.
  • copy: 用于复制切片.

内置 error 接口

error 是一个内置的接口类型, 它只有一个方法: Error() string. 任何实现了这个方法的类型, 都可以被当作一个错误类型来处理. 这是Go语言中处理错误的惯用方式.

_

_主要有两种用法:

  1. import 中使用:

    • import 一个包时, 如果前面加上下划线, 如 import _ "package_name", 那么 Go 只会执行该包的 init() 函数, 而不会将该包的其他函数或变量引入当前作用域.
    • 这种用法通常用于某些只需要其副作用(side effect)的包, 例如注册数据库驱动.
  2. 在代码中作为占位符:

    • 当一个函数返回多个值, 但你只关心其中一部分时, 可以用下划线来忽略不关心的返回值.
    • 例如, os.Open() 函数返回一个文件对象和一个错误. 如果你不关心错误处理(虽然通常不推荐这样做), 可以写成 file, _ := os.Open("filename.txt").
    • 这样做可以避免编译器因为定义了未使用变量而报错, 同时也能让代码更清晰.

变量和常量

变量

  1. 变量的意义: 程序运行时的数据都保存在内存中. 为了方便地操作这些数据, 我们使用"变量名"来关联一块内存地址, 而不是直接使用复杂的内存地址.
  2. 变量类型: Go 语言是静态类型语言, 每个变量都有明确的类型, 如整型 (int), 字符串 (string), 布尔型 (bool) 等.
  3. 变量声明:
    • 标准声明: 使用 var 关键字, 格式为 var 变量名 变量类型.
      var name string
      var age int
      
    • 批量声明: 当有多个变量需要声明时, 可以使用 var 加上括号 () 的形式.
      var (
          a string
          b int
      )
      
    • 变量初始化: 声明变量时可以同时赋值.
      • 标准格式: var 变量名 类型 = 表达式 (例如: var name string = "Go语言")
      • 类型推导: 如果提供了初始值, Go 编译器可以自动推断变量类型, 所以可以省略类型. (例如: var name = "Go语言")
      • 短变量声明: 在函数内部, 可以使用 := 这种更简洁的方式来声明并初始化变量. (例如: name := "Go语言"). 注意: 这种方式只能用在函数内部.
  4. 匿名变量:
    • 使用下划线 _ 作为变量名.
    • 它用于接收一个你不需要使用的值, 比如函数返回多个值, 但你只关心其中一部分.
    • 匿名变量不占用内存, 也不会引起"变量已声明但未使用"的编译错误.

常量

  1. 常量的意义: 常量是指程序运行期间值不能被修改的量.
  2. 声明方式: 使用 const 关键字. 常量在定义时就必须赋值.

    const pi = 3.1415
    // 批量声明
    const (
        e = 2.7182
        n1 = 100
    )
    

    • const 块中, 如果某一行没有赋值, 它会默认使用上一行的值.
  3. iota 常量计数器:

    • iota 是 Go 语言的一个特殊常量, 可以看作是一个const 块中自动递增的行索引.
    • 每当 const 关键字出现时, iota 的值会被重置为 0.
    • const 块中, 每新增一行常量声明, iota 的值就会加 1.
    • iota 非常适合用来定义枚举值或者一组有规律递增的常量, 例如定义文件大小单位 (KB, MB, GB...).

作用域

Go 语言中的作用域规则非常清晰, 主要分为以下几种:

局部作用域

这是最常见的作用域.

  • 定义: 在函数内部或者代码块(Block)内部声明的变量, 就属于局部作用域. 代码块通常指由花括号 {} 包围的区域, 例如 if, for, switch 语句等.
  • 生命周期: 它的生命周期从声明的那一刻开始, 到它所在的函数或代码块执行结束时为止.
  • 特点:
    • 只能在它被声明的函数或代码块内部访问.
    • := 短变量声明只能用在局部作用域中.

示例代码:

package main

import "fmt"

func main() { // main 函数的开始
    // a 是 main 函数的局部变量
    a := 10
    fmt.Println("在 main 函数中 a =", a) // 输出: 10

    if true { // if 代码块的开始
        // b 是 if 代码块的局部变量
        b := 20
        fmt.Println("在 if 块中 a =", a) // 可以访问外部的 a
        fmt.Println("在 if 块中 b =", b) // 输出: 20
    } // if 代码块的结束, b 在这里被销毁

    // fmt.Println(b) // 这行会编译报错! 因为 b 的作用域只在 if 块内
} // main 函数的结束, a 在这里被销毁

包级作用域

有时我们希望一个变量能在整个包的所有文件中共享, 这时就需要包级作用域.

  • 定义: 在任何函数之外声明的变量, 就属于包级作用域.
  • 生命周期: 从程序开始执行时创建, 直到程序结束时销毁.
  • 特点:
    • 同一个包 (package) 下的所有 .go 文件都可以直接访问它.
    • 声明时必须使用 var 关键字, 不能用 :=.

根据变量名的首字母大小写, 包级变量又分为两种:

包内私有变量

  • 命名: 变量名以小写字母开头.
  • 访问权限: 只能在当前包内部使用, 其他包无法访问.

示例 (mymath/math.go):

package mymath

// pi 是包内私有变量, 只能在 mymath 包中使用
var pi = 3.14159

全局公共变量

  • 命名: 变量名以大写字母开头.
  • 访问权限: 不仅可以在当前包内部使用, 还可以被其他包导入后使用. 这相当于其他语言中的 public 变量.

示例 (mymath/math.go):

package mymath

// Pi 是全局公共变量, 可以被其他包访问
var Pi = 3.14159

在另一个包中如何使用 (main.go):

package main

import (
    "fmt"
    "your_module_name/mymath" // 导入 mymath 包
)

func main() {
    // 通过 "包名.变量名" 的方式访问
    fmt.Println(mymath.Pi) // 正确, 可以访问

    // fmt.Println(mymath.pi) // 编译报错! 无法访问小写字母开头的包级变量
}

变量屏蔽

这是一个非常重要且有时会引起 bug 的概念.

当你在一个内部作用域中声明了一个和外部作用域同名的变量时, 内部的这个变量会"屏蔽"或"隐藏"外部的同名变量. 在该内部作用域中, 你访问的将是新声明的这个变量.

示例代码:

package main

import "fmt"

// 包级变量
var level = "Package"

func main() {
    fmt.Println("Main 开始:", level) // 输出: Package

    // 在 main 函数中声明一个同名变量 level
    level := "Function" // 这里创建了一个新的局部变量 level, 屏蔽了包级的 level
    fmt.Println("Main 内部:", level) // 输出: Function

    if true {
        // 在 if 块中再次声明一个同名变量
        level := "Block" // 又创建了一个新的, 作用域更小的局部变量
        fmt.Println("If 块内部:", level) // 输出: Block
    }

    // 回到 main 函数作用域, if 块内的 level 已销毁
    fmt.Println("Main 结束:", level) // 输出: Function
}

总结

作用域类型 声明位置 声明方式 可见范围
局部作用域 函数或代码块 {} 内部 var:= 仅在当前函数或代码块内
包级作用域 (私有) 任何函数外部 var (小写开头) 仅在当前包 (package) 内
包级作用域 (公共) 任何函数外部 var (大写开头) 在所有包中(需导入)