golang 快速入門 [8.4]-常量與隱式型別轉換

weishixianglian發表於2020-04-01

golang 快速入門 [8.4]-常量與隱式型別轉換

前文

前言

  • 在前文中我們學習了 go 語言中的自動型別推斷以及浮點數的細節
  • 我們將在本文中深入介紹 go 語言中另一個比較特殊的型別---const 常量

const 常量

  • Go 語言最獨特的功能之一就是該語言如何實現常量。Go 語言規範中的常量規則是 Go 特有的。其在編譯器級別提供了 Go 需求的靈活性,以使我們編寫的程式碼更具可讀性和直觀性,同時仍保持型別安全的語言。

常量宣告

  • 可以在 Go 中使用型別或省略型別來宣告常量,如下所示
const untypedInteger = 12345
const untypedFloatingPoint = 3.141592
const typedInteger int           = 12345
const typedFloatingPoint float64 = 3.141592
  • 其中等式左邊的常量可以叫做命名常量。等式右邊的常量可以叫做未命名常量,擁有未定義的型別。
  • 當有足夠的記憶體來儲存整數值時,可以始終精確地表示整數。由於規範要求整數常量的精度至少為 256 位,因此可以肯定地說整數常量在數學上是精確的。
  • 為了獲得數學上精確的浮點數,編譯器可以採用不同的策略和選項。go 語言規範未說明編譯器必須如何執行此操作,它僅指定一組需要滿足的強制性要求。
  • 以下是當今不同的編譯器用來實現數學上精確的浮點數的兩種策略:
    • 一種策略是將所有浮點數表示為分數,並對這些分數使用有理算術。這些浮點數永遠不會損失任何精度。
    • 另一種策略是使用高精度浮點數,其精度如此之高,以至於它們對於所有實際目的都是精確的。當我們使用具有數百位精度的浮點數時,精確值和近似值之間的差異實際上不存在。

常量的生存週期

  • 常量只會在編譯期間存在,因此其不會儲存在記憶體之中,也就不可以被定址.因此下面的程式碼是錯誤的。
const k = 5
address := &k

自動型別轉換

  • 如下所示,常量進行自動型別推斷。我們在之前自動型別推斷文章中其實就詳細介紹了未定義的常量如何進行轉換的。大致是在轉換為具體的型別之前,會使用一種高精度的結構去儲存
  • 詳細的介紹參見: golang 快速入門 [8.2]-自動型別推斷的祕密
var myInt =123

隱式整數型別轉換

  • 在 Go 中,變數之間沒有隱式型別轉換。但是,編譯器可以進行變數和常量之間的隱式型別轉換。
  • 例如:如下示例中,我們將常量 123 的整數型別隱式轉換為 int 型別的值。由於常量的形式不使用小數點或指數,因此常量採用整數型別。只要不需要截斷,就可以將整數型別的常量隱式轉換為有符號和無符號整數變數。
var myInt int = 123
  • 如果常量使用與整數型別相容的形式,則也可以將浮點型別的常量隱式轉換為整數變數:
var myInt int = 123.0
  • 但是下面的轉換卻是不可行的
var myInt int = 123.1

隱式浮點型別轉換

  • 如下所示,編譯器將在浮點型別的常量 0.333 到型別為 float64 的變數之間執行隱式轉換。由於常量的形式使用小數點,因此該常量採用浮點型別。
var myFloat float64 = 0.333
  • 編譯器還可以在整數型別的常量到 float64 型別的變數之間執行隱式轉換:
var myFloat float64 = 1

常量算術中的隱形轉換

  • 在其他常量和變數之間執行常量算術是我們在程式中經常要做的事情。它遵循規範中二進位制運算子的規則。該規則規定,除非操作涉及位運算未型別化的常量,否則運算元型別必須相同。
  • 讓我們看一個將兩個常數相乘的例子:
var answer = 3 * 0.333
  • 在 go 語言規範中,有一個關於常量表示式的規則專用於此操作:
  • 除了移位操作,如果算數操作的運算元是不同型別的無型別常量,則結果型別的優先順序為: 整數 (int)<符文數 (rune)<浮點數 (float)<複數 (Imag)
  • 根據此規則,上面兩個常數之間相乘的結果將是一個浮點數。因為浮點數要比整數優先順序高
  • 因此我們也能夠理解:下面的例子結果為浮點數:
const third = 1 / 3.0
  • 而下面的例子我們將在整數型別的兩個常量之間進行除法。除法的結果將是一個整數型別的新常量。由於將 3 除以 1 的值表示小於 1 的數字,因此該除法的結果為整數常數 0
const zero = 1 / 3

常量與具體型別的變數之間的算數轉換規則

  • 常量與具體型別的變數之間的算數,會使用已有的具體型別
  • 例如下例中常量 p 結果為 float64 型別,常量 2 會轉換為和 b 的型別相同
const b float64 = 1
const p = b * 2
  • 下面的例子出錯,因為 2.3 不能轉化為 b 的型別 int
const b int = 1
const p = b * 2.3

自定義型別的轉換

  • 下面再來看一個更復雜的例子,即使用者自定義的型別
type Numbers int8
const One Numbers = 1
const Two         = 2 * One
  • 在這裡,我們宣告瞭一個新型別,稱為 Numbers,其基本型別為 int8。然後,我們以 Numbers 型別宣告常量 One,並分配整數型別的常量 1。接下來,我們宣告常量 2,該常量通過將常量 2 和 Numbers 型別的常量 One 相乘而提升為 Numbers 型別。
  • 讓我們看一下標準庫中的一個實際示例。time 包宣告瞭常量集:
type Duration int64

const (
    Nanosecond Duration = 1
    Microsecond         = 1000 * Nanosecond
    Millisecond         = 1000 * Microsecond
    Second              = 1000 * Millisecond
)
  • 由於編譯器將對常量執行隱式轉換,因此我們可以在 Go 中像下面一樣編寫程式碼
package main

import (
    "fmt"
    "time"
)

const fiveSeconds = 5 * time.Second

func main() {
    now := time.Now()
    lessFiveNanoseconds := now.Add(-5)
    lessFiveSeconds := now.Add(-fiveSeconds)

    fmt.Printf("Now     : %v\n", now)
    fmt.Printf("Nano    : %v\n", lessFiveNanoseconds)
    fmt.Printf("Seconds : %v\n", lessFiveSeconds)
}

Output:
Now     : 2014-03-27 13:30:49.111038384 -0400 EDT
Nano    : 2014-03-27 13:30:49.111038379 -0400 EDT
Seconds : 2014-03-27 13:30:44.111038384 -0400 EDT
  • 讓我們看一下 Time 的 Add 方法的定義
func (t Time) Add(d Duration) Time
  • Add 方法接受一個型別為 Duration 的引數。讓我們仔細看看程式中對 Add 的方法呼叫
var lessFiveNanoseconds = now.Add(-5)
var lessFiveMinutes = now.Add(-fiveSeconds)
  • 編譯器會將常量-5 隱式轉換為 Duration 型別的變數,以允許方法呼叫發生。同時,由於常量算術規則,常量 FiveSeconds 已經是 Duration 型別
  • 但是如果指定了常量的型別,則呼叫不會成功,例如下面的例子
var difference int = -5
var lessFiveNano = now.Add(difference)

Compiler Error:
./const.go:16: cannot use difference (type int) as type time.Duration in function argument
  • 如上所示,一旦我們使用具體型別整數值作為 Add 方法呼叫的引數,我們就會收到編譯器錯誤。編譯器將不允許在型別變數之間進行隱式型別轉換。為了編譯該程式碼,我們需要執行顯式的型別轉換
Add(time.Duration(difference))
  • 常量是我們無需使用顯式型別轉換即可編寫程式碼的唯一機制

編譯時程式碼

  • 對於涉及到常量的算術規則,統一在 defaultlit2 函式中進行了處理。首先判斷操作符左邊的有無型別,有的話就將操作符右邊的型別轉成左邊的型別.
  • 如果操作符左邊為無型別,右邊為有型別,則將左邊的型別轉換為右邊的型別.
  • 如果操作符左右都無具體型別,根據優先順序決定型別的轉換
// go/src/cmd/compile/internal/gc
func defaultlit2(l *Node, r *Node, force bool) (*Node, *Node) {
    if l.Type == nil || r.Type == nil {
        return l, r
    }
    if !l.Type.IsUntyped() {
        r = convlit(r, l.Type)
        return l, r
    }

    if !r.Type.IsUntyped() {
        l = convlit(l, r.Type)
        return l, r
    }

    if !force {
        return l, r
    }

    if l.Type.IsBoolean() {
        l = convlit(l, types.Types[TBOOL])
        r = convlit(r, types.Types[TBOOL])
    }

    lkind := idealkind(l)
    rkind := idealkind(r)
    if lkind == CTCPLX || rkind == CTCPLX {
        l = convlit(l, types.Types[TCOMPLEX128])
        r = convlit(r, types.Types[TCOMPLEX128])
        return l, r
    }

    if lkind == CTFLT || rkind == CTFLT {
        l = convlit(l, types.Types[TFLOAT64])
        r = convlit(r, types.Types[TFLOAT64])
        return l, r
    }

    if lkind == CTRUNE || rkind == CTRUNE {
        l = convlit(l, types.Runetype)
        r = convlit(r, types.Runetype)
        return l, r
    }

    l = convlit(l, types.Types[TINT])
    r = convlit(r, types.Types[TINT])

    return l, r
}

總結

  • 在本文中介紹了常量的內涵、規則、常量的生存週期以及各種下情形下的隱式型別轉換。
  • 常量,Go 語言最獨特的功能之一,其是編譯時期的獨特功能。具體來說,隱式型別轉換的規則為:有型別常量優先於無型別常量,當兩個無型別常量算術運算時,結果型別的優先順序為: 整數 (int)<符文數 (rune)<浮點數 (float)<複數 (Imag)
  • see you~ ## 參考資料
  • 專案連結
  • 作者知乎
  • blog

喜歡本文的朋友歡迎點贊分享~

唯識相鏈啟用微信交流群(Go 與區塊鏈技術)

歡迎加微信:ywj2271840211

更多原創文章乾貨分享,請關注公眾號
  • golang 快速入門 [8.4]-常量與隱式型別轉換
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章