运维八一 运维八一
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)

运维八一

运维,运维!
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)
  • Go

    • 前言

    • Go基础知识

    • Go基本语法

      • go变量声明
      • 常量定义
        • 2.常量定义
          • 2.1 Go 原生支持常量的好处
          • 2.2 Go 对常量的创新
          • 2.2.1 无类型常量
          • 2.2.2 隐式转型
          • 2.2.3 实现枚举
          • 2.3 常量定义
      • fmt包
      • init、main函数和包初始化
      • 关键字
      • 命名规范
      • Go代码块与作用域
    • 实战项目:简单web服务

    • 基本数据类型

    • 内置运算符

    • 分支和循环

    • 函数 function

    • 结构体 struct

    • 方法 method

    • 实战项目:跟踪函数调用链

    • 接口 interface

    • 并发 concurrency

    • 指针

    • 实战项目:实现轻量级线程池

    • 实战项目:实现TCP服务器

    • go常用包

    • Gin框架

    • go随记

  • Python

  • Shell

  • Java

  • Vue

  • 前端

  • 编程浅尝
  • Go
  • Go基本语法
lyndon
2022-06-07
目录

常量定义

# 2.常量定义

# 2.1 Go 原生支持常量的好处

Go 原生提供的用 const 关键字定义的常量,整合了 C 语言中宏定义常量、const 修饰的“只读变量”,以及枚举常量这三种形式,并消除了每种形式的不足,使得 Go 常量是类型安全的,而且对编译器优化友好。

# 2.2 Go 对常量的创新

# 2.2.1 无类型常量

Go 语言对类型安全是有严格要求的:==即便两个类型拥有着相同的底层类型,但它们仍然是不同的数据类型,不可以被相互比较或混在一个表达式中进行运算。==

这一要求不仅仅适用于变量,也同样适用于有类型常量(Typed Constant)中:

type myInt int
const n myInt = 13
const m int = n + 5 // 编译器报错:cannot use n + 5 (type myInt) as type int in const initializer

func main() {
    var a int = 5
    fmt.Println(a + n) // 编译器报错:invalid operation: a + n (mismatched types int and myInt)
}

// 方法一:通过将常量 n 显式转型为 int 后才能参与后续运算:
type myInt int
const n myInt = 13
const m int = int(n) + 5  // OK

func main() {
    var a int = 5
    fmt.Println(a + int(n))  // 输出:18
}

// 方法二:使用 Go 中的无类型常量来实现

type myInt int
const n = 13

func main() {
    var a myInt = 5
    fmt.Println(a + n)  // 输出:18
}

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

常量 n 在声明时并没有显式地被赋予类型,在 Go 中,这样的常量就被称为==无类型常量(Untyped Constant)。==

不过,无类型常量也不是说就真的没有类型,它也有自己的默认类型,不过它的默认类型是根据它的初值形式来决定的。像上面代码中的常量 n 的初值为整数形式,所以它的默认类型为 int。

# 2.2.2 隐式转型

隐式转型:对于无类型常量参与的表达式求值,Go 编译器会根据上下文中的类型信息,把无类型常量自动转换为相应的类型后,再参与求值计算,这一转型动作是隐式进行的。

如上示例,Go 编译器会自动将 a+n 这个表达式中的常量 n 转型为 myInt 类型,再与变量 a 相加。由于变量 a 的类型 myInt 的底层类型也是 int,所以这个隐式转型不会有任何问题。

如果 Go 编译器在做隐式转型时,发现无法将常量转换为目标类型,Go 编译器也会报错,如下:

const m = 1333333333

var k int8 = 1
j := k + m // 编译器报错:constant 1333333333 overflows int8
// 常量 m 的值 1333333333 已经超出了 int8 类型可以表示的范围,所以将它转换为 int8 类型时,就会导致编译器报溢出错误。
1
2
3
4
5

==无类型常量与常量隐式转型==的“珠联璧合”使得在 Go 这样的具有强类型系统的语言,在处理表达式混合数据类型运算的时候具有比较大的灵活性,代码编写也得到了一定程度的简化。也就是说,我们不需要在求值表达式中做任何显式转型了。所以说,在 Go 中,使用无类型常量是一种惯用法。

# 2.2.3 实现枚举

Go 语言并没有原生提供枚举类型。其实,在 Go 语言中,可以使用 const 代码块定义的常量集合,来实现枚举。这是因为,枚举类型本质上就是一个由有限数量常量所构成的集合,所以这样做并没有什么问题。

Go 设计者们的原创,他们在语言设计之初就希望将枚举类型与常量合二为一,这样就不需要再单独提供枚举类型了。

Go 中的两个特性:

特性1:Go 的 const 语法提供了“隐式重复前一个非空表达式”的机制。

const (
    Apple, Banana = 11, 22
    Strawberry, Grape 
    Pear, Watermelon 
)

// 常量定义的后两行并没有被显式地赋予初始值,所以 Go 编译器就为它们自动使用上一行的表达式,也就获得了下面这个等价的代码:

const (
    Apple, Banana = 11, 22
    Strawberry, Grape  = 11, 22 // 使用上一行的初始化表达式
    Pear, Watermelon  = 11, 22 // 使用上一行的初始化表达式
)

1
2
3
4
5
6
7
8
9
10
11
12
13
14

特性2:iota

iota 是 Go 语言的一个预定义标识符,它表示的是 const 声明块(包括单行声明)中,每个常量所处位置在块中的偏移值(从零开始)。

同时,每一行中的 iota 自身也是一个无类型常量,可以像前面我们提到的无类型常量那样,自动参与到不同类型的求值过程中来,不需要我们再对它进行显式转型操作。

Go 标准库中 sync/mutex.go 中的一段基于 iota 的枚举常量的定义:

// $GOROOT/src/sync/mutex.go 
const ( 
    // iota 的值是这行在 const 块中的偏移,因此 iota 的值为 0,mutexLocked = 1 << 0,也就是 1。
    mutexLocked = 1 << iota	
    //根据const声明块里“隐式重复前一个非空表达式”的机制,这一行就等价于 mutexWorken = 1 << iota。又因为这一行是 const 块中的第二行,所以它的偏移量 iota 的值为 1,得到mutexWorken = 1 << 1,也就是 2。
    mutexWoken
    // 同 mutexWorken 一样,这一行等价于 mutexStarving = 1 << iota。因为这行的 iota 的值为 2,可以得到 mutexStarving = 1 << 2,也就是 4;
    mutexStarving
    // 为常量 mutexWaiterShift 做了显式初始化,这样就不用再重复前一行了。由于这一行是第四行,而且作为行偏移值的 iota 的值为 3,因此 mutexWaiterShift 的值就为 3。
    mutexWaiterShift = iota  
    starvationThresholdNs = 1e6
)
1
2
3
4
5
6
7
8
9
10
11
12

注意:位于同一行的 iota 即便出现多次,多个 iota 的值也是一样的。

const (
    Apple, Banana = iota, iota + 10 // 0, 10 (iota = 0)
    Strawberry, Grape // 1, 11 (iota = 1)
    Pear, Watermelon  // 2, 12 (iota = 2)
)
1
2
3
4
5

如果要略过 iota = 0,从 iota = 1 开始正式定义枚举常量,可以效仿下面标准库中的代码:

// $GOROOT/src/syscall/net_js.go
const (
    _ = iota	// 使用了空白标识符作为第一个枚举常量,它的值就是 iota = 0。
    IPV6_V6ONLY  // 1
    SOMAXCONN    // 2
    SO_ERROR     // 3
)
1
2
3
4
5
6
7

如果是要略过某一个或几个值,可以借助空白标识符来实现,如下:

const (
    _ = iota // 0
    Pin1
    Pin2
    Pin3
    _
    Pin5    // 5   
)
1
2
3
4
5
6
7
8

使用场景:iota 特性让维护枚举常量列表变得更加容易。

声明一组按首字母排序的“颜色”常量:

const ( 
    Black  = 1 
    Red    = 2
    Yellow = 3
)
// 如果增加一个新颜色 Blue。那根据字母序,这个新常量应该放在 Red 的前面呀。但这样一来,就需要像下面代码这样将 Red 到 Yellow 的常量值都手动加 1,十分费力。
const (
    Blue   = 1
    Black  = 2
    Red    = 3
    Yellow = 4
)
// 如果使用 iota 重新定义这组“颜色”枚举常量。

const (
    _ = iota     
    Blue
    Red 
    Yellow     
) 
// 无论后期需要增加多少种颜色,只需将常量名插入到对应位置就可以,其他就不需要再做任何手工调整了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

如果一个 Go 源文件中有多个 const 代码块定义的不同枚举,每个 const 代码块中的 iota 也是独立变化的,也就是说,每个 const 代码块都拥有属于自己的 iota,如下面代码所示:

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

const (
    i = iota << 1 // 0, iota = 0
    j             // 2, iota = 1
    k             // 4, iota = 2
)
1
2
3
4
5
6
7
8
9
10
11

每个 iota 的生命周期都始于一个 const 代码块的开始,在该 const 代码块结束时结束。

# 2.3 常量定义

声明了 pi 和 e 这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

const pi = 3.1415
const e = 2.7182

// 批量声明
const (
   pi = 3.1415
   e  = 2.7182
)
1
2
3
4
5
6
7
8

const 同时声明多个常量时,如果省略了值则表示和上面一行的值相同。

const (
   n1 = 100
   n2
   n3
)
1
2
3
4
5
上次更新: 2022/06/12, 15:48:09
go变量声明
fmt包

← go变量声明 fmt包→

最近更新
01
ctr和crictl显示镜像不一致
03-13
02
alpine镜像集成常用数据库客户端
03-13
03
create-cluster
02-26
更多文章>
Theme by Vdoing | Copyright © 2015-2024 op81.com
苏ICP备18041258号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式