【Go语言踩坑系列(一)】基本数据类型

声明

本系列文章并不会关注语法使用层面,更关心可能在使用中会出现的问题、以及通过这些问题引起的一些思考。相信这个系列会成为很有价值的文章。

要点

本文只关注Go语言的基本类型:如整型、浮点型、常量相关的内容。字符串、数组和切片等高级类型会在下一篇文章中讲述。

初始化顺序:当前包级别变量 -> 导入外部包的init() -> 当前包内的init() -> main()。通常可将一个包导入但是不使用的方式,初始化某些配置数据。
下面这段代码会运行config包和model包下的init()方法:

import (
    "cmdb-bg/cmd"
    _ "cmdb-bg/config"
    _ "cmdb-bg/model"
)

零值

我们都知道,当我们仅仅声明一个变量、但未对其进行初始化的时候,Go会给每种变量类型赋一个零值:

  • 整型:0
  • 浮点型:0
  • bool型:false
func main() {
    var a int
    var b float64
    var c bool
    fmt.Println(a, b, c) // 0 0 false
}

赋值与类型推断

如果你之前已经使用了”:=”对某个变量进行了声明与初始化,如果你想再次为这个变量进行重新赋值,切记不要加”:”

func main() {
    a := 1
    a := 2
    fmt.Println(a) // 报错:no new variables on left side of :=
    a = 2 // ok
}
func main() {
    var a int = 1
    for a >= 1 {
        a := 2  // 无限循环
        a = 2   // 正常执行
        a = a-2
        fmt.Println(a)
    }
}

Go中可以用如下方式高效交换两个变量的值:

func main() {
    a := 0
    b := 1
    a, b = b, a // 交换,不需要使用临时变量
    fmt.Println(a, b) // 1, 0
}
  • Go的new()返回的是一个地址,而不是值本身:
func main() {
    a := new(int)
    fmt.Println(a) // 0xc000016050
}

if赋值加判断复合语句的作用域:f的作用域会被限制在if大括号所包裹的代码块内。在if的外部并不能使用变量f:

func f1() error {
    if f, err := os.Open("abc"); err != nil {
        return err
    }
    fmt.Println(f) // 编译不通过: undefined: f
}

// 解决:
func f1() error {
    f, err := os.Open("abc")
    if err != nil {
        return err
    }
    f.Close() // ok
}

运算与类型转换

int和int32是不同类型,若要把int当成int32来使用,必须进行强制类型转换。其他类型同理。
类型断言的使用(暂作了解):

func main() {
   // a必须是空接口类型, 任何类型都是interface的实现类,当声明为interface{}时,可以赋值给他任意类型
   var a interface{}
   a = 2;
   // 类型断言会返回两个值
   v, ok := a.(int)
   // 如果变量a是断言的类型,ok为true,v为被断言变量的值。
   // 否则ok为false,v为断言类型的零值
   fmt.Println(v, ok) // 2 true
}

如果进行算术运算之后发生了溢出,那么Go会直接丢掉溢出的高位部分。
所有基本类型的值都是可以比较的(整型、浮点型),其他高级类型的比较,一部分需要遵循一定规则,而一部分高级类型是禁止比较的。
不同数据类型不能直接做运算。不像其他语言,Go语言没有隐式类型转换。要想强制对不同类型做运算,必须进行显式的强制类型转换。转换成同一种类型之后,才能做运算:

func main() {
    var a int8  = 100
    var b int16 = 100
    fmt.Println(a + b) //  invalid operation: a + b (mismatched types int8 and int16)
    fmt.Println(int16(a) + b) // ok
}

在进行强制类型转换时需要注意:当整数值的类型的有效范围由宽变窄时,会截掉一定数量的高位二进制数。与这个类似的还有把一个浮点数类型的值转换为整数类型值时,浮点数类型的小数部分会被全部截断:

func main() {
    var a int16 = 428 //00000001 10101100
    fmt.Println(int8(a)) // -84
    // 截断高8位为:10101100。Go中用二进制补码表示数值。转成原码为为 11010100 即十进制-84
}

浮点数的精度有限,尽量不要做浮点数运算结果的比较:

func main() {
    var f float32 = 16777216 // 1 << 24
    fmt.Println(f == f+1)  // false

    var a float32 = 1.23
    var b float32 = 1.25
    fmt.Println(a-b)   // -0.01999998
}

iota常量让用二进制位做标记更简单了。在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一:

const (
   FlagUp Flags = 1 << iota // 第一种标记
   FlagBroadcast            // 第二种标记
   FlagLoopback             // 第三种标记
   FlagPointToPoint         // 第四种标记
   FlagMulticast            // 第五种标记
)

下篇预告

【Go语言踩坑系列(二)】字符串