Go基礎 #2 変数、型、定数

読了 6分

#1 はじめてのプログラム で環境を整えたので — 今回は言語そのもの。Goの基本型と変数宣言の方法、定数まで。

基本型 #

Goは静的型付け言語です。コンパイル時にすべての変数の型が決まっています。

種類
整数int, int8, int16, int32, int64, uint, uint8 (= byte), …
浮動小数float32, float64
複素数complex64, complex128
真偽値bool
文字列string
ルーン (Unicodeコードポイント)rune (= int32)

最もよく使うのは intstringboolfloat64。ビット幅が決まった型(int32int64 など)は、正確なサイズが必要なときだけ使います。

int のサイズ #

intプラットフォームによって32ビットまたは64ビットです。64ビットOSでは通常 int64 と同じです。正確なサイズが重要なときは int32 / int64 を明示します。

変数宣言 — 2種類の方法 #

1) var — 明示的宣言 #

var
var name string = "カーティス"
var age int = 30
var ok bool = true

最も明示的な形。型を書きます。

1.5) var — 型推論 #

var + 推論
var name = "カーティス"   // stringと推論
var age = 30          // intと推論

初期値から型を推論できれば、型の明示を省略できます。

2) := — 短縮宣言 #

関数内で最も一般的な形。

:=
name := "カーティス"
age := 30
ok := true

var x = ... とほぼ同じ意味ですが、関数内でしか使えません。関数内の新しい変数にはほぼ常に := が標準的な慣習です。

関数の外では := 不可 #

関数の外
package main

var globalName = "カーティス"   // OK
// globalName2 := "別"        ✗ 関数の外では := 不可

func main() {
	localName := "ローカル"    // OK
}

複数変数を一度に #

複数変数
var a, b, c int = 1, 2, 3
var x, y = 1, "hi"   // 異なる型もOK

i, j := 10, 20
i, j = j, i           // swap

:= の場でswapのようなパターンが自然です。

var ブロック #

var ブロック
var (
	name string = "カーティス"
	age  int    = 30
	ok   bool   = true
)

複数のvarをまとめて整理するとき。パッケージレベル変数でよく見かける形です。

ゼロ(zero)値 #

宣言だけして初期値を与えなければ — Goがゼロ値(zero value) で初期化してくれます。

zero values
var i int        // 0
var f float64    // 0.0
var b bool       // false
var s string     // "" (空文字列)
var p *int       // nil (ポインタのゼロ値)

他の言語の null/undefined と違う点 — Goではすべての変数が常に意味のある値を持ちます。「宣言だけして使っていない」ような状態がありません。

型変換 — 明示的 #

Goは自動型変換をほとんど行いません。異なる型同士の間では明示的変換が必要です。

明示的変換
i := 42
f := float64(i)      // int → float64
ui := uint(f)         // float64 → uint

// i + f          ✗ mismatched types
i + int(f)        // OK

最初は煩わしくても、意図しない精度の損失を防ぐ設計です。

文字列 ↔ 数値 #

ここは変換関数が別にあります。

文字列と数値
import "strconv"

s := strconv.Itoa(42)              // int → string ("42")
n, err := strconv.Atoi("42")       // string → int (エラーの可能性)

f, err := strconv.ParseFloat("3.14", 64)

エラー返却は #4 関数、多値返却、error型 で詳しく。

定数 — const #

値がコンパイル時に決まり変わらないなら const

const 基本
const Pi = 3.14159
const MaxRetries = 3
const AppName = "MyApp"

var と似ていますが — コンパイル時定数なので実行時に変わらず、関数呼び出しの結果のような値はconstにできません。

const の制約
const Now = time.Now()   // ✗ time.Now() は実行時呼び出し

型ありconst vs 型なしconst #

typed vs untyped const
const Pi = 3.14159              // untyped — 文脈に応じて型決定
const PiTyped float32 = 3.14159 // typed — float32に固定

var f float64 = Pi        // OK — Pi がuntyped
var f32 float32 = Pi      // OK — Pi がuntyped
var f32 float32 = PiTyped // OK
var f64 float64 = PiTyped // ✗ float32 → float64 明示変換必要

型なし(untyped)constはより柔軟です。特別な理由がなければuntypedにしておくのが普通です。

iota — 自動増加const #

同じconstグループ内で自動的に増加する値を作れます。他の言語のenumに似た位置付け。

iota 基本
const (
	Red   = iota   // 0
	Green          // 1
	Blue           // 2
)

iota はconstブロック内で0から始まり、1行ごとに1ずつ増加します。同じ式を繰り返す必要なく自動で。

iotaの活用 #

多様なiotaパターン
// 定数の省略
const (
	A = iota    // 0
	B           // 1
	C           // 2
	_           // 3 (スキップ)
	E           // 4
)

// ビットフラグ
const (
	Read    = 1 << iota   // 1 (1 << 0)
	Write                  // 2 (1 << 1)
	Execute                // 4 (1 << 2)
)

// 単位
const (
	_        = iota          // 無視 (iota = 0)
	KB       = 1 << (10 * iota)   // 1024
	MB                              // 1024 * 1024
	GB                              // 1024^3
)

これらのパターンはライブラリコードでよく登場します。ビットフラグが特によく使われます。

Named type — 意味のある型を作る #

基本型をそのまま使わず自分の名前の型を作れます。

named type
type UserID string
type Email string

func sendMessage(id UserID, to Email) {
	// ...
}

var u UserID = "u1"
var e Email = "me@example.com"

sendMessage(u, e)        // OK
// sendMessage(e, u)      ✗ UserIDとEmailは別の型

同じstringなのに — UserIDとEmailは互換ではありません。意味を型に焼き付けて、間違った呼び出しをコンパイル段階で防げます。TypeScriptのbranded type (TS 上級 #5) と似た効果です。

基本出力 / 入力 — fmt #

よく使うfmtパッケージの関数。

fmt よく使うもの
fmt.Println("行末に自動newline")
fmt.Print("行末newlineなし")

fmt.Printf("名前: %s, 年齢: %d\n", name, age)

s := fmt.Sprintf("結果: %d", 42)   // 文字列にする

よく使うフォーマットverb:

verb意味
%vデフォルト表現 (どの型でも)
%+v構造体のフィールド名まで
%d10進整数
%s文字列
%q引用符付き文字列
%tbool
%f浮動小数
%T型名
フォーマット例
fmt.Printf("%d %s %t\n", 42, "hi", true)        // 42 hi true
fmt.Printf("%v %T\n", 3.14, 3.14)                // 3.14 float64
fmt.Printf("%q\n", "hello")                       // "hello"

文字列の二つの顔 #

Goの文字列は不変なバイト列です。インデックスアクセスはbyteを返します。

byte vs rune
s := "こんにちは"
fmt.Println(len(s))      // 15 — UTF-8 バイト数
fmt.Println(s[0])        // 227 — 最初のバイト (数値)

// 文字(rune)単位で巡回
for i, r := range s {
	fmt.Printf("%d %c\n", i, r)
}
// 0 こ
// 3 ん
// 6 に
// 9 ち
// 12 は

for range はルーン(Unicodeコードポイント)単位で巡回します。日本語/絵文字を扱うときはbyteインデックスではなくruneで扱う必要があります。

よく出会う罠 #

1) :== の混同 #

新しい変数 vs 再代入
x := 10        // 新しい変数 (宣言 + 代入)
x = 20         // 再代入
y := 30        // 新しい変数
// y := 40      ✗ y は既に宣言済み (ただし、多重宣言の中に新しい変数が一つでもあればOK)

複数の変数のうち一つでも新しいものがあれば := が通ります。

多重宣言の := 動作
x := 10
x, y := 20, 30   // x 再代入、y 新しい変数 — OK

2) 整数除算は切り捨て #

整数 / 整数 = 整数
result := 10 / 3        // 3 (小数点切り捨て)
result2 := 10.0 / 3.0   // 3.333...
result3 := float64(10) / 3   // 3.333...

他の言語のように自動変換されません。浮動小数の結果が欲しければ明示的変換を。

まとめ #

今回の記事で整理した内容:

  • 静的型付け — すべての変数の型がコンパイル時に決まる
  • var:= の2種類の宣言方法
  • 関数内では := がほぼ標準
  • ゼロ値 — 宣言するだけで意味のある初期値が自動で
  • 自動型変換はほとんどなし — 明示的変換が必要
  • 文字列 ↔ 数値は strconv
  • const と untyped vs typed
  • iota で自動増加 / ビットフラグ / 単位表現
  • named typeで意味のある型を作る
  • 文字列はbyte列、rune単位の巡回は for range

次の記事(#3 制御フロー)ではif/for/switchとその中のモダンなパターンを扱います。Goにはwhileがなく、forがすべての繰り返しを行います。

X