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~

參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章