## 2.5. 類型

變量或表達式的類型定義了對應存儲值的屬性特徵，例如數值在內存的存儲大小（或者是元素的bit個數），它們在內部是如何表達的，是否支持一些操作符，以及它們自己關聯的方法集等。

在任何程序中都會存在一些變量有着相同的內部結構，但是卻表示完全不同的概念。例如，一個int類型的變量可以用來表示一個循環的迭代索引、或者一個時間戳、或者一個文件描述符、或者一個月份；一個float64類型的變量可以用來表示每秒移動幾米的速度、或者是不同溫度單位下的溫度；一個字符串可以用來表示一個密碼或者一個顔色的名稱。

一個類型聲明語句創建了一個新的類型名稱，和現有類型具有相同的底層結構。新命名的類型提供了一個方法，用來分隔不同概念的類型，這樣卽使它們底層類型相同也是不兼容的。

```Go
type 類型名字 底層類型
```

類型聲明語句一般出現在包一級，因此如果新創建的類型名字的首字符大寫，則在外部包也可以使用。

譯註：對於中文漢字，Unicode標誌都作爲小寫字母處理，因此中文的命名默認不能導出；不過国內的用戶針對該問題提出了不同的看法，根據RobPike的迴複，在Go2中有可能會將中日韓等字符當作大寫字母處理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的迴複：

> A solution that's been kicking around for a while:
>
> For Go 2 (can't do it before then): Change the definition to “lower case letters and _ are package-local; all else is exported”. Then with non-cased languages, such as Japanese, we can write 日本語 for an exported name and _日本語 for a local name. This rule has no effect, relative to the Go 1 rule, with cased languages. They behave exactly the same.

爲了説明類型聲明，我們將不同溫度單位分别定義爲不同的類型：

```Go
gopl.io/ch2/tempconv0
// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv

import "fmt"

type Celsius float64    // 攝氏溫度
type Fahrenheit float64 // 華氏溫度

const (
	AbsoluteZeroC Celsius = -273.15 // 絶對零度
	FreezingC     Celsius = 0       // 結冰點溫度
	BoilingC      Celsius = 100     // 沸水溫度
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
```

我們在這個包聲明了兩種類型：Celsius和Fahrenheit分别對應不同的溫度單位。它們雖然有着相同的底層類型float64，但是它們是不同的數據類型，因此它們不可以被相互比較或混在一個表達式運算。刻意區分類型，可以避免一些像無意中使用不同單位的溫度混合計算導致的錯誤；因此需要一個類似Celsius(t)或Fahrenheit(t)形式的顯式轉型操作才能將float64轉爲對應的類型。Celsius(t)和Fahrenheit(t)是類型轉換操作，它們併不是函數調用。類型轉換不會改變值本身，但是會使它們的語義發生變化。另一方面，CToF和FToC兩個函數則是對不同溫度單位下的溫度進行換算，它們會返迴不同的值。

對於每一個類型T，都有一個對應的類型轉換操作T(x)，用於將x轉爲T類型（譯註：如果T是指針類型，可能會需要用小括弧包裝T，比如`(*int)(0)`）。隻有當兩個類型的底層基礎類型相同時，才允許這種轉型操作，或者是兩者都是指向相同底層結構的指針類型，這些轉換隻改變類型而不會影響值本身。如果x是可以賦值給T類型的值，那麽x必然也可以被轉爲T類型，但是一般沒有這個必要。

數值類型之間的轉型也是允許的，併且在字符串和一些特定類型的slice之間也是可以轉換的，在下一章我們會看到這樣的例子。這類轉換可能改變值的表現。例如，將一個浮點數轉爲整數將丟棄小數部分，將一個字符串轉爲`[]byte`類型的slice將拷貝一個字符串數據的副本。在任何情況下，運行時不會發生轉換失敗的錯誤（譯註: 錯誤隻會發生在編譯階段）。

底層數據類型決定了內部結構和表達方式，也決定是否可以像底層類型一樣對內置運算符的支持。這意味着，Celsius和Fahrenheit類型的算術運算行爲和底層的float64類型是一樣的，正如我們所期望的那樣。

```Go
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
fmt.Printf("%g\n", boilingF-FreezingC)       // compile error: type mismatch
```

比較運算符`==`和`<`也可以用來比較一個命名類型的變量和另一個有相同類型的變量，或有着相同底層類型的未命名類型的值之間做比較。但是如果兩個值有着不同的類型，則不能直接進行比較：

```Go
var c Celsius
var f Fahrenheit
fmt.Println(c == 0)          // "true"
fmt.Println(f >= 0)          // "true"
fmt.Println(c == f)          // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!
```

註意最後那個語句。盡管看起來想函數調用，但是Celsius(f)是類型轉換操作，它併不會改變值，僅僅是改變值的類型而已。測試爲眞的原因是因爲c和g都是零值。

一個命名的類型可以提供書寫方便，特别是可以避免一遍又一遍地書寫複雜類型（譯註：例如用匿名的結構體定義變量）。雖然對於像float64這種簡單的底層類型沒有簡潔很多，但是如果是複雜的類型將會簡潔很多，特别是我們卽將討論的結構體類型。

命名類型還可以爲該類型的值定義新的行爲。這些行爲表示爲一組關聯到該類型的函數集合，我們稱爲類型的方法集。我們將在第六章中討論方法的細節，這里值説寫簡單用法。

下面的聲明語句，Celsius類型的參數c出現在了函數名的前面，表示聲明的是Celsius類型的一個叫名叫String的方法，該方法返迴該類型對象c帶着°C溫度單位的字符串：

```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
```

許多類型都會定義一個String方法，因爲當使用fmt包的打印方法時，將會優先使用該類型對應的String方法返迴的結果打印，我們將在7.1節講述。

```Go
c := FToC(212.0)
fmt.Println(c.String()) // "100°C"
fmt.Printf("%v\n", c)   // "100°C"; no need to call String explicitly
fmt.Printf("%s\n", c)   // "100°C"
fmt.Println(c)          // "100°C"
fmt.Printf("%g\n", c)   // "100"; does not call String
fmt.Println(float64(c)) // "100"; does not call String
```


