Go基礎 #2 変数、型、定数
#1 はじめてのプログラム で環境を整えたので — 今回は言語そのもの。Goの基本型と変数宣言の方法、定数まで。
基本型 #
Goは静的型付け言語です。コンパイル時にすべての変数の型が決まっています。
| 種類 | 型 |
|---|---|
| 整数 | int, int8, int16, int32, int64, uint, uint8 (= byte), … |
| 浮動小数 | float32, float64 |
| 複素数 | complex64, complex128 |
| 真偽値 | bool |
| 文字列 | string |
| ルーン (Unicodeコードポイント) | rune (= int32) |
最もよく使うのは int、string、bool、float64。ビット幅が決まった型(int32、int64 など)は、正確なサイズが必要なときだけ使います。
int のサイズ
#
int はプラットフォームによって32ビットまたは64ビットです。64ビットOSでは通常 int64 と同じです。正確なサイズが重要なときは int32 / int64 を明示します。
変数宣言 — 2種類の方法 #
1) var — 明示的宣言
#
var name string = "カーティス"
var age int = 30
var ok bool = true最も明示的な形。型を書きます。
1.5) var — 型推論
#
var name = "カーティス" // stringと推論
var age = 30 // intと推論初期値から型を推論できれば、型の明示を省略できます。
2) := — 短縮宣言
#
関数内で最も一般的な形。
name := "カーティス"
age := 30
ok := truevar 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 (
name string = "カーティス"
age int = 30
ok bool = true
)複数のvarをまとめて整理するとき。パッケージレベル変数でよく見かける形です。
ゼロ(zero)値 #
宣言だけして初期値を与えなければ — Goがゼロ値(zero value) で初期化してくれます。
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 Pi = 3.14159
const MaxRetries = 3
const AppName = "MyApp"var と似ていますが — コンパイル時定数なので実行時に変わらず、関数呼び出しの結果のような値はconstにできません。
const Now = time.Now() // ✗ time.Now() は実行時呼び出し型ありconst vs 型なし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に似た位置付け。
const (
Red = iota // 0
Green // 1
Blue // 2
)iota はconstブロック内で0から始まり、1行ごとに1ずつ増加します。同じ式を繰り返す必要なく自動で。
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 — 意味のある型を作る #
基本型をそのまま使わず自分の名前の型を作れます。
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.Println("行末に自動newline")
fmt.Print("行末newlineなし")
fmt.Printf("名前: %s, 年齢: %d\n", name, age)
s := fmt.Sprintf("結果: %d", 42) // 文字列にするよく使うフォーマットverb:
| verb | 意味 |
|---|---|
%v | デフォルト表現 (どの型でも) |
%+v | 構造体のフィールド名まで |
%d | 10進整数 |
%s | 文字列 |
%q | 引用符付き文字列 |
%t | bool |
%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を返します。
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) := と = の混同
#
x := 10 // 新しい変数 (宣言 + 代入)
x = 20 // 再代入
y := 30 // 新しい変数
// y := 40 ✗ y は既に宣言済み (ただし、多重宣言の中に新しい変数が一つでもあればOK)複数の変数のうち一つでも新しいものがあれば := が通ります。
x := 10
x, y := 20, 30 // x 再代入、y 新しい変数 — OK2) 整数除算は切り捨て #
result := 10 / 3 // 3 (小数点切り捨て)
result2 := 10.0 / 3.0 // 3.333...
result3 := float64(10) / 3 // 3.333...他の言語のように自動変換されません。浮動小数の結果が欲しければ明示的変換を。
まとめ #
今回の記事で整理した内容:
- 静的型付け — すべての変数の型がコンパイル時に決まる
varと:=の2種類の宣言方法- 関数内では
:=がほぼ標準 - ゼロ値 — 宣言するだけで意味のある初期値が自動で
- 自動型変換はほとんどなし — 明示的変換が必要
- 文字列 ↔ 数値は
strconv constと untyped vs typediotaで自動増加 / ビットフラグ / 単位表現- named typeで意味のある型を作る
- 文字列はbyte列、rune単位の巡回は
for range
次の記事(#3 制御フロー)ではif/for/switchとその中のモダンなパターンを扱います。Goにはwhileがなく、forがすべての繰り返しを行います。