Notes
Go
变量和数据类型

变量与数据类型

变量与常量

变量(Variable)

变量即一段或多段内存空间,用于存储数据。

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

var 变量名 变量类型

例如:

var a int
var b string
var c bool
a = 1
b = "hello"
c = true
var d int = 2
// Go 具有类型推导,可以不指定类型
var d = 2 // int
var e string = "world"
var f bool = false
// 类型推导
var g = 3 // int
// 简短声明
h:= 4 // int
// 避免对已声明的变量进行重复声明
h:= 5 // 报错

输出结果:

fmt.Print("a=%v, b=%v, c=%v\n", a, b, c)
fmt.Print("d=%v, e=%v, f=%v\n", d, e, f)
fmt.Print("g=%v, h=%v\n", g, h)

此处使用了 fmt.Print 函数,%v 为占位符,表示输出变量的值。

当然,可以同时声明多个变量:

var a, b, c int
a, b, c = 1, 2, 3
var d, e, f int = 4, 5, 6
var g, h, i = 7, 8, 9
j, k, l := 10, 11, 12

对于不同类型的变量必须要分开声明,但是 var 关键字可以共享。

var (
    a int = 1
    b string = "hello"
    c bool = true
)

在 Go 中,一个变量只进行了声明而没有赋值,那么它的值就是该类型的零值。以下是 Go 中的一些零值:

  • 整型的零值是 0
  • 浮点型的零值是 0.0
  • 布尔型的零值是 false
  • 字符串的零值是 ""
  • 数组的零值是一个全是零值的数组
  • 接口(interface)、切片(slice)、通道(channel)、字典(map)、指针(pointer)、函数(func)的零值是 nil(空指针)
  • 结构体的零值是每个字段都是零值

常量(Constant)

常量即固定值,不可修改。

Go 语言中的常量声明格式为:

const 常量名 常量类型 = 常量值

例如:

const a int = 1
const b string = "hello"
const c bool = true

还有一种写法

const (
    a int = 1
    b string = "hello"
    c bool = true
)

还可以使用 iota 来简化常量的定义:

const (
    a = iota  // 0
    b // 1
    c // 2
)

iota 是一个特殊的常量,它的值默认为 0,每次使用后自动加 1。第一行使用后,后面行可以省略 iota

iota 计数不会中断

const (
    a = iota  // 0
    b // 1
    c = 100 // 100
    d = iota // 3
    e // 4
)

但是如果在中断后不再使用 iota,则会重新计数:

const (
    a = iota  // 0
    b // 1
    c = 100 // 100
    d // 100, 未显式赋值,将使用最近的赋值
    e = 200 // 200
    f // 200
)

iota 需要谨慎使用,因为如果插入一个值将会产生骨牌效应,例如数据库中一张表使用枚举值表示状态,如果插入一个状态,将会导致后面的状态值全部改变,这将与之前的业务表达不符。例如:

cosnt {
    INIT = iota
    START
    STOP
    PAUSE
    FINISH
}

在数据库中,上述的状态值分别对应 0、1、2、3、4。当某天程序员需要在 STARTSTOP 之间插入一个状态,例如为 RUNNING,那么 STOP 的值将变为 3,PAUSE 的值将变为 4,FINISH 的值将变为 5。这将导致数据库中的状态值全部改变,这是不可接受的。

最佳使用 iota 的场景是在枚举类型中,例如:

const (
    MONDAY = iota
    TUESDAY
    WEDNESDAY
    THURSDAY
    FRIDAY
    SATURDAY
    SUNDAY
)

但是如果我们希望从 1 开始计数,可以使用占位符 _

const (
    _ = iota
    MONDAY
    TUESDAY
    WEDNESDAY
    THURSDAY
    FRIDAY
    SATURDAY
    SUNDAY
)

当然也可以使用 iota+1

const (
    MONDAY = iota + 1
    TUESDAY
    WEDNESDAY
    THURSDAY
    FRIDAY
    SATURDAY
    SUNDAY
)

全局变量/常量

变量通常的作用域为当前代码块,如果想要在整个包中使用,可以使用全局变量。全局变量和函数具有相同的作用域。

全局变量的声明格式为:

var 变量名 变量类型

注意,全局变量的声明不能使用 :=,只能使用 var

全局跨包变量/常量

如果想要在其他包中使用全局变量,需要将变量名首字母大写。

例如:

// demo/note/note.go
package note
 
var Note string = "hello"
// demo/main.go
 
import (
    "demo/note"
    "fmt"
)
 
func main() {
    fmt.Println(note.Note)
}

没有多余的局部变量

Go 语言中,如果声明了一个局部变量,但是没有使用,编译器会报错。

func main() {
    var a int
}

这里声明了一个变量 a,但是没有使用,编译器会报错。这是因为 Go 力求保持代码整洁。

全局变量不会出现这种情况,未使用的全局变量不会报错。

常量也可以不使用,不会报错。这是因为常量在编译器就已经决定,导致的副作用较小。

基本数据类型

Go 中,所有的值类型变量常量都会在声明时被分配空间并赋予默认值。

bit: 位,计算机中最小的数据单位,只有 0 和 1 两种状态。 Byte:字节,计算机中基本的存储单位,1 Byte = 8 bit。

整型

整型分为有符号整型和无符号整型。有符号整型包括 int8、int16、int32、int64 和 int。无符号整型包括 uint8、uint16、uint32、uint64 和 uint。

类型位数取值范围
int88-128 ~ 127
uint880 ~ 255
int1616-32768 ~ 32767
uint16160 ~ 65535
int3232-2147483648 ~ 2147483647
uint32320 ~ 4294967295
int6464-9223372036854775808 ~ 9223372036854775807
uint64640 ~ 18446744073709551615
int32/64与操作系统有关,根据操作系统的位数决定
uint32/64与操作系统有关
👾

为什么有了这么多版本的 int 和 unit 之后,还要设计单独的 int 和 unit 类型呢?

  • 能够避免选择,程序员通常并不关注数据长度的具体大小,如果 32 或者 64 都可以满足需求,那么就可以使用 int 或者 unit。
  • 提升效率。CPU 按照自己的字长(word size)的整数倍来处理数据肯定是效率最高的,所以 int 和 unit 会根据 CPU 的字长来选择 32 或者 64 以提升效率。
var a int8 = 1
var b uint8 = 2
var c int16 = 3
var d int = 4

支持二进制、八进制、十进制、十六进制的表示方式:

var a int = 0b1010 // 二进制
var b int = 0O12 // 八进制
var c int = 10 // 十进制
var d int = 0xA // 十六进制

精度转换:

// 低精度转高精度
var a int8 = 1
var b int16 = int16(a)
var c int32 = int32(b)
var d int64 = int64(c)
var e int = int(d)
// 高精度转低精度
var f int = 1
var g int64 = int64(f)
var h int32 = int32(g)
var i int16 = int16(h)
var j int8 = int8(i)

浮点型

浮点型分为单精度浮点型和双精度浮点型。单精度浮点型为 float32,双精度浮点型为 float64。

类型位数
float3232
float6464
var a float32 = 1.1
var b float64 = 2.2

浮点型将导致精度丢失

在计算机中,整数是能够准确表达的,因为十进制整数转化为二进制是因为所有的整数的基石 1 是能够通过二进制准确表达的,但是浮点数的小数部分利用 2 的幂次方来表示,例如 2 的 -1 次方表示 0.5,2 的 -2 次方表示 0.25,但是 0.1 无法通过有限的二进制数来准确表示,这就导致了浮点数的精度丢失。

浮点数在计算机中表示示意如下:

  • IEEE 32
符号位指数位尾数位
1bit8bit23bit
  • IEEE 64
符号位指数位尾数位
1bit11bit52bit

不同于 Int 和 Uint 类型,Go 语言中没有 float 关键字来声明 32 位的浮点数或者与平台匹配的浮点数,只有 float32 和 float64。这可能是因为浮点数并不能准确表达一个数字,所以在这里程序员需要关注精度,所以需要明确声明。

由于精度缺失,浮点数不能使用 == > < 来比较,而应使用 big 包中的函数。

import "math/big"
 
func main() {
    a := 3.14
    b := 3.14
    result := big.NewFloat(a).Cmp(big.NewFloat(b))
 
    if result == 0 {
        fmt.Println("a == b")
    } else if result == 1 {
        fmt.Println("a > b")
    } else {
        fmt.Println("a < b")
    }
}

字符型

类型位数取值范围
byte8
rune32
var a byte = 'a'
var b rune = 'b'
// 格式化打印字符
fmt.Printf("%c\n", a)
// 直接打印字符
fmt.Println(b)
// 强制转换为字符串
fmt.Println(string(a))

这里的 byterune 都是别名,实际上是 uint8int32 来存储的。

布尔型

类型位数
bool1
var a bool = true
var b bool = false

字符串

字符串是一串固定长度的字符连接起来的字符序列。

var a string = "hello"
var b string = "world"

字符串在内存中存储时,实际上是一个字节数组 []byte,每个字符占用一个字节。字符串的长度是字符的个数,而不是字节数。

字符串的长度可以通过 len 函数获取,但是其用于获得变量的实际的存储空间,即底层字节数组的长度。

package main
 
import {
  "fmt"
}
 
func main{
  s:= "字符串"
 
  len:= len(s)
  fmt.Println(len) // 9
 
  for i:= 0; i < len; i++ {
    fmt.Printf("%c\n", s[i])
  }
}

执行结果:

9
E5 AD 97 E7 AC A6 E4 B8 B2

在这里打印的“字符串”的长度为 9,这对应每个汉字使用 3 个字节来存储。打印出来的内容也不是字符本身,而是字符的 UTF-8 编码。

rune 类型

如果我们希望以自然语言的视角来处理字符串,我们需要使用 rune 类型。

package main
 
import {
  "fmt"
}
 
func main{
  s:= "字符串"
 
  fmt.Println(utf8.RuneCountInString(s)) // 3
 
  for _, r:= range s {
      fmt.Printf("%T, %X\n", r, r)
  }
}

打印结果:

3
int32, 5B57
int32, 7B26
int32, 4E32

复数

类型位数取值范围
complex6464
complex128128
var a complex64 = 1 + 2i
var b complex128 = 3 + 4i

辨认数据类型

Go 语言中可以使用 reflect 包来辨认数据类型。

import "reflect"
 
func main() {
    var a int = 1
    var b string = "hello"
    var c bool = true
    var d float32 = 1.1
    var e float64 = 2.2
    var f byte = 'a'
    var g rune = 'b'
    var h complex64 = 1 + 2i
    var i complex128 = 3 + 4i
 
    fmt.Println(reflect.TypeOf(a))
    fmt.Println(reflect.TypeOf(b))
    fmt.Println(reflect.TypeOf(c))
    fmt.Println(reflect.TypeOf(d))
    fmt.Println(reflect.TypeOf(e))
    fmt.Println(reflect.TypeOf(f))
    fmt.Println(reflect.TypeOf(g))
    fmt.Println(reflect.TypeOf(h))
    fmt.Println(reflect.TypeOf(i))
}