変数とデータ型

キッチンで料理をしている場面を想像してください:変数はラベル付きの保存ボックスです — ラベルが名前で、ボックスにデータが入っています;データ型はボックスの形です — 丸いボックスにはスープが、四角いボックスにはおかずが入り、混ぜることはできません。Goは「ボックス」の管理に非常に厳格です:どのボックス(型)が必要か宣言し、どの内容を入れるか(代入)を指定し、空のボックスにもデフォルトの内容(ゼロ値)があります。


1. コアコンセプト

1.1 変数宣言:var vs :=

Goには変数を宣言する2つの方法があります:

方法 構文 ユースケース スコープ
var var name int パッケージレベル変数、明示的な型が必要 関数内外
短縮宣言 := name := 10 ローカル変数、型推論 関数内のみ

主な違い:

1.2 基本データ型

カテゴリ 説明
整数 int, int8, int16, int32, int64 符号付き整数 var a int = 42
符号なし整数 uint, uint8, uint16, uint32, uint64 非負整数 var b uint = 100
浮動小数点 float32, float64 小数(デフォルトfloat64 var c float64 = 3.14
ブール値 bool true / false var d bool = true
文字列 string 不変のバイトシーケンス var e string = "hello"
バイト byte uint8のエイリアス、1バイトを表現 var f byte = 'A'
Unicode文字 rune int32のエイリアス、1つのUnicodeコードポイントを表現 var g rune = '中'

プラットフォーム依存型: intuintは32ビットシステムでは32ビット、64ビットシステムでは64ビットです。日常開発では、ほとんどの場合intで十分です。

1.3 ゼロ値メカニズム

Goで変数が宣言されても代入されない場合、nullや未定義ではなくゼロ値に自動初期化されます:

ゼロ値
整数(int, uintなど) 0
浮動小数点(float32, float64 0.0
ブール値(bool false
文字列(string ""(空文字列)
ポインタ、スライス、map、チャネル、関数、インターフェース nil

これはGoの安全メカニズムです — 「値がない」変数が存在せず、多くのヌルポインターエラーを回避できます。

1.4 定数とiota

1.5 型変換

Goは暗黙の型変換をサポートしません。異なる型は明示的に変換する必要があります:

GO
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f)        // float64 → uint

1.6 fmtパッケージのフォーマット出力

fmtはGoで最もよく使われるフォーマットパッケージです。主要な関数:

関数 用途
fmt.Println 改行付きで出力 fmt.Println("hello")
fmt.Printf フォーマット出力(改行なし) fmt.Printf("value=%d", 42)
fmt.Sprintf 文字列にフォーマット(出力なし) s := fmt.Sprintf("%d", 42)

よく使うフォーマット指定子:%d(整数)、%f(浮動小数点)、%s(文字列)、%t(ブール値)、%T(型)、%v(汎用値)、%q(引用符付き文字列)。


2. 基本構文/使い方

変数宣言

GO
// 方法1:var宣言(関数内外で使用可能)
var name string       // 宣言、ゼロ値 ""
var age int = 25      // 宣言と代入
var score = 98.5      // 型推論でfloat64

// 方法2:短縮宣言 :=(関数内のみ)
city := "Beijing"     // 宣言と代入、stringと推論
count := 100          // intと推論

// 一括宣言
var (
    width  int     = 10
    height int     = 20
    area   float64
)
💡 ヒント: varで変数を宣言して使用しない場合、Goコンパイラはdeclared and not usedエラーを報告します。これはGoの設計哲学です:デッドコードを許可しません。短縮宣言:=也同样です。

定数宣言

GO
// 単一定数
const Pi = 3.14159
const Language string = "Go"

// 一括定数 + iota列挙
const (
    Sunday    = iota // 0
    Monday           // 1
    Tuesday          // 2
    Wednesday        // 3
    Thursday         // 4
    Friday           // 5
    Saturday         // 6
)
💡 ヒント: iotaconstブロック内でのみ有効で、新しい行ごとに1ずつ自動インクリメントされます。_を使ってスキップしても、iotaはインクリメントされます。

型変換

GO
var i int = 100
var f float64 = float64(i)  // 明示的変換:int → float64
var s string = string(rune(i)) // int → rune → string(注意:数値を文字列にはしません)
// 数値を文字列に変換する正しい方法:
s2 := fmt.Sprintf("%d", i)  // "100"
💡 ヒント: string(65)は文字"A"(ASCIIコード)を生成し、"65"ではありません。数値を10進数の文字列に変換するには、fmt.Sprintfまたはstrconv.Itoaを使用してください。


3. コード例

例1:基本的な使い方(難易度⭐)

シナリオ: 異なる型の変数を宣言し、その値と型を出力します。

GO
package main

import "fmt"

func main() {
    // ========== 変数宣言 ==========

    // 1. var宣言 + 明示的な型
    var name string = "Alice"
    var age int = 28

    // 2. var宣言 + 型推論(コンパイラが値から型を推論)
    salary := 15000.5 // float64と推論

    // 3. 短縮変数宣言(最も一般的)
    isEmployed := true
    city := "New York"

    // 4. ゼロ値のデモ — 代入なしで宣言
    var defaultInt int        // ゼロ値:0
    var defaultFloat float64  // ゼロ値:0
    var defaultBool bool      // ゼロ値:false
    var defaultString string  // ゼロ値:""

    // ========== 出力 ==========
    fmt.Println("=== 個人情報 ===")
    fmt.Printf("名前: %s (型: %T)\n", name, name)
    fmt.Printf("年齢: %d (型: %T)\n", age, age)
    fmt.Printf("給与: %.1f (型: %T)\n", salary, salary)
    fmt.Printf("雇用状況: %t (型: %T)\n", isEmployed, isEmployed)
    fmt.Printf("都市: %s (型: %T)\n", city, city)

    fmt.Println("\n=== ゼロ値デモ ===")
    fmt.Printf("int ゼロ値:    %d\n", defaultInt)
    fmt.Printf("float64 ゼロ値: %v\n", defaultFloat)
    fmt.Printf("bool ゼロ値:   %t\n", defaultBool)
    fmt.Printf("string ゼロ値: %q\n", defaultString) // %qは引用符付き文字列を表示
}
▶ 試してみよう

出力:

TEXT
=== 個人情報 ===
名前: Alice (型: string)
年齢: 28 (型: int)
給与: 15000.5 (型: float64)
雇用状況: true (型: bool)
都市: New York (型: string)

=== ゼロ値デモ ===
int ゼロ値:    0
float64 ゼロ値: 0
bool ゼロ値:   false
string ゼロ値: ""

要点:


例2:中級的な使い方(難易度⭐⭐)

シナリオ: 定数、iotaを使った列挙型の定義、実践的な型変換を使用します。

GO
package main

import "fmt"

// ========== iota列挙:ロール権限の定義 ==========

type Role int // intを基本型とするカスタム型

const (
    Guest    Role = iota // 0:ゲスト
    Member               // 1:メンバー
    Admin                // 2:管理者
    SuperAdmin           // 3:スーパー管理者
)

// RoleにString()メソッドを実装し、出力を容易にする
func (r Role) String() string {
    names := [...]string{"Guest", "Member", "Admin", "Super Admin"}
    if int(r) < len(names) {
        return names[r]
    }
    return "Unknown Role"
}

// ========== iotaの高度な使い方:ビット権限フラグ ==========

type Permission int

const (
    Read    Permission = 1 << iota // 1 (001)
    Write                          // 2 (010)
    Execute                        // 4 (100)
)

// ========== 実践的な型変換 ==========

func main() {
    // --- iota列挙の使用 ---
    var userRole Role = Admin
    fmt.Printf("ユーザーロール: %s (値: %d)\n", userRole, userRole)

    // --- ビット権限の組み合わせ ---
    userPerm := Read | Write // 組み合わせ権限:Read + Write = 3 (011)
    fmt.Printf("\nユーザー権限値: %d (バイナリ: %03b)\n", userPerm, userPerm)
    fmt.Printf("読み取り権限あり? %t\n", userPerm&Read != 0)
    fmt.Printf("実行権限あり? %t\n", userPerm&Execute != 0)

    // --- 型変換 ---
    var temperature float64 = 36.6
    var intTemp int = int(temperature) // float64 → int(切り捨て、四捨五入ではない)
    fmt.Printf("\n体温: %.1f°C → 整数部分: %d\n", temperature, intTemp)

    // 数値 → 文字列(2つの方法)
    var code int = 65
    char := string(rune(code))      // 方法1:対応するUnicode文字に変換 → "A"
    numStr := fmt.Sprintf("%d", code) // 方法2:数値文字列に変換 → "65"
    fmt.Printf("数値 %d → 文字: %s, 文字列: %q\n", code, char, numStr)

    // --- 文字列とバイト/文字 ---
    s := "Hello,世界"
    fmt.Printf("\n文字列 %q の長さ: %d バイト\n", s, len(s))
    fmt.Println("バイト単位の走査(CJKには推奨されません):")
    for i := 0; i < len(s); i++ {
        fmt.Printf("  [%d] %d %c\n", i, s[i], s[i])
    }
    fmt.Println("rune単位の走査(CJKを正しく処理):")
    for i, r := range s {
        fmt.Printf("  [%d] %U '%c'\n", i, r, r)
    }
}
▶ 試してみよう

出力:

TEXT
ユーザーロール: Admin (値: 2)

ユーザー権限値: 3 (バイナリ: 011)
読み取り権限あり? true
実行権限あり? false

体温: 36.6°C → 整数部分: 36
数値 65 → 文字: A, 文字列: "65"

文字列 "Hello,世界" の長さ: 12 バイト
バイト単位の走査(CJKには推奨されません):
  [0] 72 H
  [1] 101 e
  [2] 108 l
  [3] 108 l
  [4] 111 o
  [5] 44 ,
  [6] 228 ä
  [7] 184 ¸
  [8] 150 \x96
  [9] 228 ä
  [10] 150 \x96
  [11] 175 ¯
rune単位の走査(CJKを正しく処理):
  [0] U+0048 'H'
  [1] U+0065 'e'
  [2] U+006C 'l'
  [3] U+006C 'l'
  [4] U+006F 'o'
  [5] U+002C ','
  [6] U+4E16 '世'
  [7] U+754C '界'

要点:


例3:総合応用(難易度⭐⭐⭐)

シナリオ: 変数、定数、型変換、フォーマット出力を組み合わせた、シンプルなユーザー登録情報処理システムをシミュレートします。

GO
package main

import (
	"fmt"
	"strings"
)

// ========== 定数 ==========

// 性別列挙型
type Gender int

const (
	Unknown Gender = iota // 0:不明
	Male                  // 1:男性
	Female                // 2:女性
)

func (g Gender) String() string {
	return [...]string{"Unknown", "Male", "Female"}[g]
}

// 会員レベルと対応する割引
const (
	LevelBronze   = 1
	LevelSilver   = 2
	LevelGold     = 3
	LevelPlatinum = 4
)

// iotaで割引パーセンテージを生成
const (
	_                   = iota // 0をスキップ
	DiscountBronze   = 95      // 5%割引
	DiscountSilver   = 90      // 10%割引
	DiscountGold     = 85      // 15%割引
	DiscountPlatinum = 80      // 20%割引
)

// ========== ユーザー構造体(プレビュー、詳細は後続レッスンで) ==========

type User struct {
	Name     string
	Age      int
	Gender   Gender
	Level    int
	Balance  float64
}

// ========== コアロジック ==========

func main() {
	// シミュレーションデータ
	users := []User{
		{Name: "Li Ming", Age: 25, Gender: Male, Level: LevelGold, Balance: 1580.50},
		{Name: "Wang Fang", Age: 30, Gender: Female, Level: LevelPlatinum, Balance: 3200.00},
		{Name: "Zhang Wei", Age: 17, Gender: Male, Level: LevelBronze, Balance: 200.00},
		{Name: "Zhao Min", Age: 22, Gender: Female, Level: LevelSilver, Balance: 800.75},
	}

	fmt.Println("╔══════════════════════════════════════════════════════════╗")
	fmt.Println("║              ユーザー登録システム                         ║")
	fmt.Println("╚══════════════════════════════════════════════════════════╝")
	fmt.Println()

	// 統計変数
	var totalAge int
	var totalBalance float64
	var maleCount, femaleCount int
	var adultCount int

	for _, u := range users {
		// 割引を取得
		discount := getDiscount(u.Level)
		discountAmount := u.Balance * float64(100-discount) / 100.0

		// フォーマット出力
		ageTag := ""
		if u.Age >= 18 {
			ageTag = "✅ 成人"
			adultCount++
		} else {
			ageTag = "❌ 未成年"
		}

		fmt.Printf("👤 %-8s | 性別: %-6s | 年齢: %d (%s) | レベル: Lv.%d | 残高: $%.2f\n",
			u.Name, u.Gender, u.Age, ageTag, u.Level, u.Balance)
		fmt.Printf("   💰 %d%%割引、$%.2f節約\n", discount, discountAmount)

		// 統計
		totalAge += u.Age
		totalBalance += u.Balance
		switch u.Gender {
		case Male:
			maleCount++
		case Female:
			femaleCount++
		}

		fmt.Println(strings.Repeat("-", 60))
	}

	// 統計を出力
	avgAge := float64(totalAge) / float64(len(users))
	fmt.Println()
	fmt.Println("📊 統計:")
	fmt.Printf("   総ユーザー数: %d\n", len(users))
	fmt.Printf("   男性: %d | 女性: %d\n", maleCount, femaleCount)
	fmt.Printf("   成人: %d | 未成年: %d\n", adultCount, len(users)-adultCount)
	fmt.Printf("   平均年齢: %.1f歳\n", avgAge)
	fmt.Printf("   総残高: $%.2f\n", totalBalance)
	fmt.Printf("   平均残高: $%.2f\n", totalBalance/float64(len(users)))

	// 型変換とフォーマットのデモ
	fmt.Println()
	fmt.Println("🔧 型変換デモ:")
	demoTypeConversion()
}

// getDiscountは会員レベルに基づいて割引を返します
func getDiscount(level int) int {
	switch level {
	case LevelBronze:
		return DiscountBronze
	case LevelSilver:
		return DiscountSilver
	case LevelGold:
		return DiscountGold
	case LevelPlatinum:
		return DiscountPlatinum
	default:
		return 100 // 割引なし
	}
}

// demoTypeConversionは様々な型変換シナリオをデモンストレーションします
func demoTypeConversion() {
	// 1. 数値型変換
	var pi float64 = 3.14159
	var intPi int = int(pi) // 切り捨て、四捨五入ではない
	fmt.Printf("   float64 → int: %.5f → %d\n", pi, intPi)

	// 2. 数値 → 文字列(2つの方法の比較)
	var num int = 2024
	way1 := string(rune(num))      // Unicode文字 → 想定とは異なる
	way2 := fmt.Sprintf("%d", num) // 10進数文字列 → "2024"
	way3 := fmt.Sprintf("%x", num) // 16進数文字列 → "7e8"
	fmt.Printf("   int → string (rune):  %q\n", way1)
	fmt.Printf("   int → string (%%d):    %q\n", way2)
	fmt.Printf("   int → string (%%x):    %q\n", way3)

	// 3. 文字列 → 数値(fmt.Sscanfを使用)
	var parsed int
	_, err := fmt.Sscanf("12345", "%d", &parsed)
	if err == nil {
		fmt.Printf("   string → int (Sscanf): %d\n", parsed)
	}

	// 4. bool → 文字列
	b := true
	boolStr := fmt.Sprintf("%t", b)
	fmt.Printf("   bool → string: %t → %q\n", b, boolStr)

	// 5. runeとbyteの違い
	var r rune = '中'
	var b2 byte = 'A'
	fmt.Printf("   rune '中': 値=%d, 16進=%U, %dバイト (UTF-8)\n", r, r, len(string(r)))
	fmt.Printf("   byte 'A': 値=%d, 16進=%U, 1バイト\n", b2, b2)
}
▶ 試してみよう

出力:

TEXT
╔══════════════════════════════════════════════════════════╗
║              ユーザー登録システム                         ║
╚══════════════════════════════════════════════════════════╝

👤 Li Ming   | 性別: Male   | 年齢: 25 (✅ 成人) | レベル: Lv.3 | 残高: $1580.50
   💰 15%割引、$237.08節約
------------------------------------------------------------
👤 Wang Fang | 性別: Female | 年齢: 30 (✅ 成人) | レベル: Lv.4 | 残高: $3200.00
   💰 20%割引、$640.00節約
------------------------------------------------------------
👤 Zhang Wei | 性別: Male   | 年齢: 17 (❌ 未成年) | レベル: Lv.1 | 残高: $200.00
   💰 5%割引、$10.00節約
------------------------------------------------------------
👤 Zhao Min  | 性別: Female | 年齢: 22 (✅ 成人) | レベル: Lv.2 | 残高: $800.75
   💰 10%割引、$80.08節約
------------------------------------------------------------

📊 統計:
   総ユーザー数: 4
   男性: 2 | 女性: 2
   成人: 3 | 未成年: 1
   平均年齢: 23.5歳
   総残高: $5781.25
   平均残高: $1445.31

🔧 型変換デモ:
   float64 → int: 3.14159 → 3
   int → string (rune):  "\u07e8"
   int → string (%d):    "2024"
   int → string (%x):    "7e8"
   string → int (Sscanf): 12345
   bool → string: true → "true"
   rune '中': 値=20013, 16進=U+4E16, 3バイト (UTF-8)
   byte 'A': 値=65, 16進=U+0041, 1バイト

要点:


3. よくあるユースケース

ケース1:設定管理

GO
package main

import "fmt"

// アプリケーション設定定数(コンパイル時に確定、不変)
const (
	AppName    = "GoWeb"
	Version    = "1.0.0"
	MaxRetries = 3
	DefaultTimeout = 30 // 秒
)

// 環境列挙型
type Env int

const (
	Dev  Env = iota // 開発
	Staging         // ステージング
	Prod            // 本番
)

func (e Env) String() string {
	return [...]string{"Dev", "Staging", "Prod"}[e]
}

func main() {
	// 実行時変数
	currentEnv := Prod
	dbHost := "192.168.1.100"
	dbPort := 3306
	debug := currentEnv == Dev

	fmt.Printf("アプリ: %s v%s\n", AppName, Version)
	fmt.Printf("環境: %s\n", currentEnv)
	fmt.Printf("データベース: %s:%d\n", dbHost, dbPort)
	fmt.Printf("デバッグモード: %t\n", debug)
	fmt.Printf("最大リトライ: %d、タイムアウト: %d秒\n", MaxRetries, DefaultTimeout)
}

ケース2:データフォーマット&レポート

GO
package main

import "fmt"

func main() {
	// シミュレーション学生の成績データ
	type Student struct {
		Name  string
		Score float64
		Grade string
	}

	students := []Student{
		{"Alice", 92.5, ""},
		{"Bob", 87.0, ""},
		{"Charlie", 76.5, ""},
		{"Diana", 95.0, ""},
		{"Eve", 63.0, ""},
	}

	// 点数に基づいて自動的に成績を割り当て
	for i := range students {
		switch {
		case students[i].Score >= 90:
			students[i].Grade = "A (優秀)"
		case students[i].Score >= 80:
			students[i].Grade = "B (良好)"
		case students[i].Score >= 70:
			students[i].Grade = "C (平均)"
		case students[i].Score >= 60:
			students[i].Grade = "D (可)"
		default:
			students[i].Grade = "F (不可)"
		}
	}

	// 成績レポートを出力
	fmt.Println("┌─────────┬────────┬────────────┐")
	fmt.Println("│  名前   │ 点数   │   成績     │")
	fmt.Println("├─────────┼────────┼────────────┤")
	var total float64
	for _, s := range students {
		fmt.Printf("│ %-7s │ %6.1f │ %-10s │\n", s.Name, s.Score, s.Grade)
		total += s.Score
	}
	fmt.Println("├─────────┼────────┼────────────┤")
	fmt.Printf("│ 平均    │ %6.1f │            │\n", total/float64(len(students)))
	fmt.Println("└─────────┴────────┴────────────┘")
}

❓ よくある質問

質問1:var:=のどちらを使うべきですか?(概念的な混乱)

回答: このシンプルな原則に従ってください:

シナリオ 推奨 理由
関数内で宣言と代入 := より簡潔、Goコミュニティの慣例
関数内で代入なしの宣言 var :=は代入が必要
パッケージレベル変数 var :=は関数外では使用不可
明示的な型指定が必要 var 予期しない型推論を回避
GO
func example() {
    // ✅ 推奨:関数内では := を使用
    name := "Go"
    count := 0

    // ✅ var を使用:代入なしの宣言
    var result string
    if someCondition {
        result = "yes"
    } else {
        result = "no"
    }

    // ✅ var を使用:正確な型が必要
    var b byte = 255 // := だと int と推論される
}

質問2:なぜstring(65)"65"と等しくないのですか?(よくある落とし穴)

回答: string(int)は整数をUnicodeコードポイントとして扱い、数値のテキスト表現としては扱いません:

GO
// ❌ 誤った想定
s1 := string(65) // "65"を期待、実際は"A"(Unicode U+0041)

// ✅ 正しい方法
s2 := fmt.Sprintf("%d", 65)  // "65"
s3 := strconv.Itoa(65)       // "65"("strconv"のインポートが必要)

// string(rune)の実際の用途:Unicodeコードポイント → 文字
ch := string(rune(20013)) // "中" (U+4E16)

質問3:なぜlen("Hello,世界")は8ではないのですか?(よくある落とし穴)

回答: len()バイト数を返し、文字数ではありません。Goの文字列はデフォルトでUTF-8エンコーディングを使用し、CJK文字はそれぞれ3バイトを占めます:

GO
s := "Hello,世界"
fmt.Println(len(s))          // 12 (5 + 1 + 3×2 = 12バイト)
fmt.Println(utf8.RuneCountInString(s)) // 8 (5 + 1 + 2 = 8文字)

// 正しい文字数の数え方
count := 0
for range s {
    count++
}
fmt.Println(count) // 8

質問4:2つの変数を素早くスワップするには?(実践的なヒント)

回答: Goは多重代入をサポートしており、1行のコードで実現できます:

GO
a, b := 10, 20
a, b = b, a // スワップ:a=20, b=10

// 値を無視することも可能です
_, err := someFunction() // _を使って不要な戻り値を無視

📖 まとめ


📝 演習

演習1(⭐):変数とゼロ値

以下の変数を宣言し、そのゼロ値を出力してください:

期待される出力:

TEXT
count: 0
price: 0
active: false
message: ""

演習2(⭐⭐):iota列挙型と型変換

Color型(基本型int)を定義し、iotaを使って3つの色を定義してください:Red=0Green=1Blue=2ColorString()メソッドを実装してください。そして:

  1. Color変数をGreenに設定し、その名前と値を出力してください。
  2. float643.14159intに変換し、結果を出力してください(3になるはずです)。
  3. 数値2025を文字列"2025"に変換し、結果を出力してください。

演習3(⭐⭐⭐):温度変換ツール

以下のプログラムを書いてください:

  1. 定数AbsoluteZeroC = -273.15(摂氏の絶対零度)を定義してください。
  2. 摂氏温度のセットを宣言してください:-40, 0, 37, 100
  3. 温度配列をイテレーションし、各摂氏温度を華氏に変換してください(式:F = C × 9/5 + 32)。
  4. テーブル形式でフォーマット出力し、要件:
    • 摂氏と華氏の温度を小数点以下1桁に丸める
    • 温度が絶対零度以下かどうかを記載(物理的に不可能)
    • 列幅を揃える

期待される出力:

TEXT
┌─────────┬─────────┬────────────┐
│  摂氏   │  華氏   │   状態     │
├─────────┼─────────┼────────────┤
│  -40.0  │  -40.0  │ 有効      │
│    0.0  │   32.0  │ 有効      │
│   37.0  │   98.6  │ 有効      │
│  100.0  │  212.0  │ 有効      │
└─────────┴─────────┴────────────┘

ヒント: constvar(配列/スライス)、forループ、fmt.Printfフォーマット、型変換(intfloat64)を使用してください。


次のレッスン

レッスン3:制御フロー — if/else、switch、forループ

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%