型
以前のデータ型のセクションで、Go のすべての組み込みデータ型について簡単に紹介しました。これらの組み込みの基礎型は、後続のカスタム型の基礎です。Go は典型的な静的型付け言語で、すべての変数の型はコンパイル時に確定され、プログラム全体のライフサイクルを通じて変更されません。このセクションでは、Go の型システムと基本的な使用方法を簡単に紹介します。
静的型付け
Go は静的型付け言語です。静的とは、Go のすべての変数の型はコンパイル時にすでに決定されており、プログラムのライフサイクルを通じて変更されないことを意味します。Go の短変数宣言は動的言語の書き方に似ていますが、変数の型はコンパイラによって推論されます。根本的な違いは、型が推論された後に変更されないことです。動的言語は完全に逆です。したがって、以下のコードはコンパイルできません。a は int 型の変数であり、文字列を代入できないためです。
func main() {
var a int = 64
a = "64"
fmt.Println(a) // cannot use "64" (untyped string constant) as int value in assignment
}強い型付けとは、プログラムで厳密な型チェックを実行することを指します。型が一致しない場合、動的言語のように可能な結果を推測するのではなく、プログラマーにそうすべきではないことをすぐに通知します。したがって、以下のコードはコンパイルできません。両者の型が異なるため、演算を実行できないためです。
func main() {
fmt.Println(1 + "1") // invalid operation: 1 + "1" (mismatched types untyped int and untyped string)
}型後置
Go が型宣言を前に配置せず後に配置する理由は、很大程度上 C 言語から教訓を学んだためです。公式の例を挙げて効果を示します。これは関数ポインタです。
int (*(*fp)(int (*)(int, int), int))(int, int)正直なところ、真剣に見ないとこれがどのような型なのか分かりません。Go での類似の書き方は以下の通りです。
f func(func(int,int) int, int) func(int, int) intGo の宣言方法は常に名前が前で、型が後の原則に従います。左から右に読んで、最初の目でこれが関数であり、戻り値が func(int,int) int であることがわかります。型がより複雑になるにつれて、型後置は可読性の面で大幅に優れています。Go の多くの設計は可読性のためにサービスされています。
型宣言
Go では、型宣言を通じてカスタム名前の新しい型を宣言できます。新しい型を宣言するには通常、型名と基礎型が必要です。簡単な例を以下に示します。
type MyInt int64上記の型宣言では、type キーワードを使用して、基礎型が int64 で名前が MyInt の型を宣言しました。Go では、宣言されるすべての型には対応する基礎型が必要であり、型名は既存の組み込み識別子と重複しないことを推奨します。
type MyInt int64
type MyFloat64 float64
type MyMap map[string]int
// コンパイルできますが、推奨されません。既存の型をオーバーライドします
type int int64型宣言を通じて宣言される型はすべて新しい型であり、異なる型は演算できません。基礎型が同じでも同様です。
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(f1 + f)invalid operation: f1 + f (mismatched types MyFloat64 and float64)型エイリアス
型エイリアスは型宣言とは異なります。型エイリアスは単なるエイリアスであり、新しい型を作成するわけではありません。簡単な例を以下に示します。
type Int = int両者は同じ型であり、名前が異なるだけです。したがって、演算を実行できます。したがって、以下の例もコンパイルできます。
type Int = int
var a Int = 1
var b int = 2
fmt.Println(a + b)3型エイリアスは特に複雑な型に対して非常に有用です。例えば、型 map[string]map[string]int があるとします。これは 2 次元 map です。現在、関数パラメータが map[string]map[string]int 型であるとします。以下に示します。
func PrintMyMap(mymap map[string]map[string]int) {
fmt.Println(mymap)
}この場合、型宣言を使用する必要はありません。前者は新しい型を宣言しているため、この関数のパラメータとして使用できないためです。型エイリアスを使用した例を以下に示します。
type TwoDMap = map[string]map[string]int
func PrintMyMap(mymap TwoDMap) {
fmt.Println(mymap)
}型エイリアスを使用した後、より簡潔に見えることがわかります。
TIP
組み込み型 any は interface{} の型エイリアスです。両者は完全に等価で、呼び方が異なるだけです。
型変換
Go では、明示的な型変換のみが存在し、暗黙の型変換は存在しません。したがって、異なる型の変数は演算できず、パラメータとして渡すこともできません。型変換が適用される前提は、変換される変数の型と変換先の目標型が既知であることです。例を以下に示します。
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(float64(f1) + f)0.30000000000000004MyFloat64 を float64 型に明示的に変換することで、加算演算を実行できます。型変換のもう 1 つの前提は、変換される型が目標型によって表現可能(Representability)でなければならないことです。例えば、int は int64 型によって表現可能であり、float64 型によっても表現可能です。したがって、それらの間で明示的な型変換を実行できます。しかし、int 型は string や bool 型によって表現できないため、型変換を実行できません。
TIP
表現可能(Representability)の定義については、リファレンスマニュアル - Representability を参照して詳細を確認できます。
2 つの型が互いに表現可能であっても、型変換の結果が常に正しいとは限りません。以下の例をご覧ください。
var num1 int8 = 1
var num2 int32 = 512
fmt.Println(int32(num1), int8(num2))1 0num1 は正しく int32 型に変換されましたが、num2 はそうではありません。これは典型的な数値オーバーフロー問題です。int32 は 31 ビット整数を表現でき、int8 は 7 ビット整数のみを表現できます。高精度整数を低精度整数に変換する際、上位ビットを破棄して下位ビットを保持します。したがって、num1 の変換結果は 0 になります。数値の型変換では、通常、小から大への変換を推奨し、大から小への変換は推奨されません。
型変換を使用する際、一部の型については曖昧さを避ける必要があります。例を以下に示します。
*Point(p) // 等价于 *(Point(p))
(*Point)(p) // p を *Point 型に変換
<-chan int(c) // 等价于 <-(chan int(c))
(<-chan int)(c) // c を <-chan int 型に変換
(func())(x) // x を func() 型に変換
(func() int)(x) // x を func() int 型に変換型アサーション
型アサーションは通常、あるインターフェース型の変数が特定の型に属しているかどうかを判断するために使用されます。例を以下に示します。
var b int = 1
var a interface{} = b
if intVal, ok := a.(int); ok {
fmt.Println(intVal)
} else {
fmt.Println("error type")
}1interface{} は空インターフェース型であり、空インターフェース型はすべての型を表現できますが、int 型は interface{} 型を表現できないため、型変換を使用できません。型アサーションは、その基底型が目的の型かどうかを判断できます。型アサーション文には 2 つの戻り値があります。1 つは型変換後の値、もう 1 つは変換結果のブール値です。
型判断
Go では、switch 文は特殊な書き方をサポートしています。この書き方を通じて、異なる case に応じて異なるロジック処理を実行できます。使用の前提は、入力パラメータがインターフェース型でなければならないことです。例を以下に示します。
var a interface{} = 2
switch a.(type) {
case int: fmt.Println("int")
case float64: fmt.Println("float")
case string: fmt.Println("string")
}intTIP
unsafe パッケージが提供する操作を通じて、Go の型システムを迂回できます。これにより、元々コンパイルできなかった型変換操作を実行できます。
