Goの変数とデータ型
変数はプログラムの基礎となる要素です。Go言語では、最小限の構文を用いて、変数宣言を明確かつ安全に行えるようにしています。
Goの型システムの設計思想は、「明示的優先、暗黙的後回し」です。これは、すべての変数に明確に定義された型が割り当てられることを意味し、これによりコンパイラはコンパイル時に90%以上のエラーを検出できるようになります。このレッスンでは、Goにおける変数の宣言に関するすべての構文について学びます。
1. 学習内容
- 変数を宣言する2つの方法:
varと:= - Go言語の14の基本データ型
- Go言語特有の「ゼロ値」の仕組み
const定数の宣言とiota列挙型- 明示的な型変換(Goでは暗黙的な型変換は許可されていません)
fmt.Printf/fmt.Sprintf形式の出力- ユーザー登録フォームの包括的な例
2. フルスタックエンジニアの実話
(1) 課題:動的型付けに起因する深夜のデバッグ作業
アリスは、PythonからGoへ移行したエンジニアです。彼女は最近この会社に入社し、ユーザー登録モジュールの引き継ぎを行いました:
「PMは次のように述べた。『サービス開始後、ユーザーデータはめちゃくちゃだった。誕生日に2024年1月1日と入力した人もいれば、「昨日」と入力した人もいれば、
nullと入力した人もいて、データベース内のデータの半分は解析できなかった。』」
彼女はコードを調べてみると、Pythonの動的型付けが原因であることに気づいた:
# Python:変数の型はいつでも変更可能
birthday = "1990-01-01"
birthday = 1990 # 整数が突然合法になった
birthday = None # None それも合法だ
birthday = ["today"] # リストも有効です!
同じ変数が、コードの異なる箇所で文字列、整数、None、あるいはリストとして扱われる可能性があります。この混同を防ぐ仕組みは存在しません。
(2) Go言語での解決策
アリスはユーザーモデルをGoで書き直しました:
// user.go
package main
import "fmt"
type User struct {
Name string // 文字列
Age int // 整数
Height float64 // 浮動小数点
IsActive bool // ブール
Birthday string // ISO 8601 日付文字列
}
func main() {
user := User{
Name: "Alice",
Age: 28,
Height: 165.5,
IsActive: true,
Birthday: "1996-05-12",
}
// Want to change `Age` to a string? You'll get a compile-time error!
// user.Age = "twenty-eight" // ❌ cannot use "twenty-eight" as int
fmt.Printf("User: %s, Age: %d, Height: %.1fcm\n",
user.Name, user.Age, user.Height)
}
出力:
User: Alice, Age: 28, Height: 165.5cm
(3) 利点:コンパイル時にエラーの90%を検出できる
| 次元 | Python(動的型付け) | Go(静的かつ強型付け) |
|---|---|---|
| コンパイル時の型チェック | ❌ なし | ✅ 厳格 |
| 実行時タイプエラー | 頻繁 | まれ |
| IDEの自動補完 | 弱い | 強い |
| リファクタリングの安全性 | 低 | 高 |
| ヌル値トラップ (None/null) | 一般的 | ゼロ値メカニズムによる保護 |
3. 変数の宣言:var および :=
Go では、変数を宣言する方法として、var キーワード(標準)と := 短縮宣言(省略形)の 2 つの方法が用意されています。
(1) var キーワードの宣言(標準的な方法)
var は関数内やパッケージレベルで使用でき、記述方法は3通りあります:
// Method 1: Declare a variable with an explicit type
var name string = "Alice"
// Method 2: Declare a variable and omit the type (the type is inferred from the value)
var name = "Alice"
// Method 3: Declare only; initialize to zero
var name string // name = ""
▶ サンプル:var の3つの表記方法の比較
package main
import "fmt"
func main() {
// Method 1: Explicit Type + Initialization
var userName string = "Alice"
var userAge int = 28
// Method 2: Type Inference
var userEmail = "alice@example.com"
// Method 3: Declare only; rely on the default value
var userBio string // ""
fmt.Println(userName, userAge, userEmail, userBio)
}
出力:
Alice 28 alice@example.com
(2) := 簡潔な宣言(最も一般的)
:= は Go における構文糖衣であり、関数内でのみ使用可能で、型を自動的に推論します:
package main
import "fmt"
func main() {
// Short declaration: variable_name := value
name := "Alice" // 推定されるのは string
age := 28 // 推定されるのは int
height := 165.5 // 推定されるのは float64
isActive := true // 推定されるのは bool
fmt.Printf("%s is %d years old, height %.1f, active: %v\n",
name, age, height, isActive)
}
出力:
Alice is 28 years old, height 165.5, active: true
(3) var と :=――どちらを選べばいいですか?
| シナリオ | 推奨されるアプローチ | 理由 |
|---|---|---|
| スコープレベルの変数 | var |
:= 関数の外では使用できない |
| 関数内のローカル変数 | := |
簡潔で、4文字節約できる |
| ゼロでなければならない | var x int |
:= 同時に初期化する必要がある |
| 明示的な型変換が必要 | var x int = int(3.14) |
型変換がはっきりと確認できる |
| 一括宣言 | var (...) |
:= は一括宣言に対応していません |
4. Goの基本データ型
Goには14種類の基本データ型があり、その用途に応じて4つの主要なカテゴリに分類されます。
(1) 整数型
graph TB
A[整数型] --> B[符号付き int]
A --> C[符号なし uint]
A --> D[バイト型 byte]
A --> E[特殊 rune]
B --> B1[int<br/>少なくとも 32 位]
B --> B2[int8 / int16 / int32 / int64]
C --> C1[uint<br/>少なくとも 32 位]
C --> C2[uint8 / uint16 / uint32 / uint64]
D --> D1[byte = uint8<br/>強調するのは「バイト」です]
E --> E1[rune = int32<br/>強調は Unicode コードポイント]
| 種類 | サイズ | 適用範囲 | 代表的な用途 |
|---|---|---|---|
int |
32ビットまたは64ビット(プラットフォームに依存) | -2^31 から 2^31-1 またはそれ以上 | デフォルトの整数型 |
int8 |
8ビット | -128~127 | 範囲の狭い整数 |
int32 |
32ビット | -2^31 ~ 2^31-1 | クロスプラットフォームの固定長 |
int64 |
64ビット | -2^63 ~ 2^63-1 | 大きな整数、タイムスタンプ |
uint |
32ビットまたは64ビット | 0 から 2^32-1 またはそれ以上 | デフォルトでは符号なし |
byte |
8ビット | 0~255 | バイト、ASCII文字 |
rune |
32ビット | ユニコードコードポイント | 単一のユニコード文字 |
▶ サンプル:int と int64 の違い
package main
import (
"fmt"
"unsafe"
)
func main() {
var a int = 100
var b int64 = 200
fmt.Printf("a (int) size: %d bytes\n", unsafe.Sizeof(a))
fmt.Printf("b (int64) size: %d bytes\n", unsafe.Sizeof(b))
// You cannot directly add `int` and `int64`!
// sum := a + b // ❌ mismatched types int and int64
// An explicit conversion is required
sum := a + int(b)
fmt.Println("sum:", sum)
}
出力(64ビットプラットフォームの場合):
a (int) size: 8 bytes
b (int64) size: 8 bytes
sum: 300
(2) 浮動小数点型
| 種類 | サイズ | 精度 | 代表的な用途 |
|---|---|---|---|
float32 |
32ビット | 有効桁数約7桁 | 科学計算、グラフィックス |
float64 |
64ビット | 有効桁数約15 | デフォルトの浮動小数点型 |
(3) ブール型
var isActive bool = true
var hasPermission bool // ゼロ値 = false
(4) 文字列型
var greeting string = "Hello, Go!"
var emptyString string // ゼロ値 = ""(空の文字列)
var multiLine string = `1行目
2行目
3行目` // バッククォートは複数行に対応しています
(5) 14種類のクイックリファレンス表
| カテゴリ | タイプ | ゼロ値 |
|---|---|---|
| 整数 | int / int8 / int16 / int32 / int64 |
0 |
| 符号なし整数 | uint / uint8 / uint16 / uint32 / uint64 |
0 |
| バイト/文字 | byte (=uint8) / rune (=int32) |
0 |
| 浮動小数点 | float32 / float64 |
0.0 |
| ブール値 | bool |
false |
| 文字列 | string |
"" |
5. Go言語特有のゼロ値の仕組み
Goには「初期化されていない」変数は存在しません。すべての変数は、宣言された時点で妥当なデフォルト値を持っています。
▶ サンプル:すべての型の値がゼロの場合
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
var p *int // 指針のゼロ値は nil
fmt.Printf("int: %d\n", i)
fmt.Printf("float64: %f\n", f)
fmt.Printf("bool: %v\n", b)
fmt.Printf("string: %q\n", s) // %q 引用符で囲まれた文字列を表示する,空の文字列の表示 ""
fmt.Printf("pointer: %v\n", p)
}
出力:
int: 0
float64: 0.000000
bool: false
string: ""
pointer: <nil>
(1) ゼロ値と他の言語との比較
| 型 | ヌル値 | Java | Python | C/C++ |
|---|---|---|---|---|
| 整数 | 0 | 0 | 存在しない | 未定義(ゴミ値) |
| 浮動小数点 | 0.0 | 0.0 | 存在しない | 未定義 |
| ブール値 | false | false | False | 未定義 |
| 文字列 | "" | null | "" | 未定義 |
| ポインタ | nil | null | None | NULL(ただし、不正なポインタになりやすい) |
6. const 定数と iota 列挙型
(1) const を使用して定数を宣言する
定数はコンパイル時に決定され、変更することはできません。また、基本型でなければなりません:
const Pi = 3.14159
const AppName = "web-tutorial"
const MaxConnections = 1000
(2) Iota列挙:Goの洗練された列挙方法
iota は、Go における定数カウンターであり、0 から始まり、各行ごとに自動的に 1 ずつ増加します:
package main
import "fmt"
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
func main() {
fmt.Printf("Sunday=%d Monday=%d Saturday=%d\n", Sunday, Monday, Saturday)
}
出力:
Sunday=0 Monday=1 Saturday=6
▶ サンプル:カスタム・イオタ増分(HTTPステータスコード)
package main
import "fmt"
const (
StatusOK = iota * 100 // 0
StatusRedirect // 100
StatusClientError // 200
StatusServerError // 300
)
func main() {
fmt.Printf("OK=%d Redirect=%d ClientError=%d ServerError=%d\n",
StatusOK, StatusRedirect, StatusClientError, StatusServerError)
}
出力:
OK=0 Redirect=100 ClientError=200 ServerError=300
(3) iotaの応用:ビットマスク(ファイルのアクセス権)
const (
ReadPermission = 1 << iota // 1 << 0 = 1
WritePermission // 1 << 1 = 2
ExecutePermission // 1 << 2 = 4
)
func main() {
// Permission Combination: Read + Write = 1 | 2 = 3
rw := ReadPermission | WritePermission
fmt.Printf("Read=%d Write=%d Execute=%d RW=%d\n",
ReadPermission, WritePermission, ExecutePermission, rw)
}
出力:
Read=1 Write=2 Execute=4 RW=3
iota は、Go において「enum」に最も近い構文ですが、Python や Java の enum 型ほど厳密ではありません。本番環境でより強力な型安全性を必要とする場合は、独自の型を定義することができます。
7. 明示的な型変換
Goでは暗黙的な型変換は許可されていません。すべての変換は明示的に宣言する必要があります。これは、Goの「暗黙的よりも明示的」という原則を反映したものです。
(1) 数値型の変換
package main
import "fmt"
func main() {
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
fmt.Printf("i=%d f=%v u=%d\n", i, f, u)
// Truncation example: Converting a float to an int results in the loss of the decimal part
var pi float64 = 3.14159
var truncated = int(pi)
fmt.Printf("pi=%.5f truncated=%d\n", pi, truncated)
}
出力:
i=42 f=42 u=42
pi=3.14159 truncated=3
(2) 文字列の変換:strconv パッケージ
package main
import (
"fmt"
"strconv"
)
func main() {
// String → Number
n, err := strconv.Atoi("123")
fmt.Printf("n=%d err=%v\n", n, err)
// Numbers → Strings
s := strconv.Itoa(456)
fmt.Printf("s=%s (type: %T)\n", s, s)
// String → float
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Printf("f=%f\n", f)
// float → string (specified precision)
fmt.Println(strconv.FormatFloat(3.14159, 'f', 2, 64)) // "3.14"
}
出力:
n=123 err=<nil>
s=456 (type: string)
f=3.140000
3.14
(3) 暗黙の型変換と明示的な型変換
| シナリオ | Java/C++ | Go |
|---|---|---|
int + float |
自動的にfloat型に変換される | コンパイルエラー;明示的に指定する必要がある |
int32 + int64 |
自動的に int64 に変換されました | コンパイルエラー |
int から string へ |
暗黙的 | strconv.Itoa() が必要 |
8. fmt: 出力の書式設定
(1) よく使われる書式設定を表す動詞
| 動詞 | 意味 | 適用可能なタイプ |
|---|---|---|
%d |
10進整数 | int, int8/16/32/64 |
%f |
浮動小数点数 | float32, float64 |
%s |
文字列 | 文字列 |
%v |
すべてのタイプのデフォルト形式 | すべてのタイプ |
%T |
タイプ | 任意のタイプ |
%t |
ブール型 | bool |
%q |
引用された文字列 | 文字列、ルーン |
%x |
16進数 | int, []byte |
%p |
ポインタアドレス | ポインタ |
%% |
パーセント記号そのもの | — |
▶ サンプル:fmt.Printf の全機能を紹介するデモ
package main
import "fmt"
func main() {
name := "Alice"
age := 28
height := 165.5
isActive := true
hobbies := []string{"coding", "reading", "hiking"}
fmt.Printf("Name: %s\n", name) // %s 文字列
fmt.Printf("Age: %d\n", age) // %d 整数
fmt.Printf("Height: %.1f cm\n", height) // %.1f 浮動小数点数の精度 1 位
fmt.Printf("Active: %t\n", isActive) // %t ブール
fmt.Printf("Hobbies: %v\n", hobbies) // %v デフォルトの書式
fmt.Printf("Name type: %T\n", name) // %T 種類
fmt.Printf("100%% complete!\n") // %% 文字通り %
// %v Advanced usage: %+v displays the field name (struct), %#v displays Go syntax
fmt.Printf("hobbies: %#v\n", hobbies)
}
出力:
Name: Alice
Age: 28
Height: 165.5 cm
Active: true
Hobbies: [coding reading hiking]
Name type: string
100% complete!
hobbies: []string{"coding", "reading", "hiking"}
(2) Print、Println、Printf の違い
| 機能 | 出力の終端処理 | 使用例 |
|---|---|---|
fmt.Print() |
改行なし | 連続出力 |
fmt.Println() |
自動改行 + スペース区切り | 簡単なデバッグ |
fmt.Printf() |
改行を追加しない;\n が必要 |
書式付き出力 |
fmt.Sprintf() |
文字列を返す。出力は行わない | 文字列を連結する |
9. 完全な例:ユーザー登録フォーム
このレッスンで学んだ重要な概念をすべてまとめ、ユーザー登録データ収集プログラムを作成してください:
// main.go
package main
import (
"fmt"
"strconv"
)
// Defining User Role Enumerations Using Iota
const (
RoleGuest = iota // 0 訪問者
RoleUser // 1 一般ユーザー
RoleAdmin // 2 管理者
RoleSuperAdmin // 3 スーパー管理者
)
func main() {
// 1. Use the := short declaration for user information
name := "Alice"
age := 28
height := 165.5
isActive := true
role := RoleUser
// 2. Explicit Type Conversion
ageAsFloat := float64(age)
heightAsString := strconv.FormatFloat(height, 'f', 1, 64)
// 3. const Constants
const MaxLoginAttempts = 5
// 4. fmt: Formatted Output
fmt.Println("========================================")
fmt.Println(" User Registration Form")
fmt.Println("========================================")
fmt.Printf("Name: %s\n", name)
fmt.Printf("Age: %d (%.0f years old)\n", age, ageAsFloat)
fmt.Printf("Height: %s cm\n", heightAsString)
fmt.Printf("Active: %t\n", isActive)
fmt.Printf("Role: %d (User)\n", role)
fmt.Printf("Max Attempts: %d\n", MaxLoginAttempts)
fmt.Printf("Type Check: name is %T, age is %T\n", name, age)
fmt.Println("========================================")
// 5. Demonstration of Zero Values for Variables
var uninitializedAge int
fmt.Printf("\nUninitialized age (zero value): %d\n", uninitializedAge)
}
期待される出力:
========================================
User Registration Form
========================================
Name: Alice
Age: 28 (28 years old)
Height: 165.5 cm
Active: true
Role: 1 (User)
Max Attempts: 5
Type Check: name is string, age is int
========================================
Uninitialized age (zero value): 0
strconv.Atoi("abc") を実行するとエラーが発生しますが、このエラーは 決して無視してはいけません。正しい構文は n, err := strconv.Atoi("abc"); if err != nil { ... } です。第8課では、エラー処理についてさらに詳しく解説します。
❓ よくある質問
var と :=、どちらが良いですか?:= の使用をお勧めします。簡潔であり、初期化が強制されるためです。パッケージレベルの変数には var を使用する必要があります。どちらによって生成されるバイトコードもまったく同じであり、単なる構文上の糖衣に過ぎません。intのサイズは、32ビットプラットフォームと64ビットプラットフォームで異なります。これによりどのような問題が発生する可能性がありますか?int32またはint64を明示的に指定することをお勧めします。enumというキーワードがありません。列挙型はどのように作成すればよいですか?const + iotaを使用してください。Javaのenum classほど厳密ではありませんが、十分です。型安全性を確保したい場合は、type Role intのようなカスタム型と一連の定数を組み合わせて使用することができます。iota の値は 0 から始まりますが、1 から始めることはできますか?_ = iota を使用すると 0 をスキップできます。また、iota + 1: const (A = iota + 1; B; C) と指定すると、A=1、B=2、C=3 となります。これは、HTTP ステータスコードや月など、値を 1 から開始する必要がある場面で一般的に使用されます。int演算とlong演算を混在させた際の精度損失など)。Goの厳格な型変換により、コードの可読性が高まり、バグの発生も減少します。string と []byte の相互変換はどのように行いますか?[]byte(s) は文字列をバイトスライスに変換し(データをコピーします)、string(b) はバイトスライスを文字列に変換します。これらは、ネットワーク I/O や暗号化の場面で一般的に使用されます。注:[]byteは変更に適しており、stringは読み取り専用での使用に適しています。&Pi を実行すると、コンパイル時にエラーが発生します。📖 まとめ
- Goでは、変数は
varまたは:=のいずれかを使用して宣言できます。関数内では:=の使用が推奨され、パッケージレベルではvarの使用が必須となります。 - 4つのカテゴリに分類される14の基本型:整数(int/int8~64)、浮動小数点数(float32/64)、ブール値、および文字列
- ゼロ値メカニズムはGo言語の安全機能の一つです。すべての変数には妥当なデフォルト値が設定されており、これにより無効なポインタが排除されます。
constは定数を宣言し、iotaは(HTTPステータスコードやビットマスクなどを)列挙するための洗練された方法を提供します。- Go言語では暗黙の型変換は許可されておらず、明示的に行う必要があります(
int(x)/strconv.Itoa()) fmt.Printfは、出力制御のためにフォーマット指定子 (%d,%f,%s,%v,%T,%q,%t) を使用します- Goは静的かつ強型付けの言語であり、エラーの90%をコンパイル時に検出できるため、共同ソフトウェア開発の基盤となっています
📝 練習問題
-
基本演習(難易度 ⭐):
varおよび:=の両方の構文を使用して、5つの変数(name/age/height/isStudent/gpa)を宣言し、それらの値と型を出力してください。 -
応用演習(難易度 ⭐⭐):
iotaを使用して 4 つの HTTP ステータスコード定数(200/404/500/503)を定義し、ステータスコードに応じて対応するテキスト(例:「OK」、 「Not Found」、「Internal Server Error」、または「Service Unavailable」)を返すhandleRequest(code int)関数を記述してください。 -
チャレンジ問題(難易度 ⭐⭐⭐):単位変換プログラムを作成してください。華氏温度を入力として受け取り、摂氏温度を出力します(計算式:C = (F - 32) × 5/9)。要件:(1) 入力の読み取りには
fmt.Scanfを使用すること;(2) 型変換にはstrconv.ParseFloatを使用すること;(3) 無効な入力への対処を行うこと(エラーを返す)。



