08 常量

常量

一 常量介绍

​ 变量指的是值可以被改的量,常量指的则是值不可以被改变的量,在Go语言中,常量的值在编译期间就已经确定好了,且在程序运行过程中不可改变。

​ 常量的声明语句如下,定义了常量的名字,和变量的声明语法类似

const 名字 类型 = 初始化表达式

// 例如
const x int = 10
x = 111 // 报错:cannot assign to x

​ 虽然常量和变量很相似,但毕竟是两种东西,所以不要硬把变量的东西往常量上套,需要注意的是

  • 1、初始化表达式可以是字面量or任意表达式

  • 2、类型是可有可无的

  • 3、常量声明完之后不用也不会报错

  • 4、我们可以取变量的地址,但是我们无法取常量的地址,原因如下

    取地址无非两种用途:
    ​ 1、根据地址修改值
    ​ 2、引用传递
    因为常量是不可以被改变的,所以在底层设计上不允许我们对常量取地址,因为如果你能取到常量的地址,岂不是顺着地址就给把它内存空间中的值可以改掉了,所以设计者干脆给我们屏蔽掉了对常量的取地址操作,因为没啥意义,所以说常量值的传递也都是值传递,而非引用传递。

二 字面量

​ 字面量(literal)也成为字面值or字面常量,就是”字面“意思上的量、是指程序中”硬编码“的量,通俗地讲,字面量就是以人类可读形式表示的固定值,有以下几种

111
3.1
3.2+12i
true
"egon" 

​ 注意注意注意!!!

​ ”字面“与”硬编码“都是在提醒你,它只是它字面的样子,不要自己意淫它的类型,例如111这个字面量就是一系列阿拉伯数字,你可能会说它难道不是int类型吗,当然不是喽,int类型是编程语言中才有的概念,而阿拉伯数字等人类自然语言的符号早在编程语言诞生之前就已经有了,即便没有编程语言111、3.1,“hello”这些值也都是存在的,比如,你随便找个人让他看看一眼111他都认识,但你跟他讲int类型,除非他是程序员他才能听懂你在讲什么。也就是说,是编程语言将字面量111与数据类型int这两种概念联系到了一起。

​ 综上所述,字面量与数据类型是两件事情,而在有的语言中,却会直接将字面量与特定的类型混杂在一起,这么做是不好的

比如111这一字面量在C语言中会认为是一个int类型,一个long类型的字面量需要写成111l,这其实是与”字面“二字的核心主旨相悖的,”字面“反映出的就应该只是它字面的东西,不应该掺杂类型的概念,因为类型这个东西是另外一件单独的事情,比如111l,这个后缀l就掺杂进了类型的概念,那么此时它的字面便不那么字面,没有那么的单纯。

​ 所以,我们应该将字面值与类型当成两种概念去看,而站在巨人肩膀上的go语言,自然是作出了对应的调整,字面量只是它字面的样子,至于数据类型,那是另外一件事。

​ 一句话:Go语言的字面常量更接近我们自然语言中的常量概念,它们本身是无类型的,在Go语言中,只有下述五种字面量可以赋值给常量

111这种字面量可以被称之为无类型的整数
3.1这种字面量被称之为无类型的浮点数
3.2+12i这种字面量被称之为无类型的复数
true这种字面量被称之为无类型的布尔型
”egon“这种字面量被称之为无类型的字符

//!!! 注意注意注意:数字常量不会分配存储空间,无须像变量那样通过内存寻址来取值,即无法获取地址

​ 字面量可以用于变量中,也可以用于常量中,是不一样的

  • 1、变量是必须有类型的

    =====> 变量是必须有类型的,字面量必须与变量的类型保持一致 <=====
    (1) 如果我们没有为变量指定类型,那么字面常量会被隐式地转换成自己默认的类型
    
    字面量                         默认类型
    字面量为整数        --------->  int类型
    字面量为浮点数       ---------> float64类型
    字面量为复数         ---------> complex128类型
    字面量为true或false  ---------> bool类型
    字面量为"egon"      --------->  string类型
    
    例如
    var x = 1+2i 
    fmt.Printf("%T",x)  // complex128
    
    (2) 如果我们已经为变量指定了类型,那么字面常量会被显式地转换成变量的类型,前提是转换合法,即字面量必须在相应类型的值域范围内。
    
    // 例如3,它在int、 uint、 int32、int64、 float32、 float64、 complex64、 complex128等类型的值域范围内
    var x int = 3 // 变量的类型明确为int,所以字面量3被隐式转换成了int类型
    
    // 例如3.1,他在float32、float64、complex64的值域范围内
    var a int = 3.1 // 报错,转换不合法
    var b float32 = 3.1 // 无类型浮点数3.1被转换成了float32类型
    var c float64 = 3.1 // 无类型浮点数3.1被转换成了float64类型
    var e complex64= 3.1 // 无类型浮点数3.1被转换成了complex64类型
    
    var x float32 = math.Pi // math.Pi也是无类型的浮点数
    var y float64 = math.Pi
    var z complex128 = math.Pi
    
    var f float64 = 3 + 0i // untyped complex -> float64
    f = 2                  // untyped integer -> float64
    f = 1e123              // untyped floating-point -> float64
    f = 'a'                // untyped rune -> float64
    上述语句的转换都是隐式的,相当于下述的显式转换
    var f float64 = float64(3 + 0i)
    f = float64(2)
    f = float64(1e123)
    f = float64('a')
    
    所以,我们可以显式地先将字面量转换成一种类型
    var x = int8(3) // 等号左侧无需指定类型了,如果非要指定,那也必须是int8
    var y = int64(3)
    
    综上,如果要给一个变量明确的类型,我们可以有两种方式
    var i = int8(0) // 方式1:显式地将无类型的常量转化为所需的类型
    var i int8 = 0 // 方式2:给声明的变量指定明确的类型
  • 2、常量可以有类型、也可以没有类型(也只有常量可以没有类型)

    1、字面量用于常量中,如果没有声明类型,那该常量就是无类型的,go语言中只有下述5种无类型常量
    const a = 111 // 无类型整数常量
    const b = 3.1 // 无类型浮点数常量
    const c = 3.2+12i // 无类型复数常量
    const d = true // 无类型布尔常量
    const e = "egon" // 无类型字符常量
    
    编译器为没有明确类型的数字常量(整数、浮点数、复数)提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。如下所示,ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量
    const YiB = 1208925819614629174706176
    const ZiB = 1180591620717411303424
    而且可以像下面常量表达式依然有效
    fmt.Println(YiB/ZiB) // YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的):
    
    2、也可以限定常量的类型,例如
    const f int = 123 // int型常量
    const g float32 = 0.0 // float32型常量
    
    3、而且通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换,比如前面我们提及字面量在变量中的应用
  • 3、无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理:

    const (
        deadbeef = 0xdeadbeef // untyped int with value 3735928559
        a = uint32(deadbeef)  // uint32 with value 3735928559
        b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
        c = float64(deadbeef) // float64 with value 3735928559 (exact)
        //d = int32(deadbeef)   // compile error: constant overflows int32
        //e = float64(1e309)    // compile error: constant overflows float64
        //f = uint(-1)          // compile error: constant underflows uint
    )

三 表达式

常量定义的右值也可以是一个在编译期运算的常量表达式,比如
const mask = 1 << 3
由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达
式,比如试图以如下方式定义常量就会导致编译错误:
const Home = os.GetEnv(“HOME”)
原因很简单, os.GetEnv()只有在运行期才能知道返回结果,在编译期并不能确定,所以无法作为常量定义的右值。

四 声明一组常量

和变量声明一样,可以批量声明多个常量;这比较适合声明一组相关的常量:

const (
    e  = 2.71828182845904523536028747135266249775724709369995957496696763
    pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的。例如:

const (
    a = 1
    b // 用的是前一常量的初始表达式,即b = 1
    c = 2
    d // 用的是前一常量的初始表达式,即d = 2
)

fmt.Println(a, b, c, d) // "1 1 2 2"

如果只是简单地复制右边的常量表达式,其实并没有太实用的价值。但是它可以带来其它的特性,那就是iota常量生成器语法。

五 预定义常量

Go语言预定义了这些常量: true、 false和iota。
iota比较特殊,可以被认为是一个可被编译器修改的常量,在且仅在同一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一

连续两个const关键字声明的iota都为0

const x = iota // 出现了一个const关键字,iota重置为0
const y = iota // 又出现了一个const关键字,iota还是重置为0

fmt.Println(x,y) // 0 0

如果是一const关键字,声明了一组常量,iota会自第一个iota开始依次累加,没新增则一个变量累加一次

const (
    a = 111 // 第一个声明的常量所在的行,此处虽然没有iota,但iota的值被置为0
    b // b的表达式同上,所以为111,此处虽然没有iota,但因为是新增一个常量声明,所以iota的值加1
    c // c的表达式同上,所以为111,此处虽然没有iota,但因为是新增一个常量声明,所以iota的值加1
    d = iota // 此处的iota基于前面的基础加1,所以d为3
    e = 333
    f // 同上为333
    g // 同上为333
)
fmt.Println(a,b,c,d,e,f,g) // 111 111 111 3 333 333 333

const (
u = iota * 42 // u == 0
v float64 = iota * 42 // v == 42.0
w = iota * 42 // w == 84
)

const ( 
c0 = iota // c0 == 0
c1 // c1 == 1
c2 // c2 == 2
)

const (
a = 1 <<iota // 1 << iota 第一个常量声明,iota为0,即 1 << 0 相当于2**0,所以a==1
b // 表达式同上为1 << iota iota累加为1,即 1 << 1 相当于2**1,所以a==2
c // 表达式同上为1 << iota iota累加为2,即 1 << 2 相当于2**1,所以a==4
)

我们可以定义一个Weekday命名类型,然后为一周的每天定义了一个常量,从周日0开始。在其它编程语言中,这种类型一般被称为枚举类型。

type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)
// 周一将对应0,周一为1,如此等等。

下面是一个更复杂的例子,每个常量都是1024的幂:

const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424    (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

不过iota常量生成规则也有其局限性。例如,它并不能用于产生1000的幂(KB、MB等),因为Go语言并没有计算幂的运算符。

六 常量的运算

所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof

上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术