golang快速入門[4]-go語言如何編譯爲機器碼windows
在上文中咱們學習了變量的各類概念和go語言中的類型系統
咱們將在本文中學習到:
什麼是自動類型推斷
爲何須要自動類型推斷
go語言中自動類型推斷的特色與陷阱
go語言在編譯時是如何進行自動類型推斷的
類型推斷是編程語言在編譯時自動解釋表達式數據類型的能力,一般在函數式編程的語言(例如Haskell)中存在,類型推斷的優點主要在於能夠省略類型,這使編程任務更加容易。
明確的指出變量的類型在編程語言中很常見,編譯器在多大程度上能夠作到這一點,因語言而異。例如,某些編譯器能夠推斷出值:變量,函數參數和返回值。
go語言做爲靜態類型語言在編譯時就須要知道變量的類型
使編譯器支持諸如類型推斷之類的東西有兩個主要的優點。首先,若是使用得當,它可使代碼更易讀,例如,能夠將以下C ++代碼:
vector<int> v;
vector<int>::iterator itr = v.iterator();
變爲:
vector<int> v;
auto itr = v.iterator();
儘管在這裏得到的收益彷佛微不足道,可是若是類型更加複雜,則類型推斷的價值變得顯而易見。在許多狀況下,這將使咱們減小代碼中的冗餘信息。
類型推斷還用於其餘功能,Haskell
語言能夠編寫爲:
succ x = x + 1
上面的函數中,無論變量X是什麼類型,加1並返回結果。
儘管如此,顯式的指出類型仍然有效,由於編譯器能夠更輕鬆地瞭解代碼實際應執行的操做,不太可能犯任何錯誤。
如上所述,類型推斷的能力每一個語言是不相同的,在go語言中根據開發人員的說法,他們的目標是減小在靜態類型語言中發現的混亂狀況。他們認爲許多像Java或C++這樣的語言中的類型系統過於繁瑣。
所以,在設計Go時,他們從這些語言中借鑑了一些想法。這些想法之一是對變量使用簡單的類型推斷,給人以編寫動態類型代碼的感受,同時仍然使用靜態類型的好處
如前所述,類型推斷能夠涵蓋參數和返回值之類的內容,可是Go中沒有
在實踐中,能夠經過在聲明新變量或常量時簡單地忽略類型信息,或使用:=
表示法來觸發Go中的類型推斷
在Go中,如下三個語句是等效的:
var a int = 10
var a = 10
a := 10
Go的類型推斷在處理包含標識符的推斷方面是半完成的。本質上,編譯器將不容許對從標識符
引用的值進行強制類型轉換,舉幾個例子:
下面這段代碼正常運行,而且a的類型爲float64
a := 1 + 1.1
下面的代碼仍然正確,a會被推斷爲浮點數,1
會變爲浮點數與a的值相加
a := 1.1
b := 1 + a
可是,下面代碼將會錯誤,即a的值已被推斷爲整數,而1.1爲浮點數,可是不能將a強制轉換爲浮點數,相加失敗。編譯器報錯:constant 1.1 truncated to integer
a := 1
b := a + 1.1
下面的類型會犯相同的錯誤,編譯器提示:,invalid operation: a + b (mismatched types int and float64)
a := 1
b := 1.1
c := a + b
在以前的這篇文章中(go語言如何編譯爲機器碼),咱們介紹了編譯器執行的過程:詞法分析 => 語法分析 => 類型檢查 => 中間代碼 => 代碼優化 => 生成機器碼
編譯階段的代碼位於go/src/cmd/compile
文件中
具體來講,在詞法分析階段,會將賦值右邊的常量解析爲一個未定義的類型,類型有以下幾種:顧名思義,其中ImagLit表明複數,IntLit表明整數…
//go/src/cmd/compile/internal/syntax
const (
IntLit LitKind = iota
FloatLit
ImagLit
RuneLit
StringLit
)
go語言源代碼採用UTF-8的編碼方式,在進行詞法分析時當遇到須要賦值的常量操做時,會逐個的讀取後面常量的UTF-8字符。字符串的首字符爲"
,數字的首字母爲'0'-'9'。實現函數位於:
// go/src/cmd/compile/internal/syntax
func (s *scanner) next() {
...
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
s.number(c)
case '"':
s.stdString()
case '`':
s.rawString()
...
所以對於整數、小數等常量的識別就顯得很是簡單。具體來講,一個整數就是全是"0"-"9"的數字。一個浮點數就是字符中有"."號的數字,字符串就是首字符爲"
下面列出的函數爲小數和整數語法分析的具體實現:
// go/src/cmd/compile/internal/syntax
func (s *scanner) number(c rune) {
s.startLit()
base := 10 // number base
prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b'
digsep := 0 // bit 0: digit present, bit 1: '_' present
invalid := -1 // index of invalid digit in literal, or < 0
// integer part
var ds int
if c != '.' {
s.kind = IntLit
if c == '0' {
c = s.getr()
switch lower(c) {
case 'x':
c = s.getr()
base, prefix = 16, 'x'
case 'o':
c = s.getr()
base, prefix = 8, 'o'
case 'b':
c = s.getr()
base, prefix = 2, 'b'
default:
base, prefix = 8, '0'
digsep = 1 // leading 0
}
}
c, ds = s.digits(c, base, &invalid)
digsep |= ds
}
// fractional part
if c == '.' {
s.kind = FloatLit
if prefix == 'o' || prefix == 'b' {
s.error("invalid radix point in " + litname(prefix))
}
c, ds = s.digits(s.getr(), base, &invalid)
digsep |= ds
}
...
咱們以賦值操做a := 333
爲例, 當完成詞法分析時, 此賦值語句用AssignStmt
表示。
AssignStmt struct {
Op Operator // 0 means no operation
Lhs, Rhs Expr // Rhs == ImplicitOne means Lhs++ (Op == Add) or Lhs-- (Op == Sub)
simpleStmt
}
其中Op
表明操做符,在這裏是賦值操做,Lhs與Rhs分別表明左右兩個表達式,左邊表明了變量a
,右邊表明了整數333
,此時右邊整數的類型爲intLit
接着生成在抽象語法樹AST時, 會將詞法分析的AssignStmt
解析變爲一個ode
,Node
結構體是對於抽象語法樹中節點的抽象。
type Node struct {
// Tree structure.
// Generic recursive walks should follow these fields.
Left *Node
Right *Node
Ninit Nodes
Nbody Nodes
List Nodes
Rlist Nodes
E interface{} // Opt or Val, see methods below
...
仍然是Left左節點表明了左邊的變量a
,Right右節點表明了整數333
。
此時在E接口中,Right右節點會存儲值333
,類型爲mpint。mpint用於存儲整數常量
具體的代碼以下,若是爲IntLit類型,轉換爲Mpint類型,其餘類型相似。
可是注意,此時左邊的節點仍是沒有任何類型的。
// go/src/cmd/compile/internal/gc
func (p *noder) basicLit(lit *syntax.BasicLit) Val {
// TODO: Don't try to convert if we had syntax errors (conversions may fail).
// Use dummy values so we can continue to compile. Eventually, use a
// form of "unknown" literals that are ignored during type-checking so
// we can continue type-checking w/o spurious follow-up errors.
switch s := lit.Value; lit.Kind {
case syntax.IntLit:
checkLangCompat(lit)
x := new(Mpint)
x.SetString(s)
return Val{U: x}
case syntax.FloatLit:
checkLangCompat(lit)
x := newMpflt()
x.SetString(s)
return Val{U: x}
以下Mpint類型的結構,咱們能夠看到AST階段整數存儲經過math/big.int進行高精度存儲。
// Mpint represents an integer constant.
type Mpint struct {
Val big.Int
Ovf bool // set if Val overflowed compiler limit (sticky)
Rune bool // set if syntax indicates default type rune
}
最後在抽象語法樹進行類型檢查的階段,會完成最終的賦值操做。將右邊常量的類型賦值給左邊變量的類型。
最終具體的函數位於typecheckas
,將右邊的類型賦值給左邊
func typecheckas(n *Node) {
...
if n.Left.Name != nil && n.Left.Name.Defn == n && n.Left.Name.Param.Ntype == nil {
n.Right = defaultlit(n.Right, nil)
n.Left.Type = n.Right.Type
}
}
...
mpint
類型對應的爲CTINT
標識。以下所示,前一階段不一樣類型對應不一樣的標識。最終左邊的變量存儲的類型會變爲types.Types[TINT]
func (v Val) Ctype() Ctype {
switch x := v.U.(type) {
default:
Fatalf("unexpected Ctype for %T", v.U)
panic("unreachable")
case nil:
return 0
case *NilVal:
return CTNIL
case bool:
return CTBOOL
case *Mpint:
if x.Rune {
return CTRUNE
}
return CTINT
case *Mpflt:
return CTFLT
case *Mpcplx:
return CTCPLX
case string:
return CTSTR
}
}
types.Types是一個數組,存儲了不一樣標識對應的go語言中的實際類型。
var Types [NTYPE]*Type
Type
是go語言中類型的存儲結構,types.Types[TINT]
最終表明的類型爲int
類型。其結構以下:
// A Type represents a Go type.
type Type struct {
Extra interface{}
// Width is the width of this Type in bytes.
Width int64 // valid if Align > 0
methods Fields
allMethods Fields
Nod *Node // canonical OTYPE node
Orig *Type // original type (type literal or predefined type)
// Cache of composite types, with this type being the element type.
Cache struct {
ptr *Type // *T, or nil
slice *Type // []T, or nil
}
Sym *Sym // symbol containing name, for named types
Vargen int32 // unique name for OTYPE/ONAME
Etype EType // kind of type
Align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed)
flags bitset8
}
最後,咱們能夠用下面的代碼來驗證類型,輸出結果爲:int
a := 333
fmt.Printf("%T",a)
在本文中,咱們介紹了自動類型推斷的內涵以及其意義。同時,咱們用例子指出了go語言中自動類型推斷的特色。
最後,咱們用a:=333
爲例,介紹了go語言在編譯時是如何進行自動類型推斷的。
具體來講,go語言在編譯時涉及到詞法分析和抽象語法樹階段。對於數字的處理首先採用了math包中進行了高精度的處理,接着會轉換爲go語言中的標準類型,int或float64.在本文中沒有對字符串等作詳細介紹,留給之後的文章。
see you~
項目連接
做者知乎
blog
Type inference
Rob Pike:Less is exponentially more
Type inference for go