Day 2: 変数と型 - 解説

学習の目的

このDayで達成すること

Day 2を完了すると、以下ができるようになります:

  • 変数を使ってデータを保存できる
- プログラムの柔軟性が大幅に向上 - 同じ値を何度も書かなくて済む

  • 適切なデータ型を選択できる
- 整数、小数、文字列、真偽値の使い分け - 型に応じた操作の理解

  • 値の更新と計算ができる
- 変数の値を変更する方法 - 基本的な算術演算

プログラミングにおける変数の重要性

固定値のみ → 変数の使用 → 条件分岐 → 繰り返し → 関数 → ...
              ↑
           あなたは今ここ

変数はプログラミングの基礎中の基礎です。この概念なしには、次のステップに進めません。

---

学習の手引き

推奨学習順序

1. 背景知識を読む(20分)
   ↓
2. 課題に取り組む(90分)
   - 必須問題を全て解く
   - わからなければ解答を見る
   ↓
3. 解答と比較(20分)
   - 自分の解答との違いを確認
   - 別解も試してみる
   ↓
4. 評価基準で自己チェック(10分)

効果的な学習のコツ

  • 実際に手を動かす
- コードを読むだけでなく、必ず書いて実行 - エラーが出ても、それが学び

  • 値を変えて実験
- 数値を変えてみる - 変数名を変えてみる - 結果がどう変わるか観察

  • エラーを恐れない
- エラーメッセージは「ヒント」 - どんなエラーが出るか、わざと試してみる

---

概念の深掘り

変数とメモリ

変数は、コンピュータのメモリ上に確保された領域に名前をつけたものです。

メモリ
┌─────────┬─────────┬─────────┬─────────┐
│  age    │  name   │  score  │  ...    │
│   25    │ "太郎"  │   80    │         │
│(0x1000) │(0x1004) │(0x1008) │         │
└─────────┴─────────┴─────────┴─────────┘
  アドレス  アドレス   アドレス

プログラマは「メモリアドレス」を意識する必要はなく、変数名で値にアクセスできます。

静的型付けとは

Goは「静的型付け言語」です。これは:

  • 変数の型がコンパイル時に決まる
  • 一度決まった型は変更できない
  • 型の不一致はコンパイルエラーになる

age := 25        // int型として確定
age = "二十五"   // エラー!string型は代入できない

メリット:

  • バグを早期発見できる
  • コードが読みやすい
  • 実行速度が速い
  • 型推論の仕組み

    :=を使うと、右辺の値から型が自動推論されます。

    a := 42        // int(整数リテラル)
    b := 3.14      // float64(小数リテラル)
    c := "hello"   // string(文字列リテラル)
    d := true      // bool(真偽値リテラル)
    

    どの型になるかの規則:

    値の形式 推論される型
    `42` `int`
    `3.14` `float64`
    `"text"` `string`
    `true`/`false` `bool`

    ---

    重要なポイントの整理

    1. 宣言と代入の違い

    操作 記号 説明
    宣言+代入 `:=` `x := 10` 新しい変数を作る
    代入のみ `=` `x = 20` 既存の変数に値を入れる

    重要: 同じスコープ内で同じ変数を2回:=で宣言するとエラーになります。

    2. ゼロ値

    変数を宣言だけして初期化しない場合、型に応じた「ゼロ値」が設定されます。

    var count int      // count = 0
    var name string    // name = ""
    var active bool    // active = false
    var ratio float64  // ratio = 0.0
    

    3. 複合代入演算子

    計算して再代入する場合、短く書けます。

    // 通常の書き方
    score = score + 10
    score = score - 5
    score = score * 2
    score = score / 4
    
    // 短縮形
    score += 10
    score -= 5
    score *= 2
    score /= 4
    

    ---

    よくある質問(FAQ)

    Q1: intとint64の違いは?

    A: intはプラットフォームによってサイズが変わります。

  • 32ビットOS: 32ビット
  • 64ビットOS: 64ビット

int64は常に64ビットです。通常はintを使えば十分です。

Q2: floatとfloat64の違いは?

A: Goにはfloat型はありません。float32float64があり、デフォルトはfloat64です。

a := 3.14       // float64
var b float32 = 3.14  // 明示的にfloat32

Q3: 文字列と数値を連結するには?

A: fmt.Sprintfまたはstrconvパッケージを使います。

age := 25

// 方法1: Sprintf
text := fmt.Sprintf("年齢: %d歳", age)

// 方法2: strconv.Itoa(整数→文字列)
import "strconv"
text := "年齢: " + strconv.Itoa(age) + "歳"

Q4: 変数名に日本語は使える?

A: 使えますが、推奨されません。

年齢 := 25  // 動くが非推奨
age := 25   // 推奨

---

明日への準備

Day 3のテーマ

「条件分岐(if文)」を学びます。

条件分岐とは:

  • 「もし〜なら」という判断をプログラムに組み込む
  • 値に応じて異なる処理を実行
  • 予習コード:

    package main
    
    import "fmt"
    
    func main() {
        age := 25
    
        if age >= 20 {
            fmt.Println("成人です")
        } else {
            fmt.Println("未成年です")
        }
    }
    

    ageの値を変えて(例: 15, 20, 30)、結果がどう変わるか試してみてください。

    確認チェックリスト

    Day 2を終える前に、以下を確認してください:

  • [ ] :=で変数を宣言できる
  • [ ] =で値を再代入できる
  • [ ] int, float64, string, boolの違いがわかる
  • [ ] 基本的な計算(+, -, , /, %)ができる
  • [ ] fmt.Printfでフォーマット出力ができる

すべてにチェックが入れば、Day 3に進む準備ができています!

---

詳細なアルゴリズム分析

時間計算量と空間計算量

変数操作の計算量を理解することは、効率的なコードを書くために重要です。

基本操作の計算量

変数宣言と代入:

// 時間計算量: O(1) - 定数時間
// 空間計算量: O(1) - 固定サイズ
age := 25

// 時間計算量: O(1)
// 空間計算量: O(1)
name := "太郎"

// 時間計算量: O(1)
// 空間計算量: O(n) - nは文字列の長さ
longString := "非常に長い文字列..." // 長さに比例したメモリ

算術演算:

// 時間計算量: O(1) - CPU命令1回
a := 10 + 20          // 足し算
b := 10 * 20          // 掛け算
c := 10 / 3           // 整数割り算
d := 10 % 3           // 剰余

// すべて O(1) の定数時間操作

文字列連結の計算量:

// 時間計算量: O(n+m) - nとmは文字列の長さ
// 空間計算量: O(n+m) - 新しい文字列を作成
str1 := "Hello"    // 長さ5
str2 := "World"    // 長さ5
result := str1 + str2  // 長さ10の新しい文字列を作成

// ループ内での連結(非効率)
// 時間計算量: O(n²) - n回のループ、各回でコピー
// 空間計算量: O(n²) - 多数の中間文字列
s := ""
for i := 0; i < 1000; i++ {
    s += "a"  // 毎回新しい文字列を作成
}

// strings.Builder(効率的)
// 時間計算量: O(n) - 線形時間
// 空間計算量: O(n) - 一度だけ割り当て
var builder strings.Builder
for i := 0; i < 1000; i++ {
    builder.WriteString("a")
}
s := builder.String()

計算量の実例分析

問題: 1からNまでの合計を計算

// アプローチ1: ループ(Day 4で学習)
// 時間計算量: O(n)
// 空間計算量: O(1)
func sumLoop(n int) int {
    sum := 0
    for i := 1; i <= n; i++ {
        sum += i
    }
    return sum
}

// アプローチ2: 数学的公式(ガウスの公式)
// 時間計算量: O(1)
// 空間計算量: O(1)
func sumFormula(n int) int {
    return n * (n + 1) / 2
}

// n=1000の場合:
// ループ: 1000回の加算
// 公式: 3回の演算(掛け算、足し算、割り算)

パフォーマンス比較:

n ループ実行時間 公式実行時間 差分
100 1μs 0.01μs 100倍
10,000 100μs 0.01μs 10,000倍
1,000,000 10ms 0.01μs 1,000,000倍

---

設計原則(SOLID, DRY, KISS, YAGNI)を使った説明

DRY原則(Don't Repeat Yourself)

違反例:

// NG: 同じ計算を3回繰り返し
price1 := 100
tax1 := price1 * 0.10
total1 := price1 + tax1

price2 := 200
tax2 := price2 * 0.10
total2 := price2 + tax2

price3 := 300
tax3 := price3 * 0.10
total3 := price3 + tax3

改善例:

// OK: 税率を変数化
taxRate := 0.10

price1 := 100
total1 := price1 + (price1 * taxRate)

price2 := 200
total2 := price2 + (price2 * taxRate)

price3 := 300
total3 := price3 + (price3 * taxRate)

// さらに改善: 関数化(Day 5で学習)
func calculateTotal(price float64) float64 {
    const taxRate = 0.10
    return price + (price * taxRate)
}

メリット:

  • 税率変更時に1箇所だけ修正
  • バグが入りにくい
  • 保守性が向上

---

KISS原則(Keep It Simple, Stupid)

複雑すぎる例:

// NG: 過度に複雑
temperature := 25
var category string
if temperature < 0 {
    category = "freezing"
} else if temperature >= 0 && temperature < 10 {
    category = "cold"
} else if temperature >= 10 && temperature < 20 {
    category = "cool"
} else if temperature >= 20 && temperature < 30 {
    category = "warm"
} else {
    category = "hot"
}

シンプルな例:

// OK: シンプルで読みやすい
temperature := 25
var category string

if temperature < 0 {
    category = "freezing"
} else if temperature < 10 {
    category = "cold"
} else if temperature < 20 {
    category = "cool"
} else if temperature < 30 {
    category = "warm"
} else {
    category = "hot"
}

改善ポイント:

  • 冗長な条件を削除(>= 0は不要)
  • 読みやすさが向上
  • バグの可能性が減少

---

YAGNI原則(You Aren't Gonna Need It)

過剰設計の例:

// NG: 今は使わない機能を実装
type User struct {
    id              int
    name            string
    email           string
    phone           string           // 今は使わない
    address         string           // 今は使わない
    birthdate       string           // 今は使わない
    lastLoginTime   time.Time        // 今は使わない
    accountType     string           // 今は使わない
}

user := User{
    id:    1,
    name:  "太郎",
    email: "taro@example.com",
    // 他のフィールドは空のまま
}

必要最小限の例:

// OK: 必要な機能だけ実装
type User struct {
    id    int
    name  string
    email string
}

user := User{
    id:    1,
    name:  "太郎",
    email: "taro@example.com",
}

// 将来必要になったら追加する

メリット:

  • コードが簡潔
  • 保守しやすい
  • テストが容易
  • ---

    単一責任原則(Single Responsibility Principle)

    変数も「単一の責任」を持つべきです。

    違反例:

    // NG: 1つの変数が複数の意味を持つ
    status := 0  // 0=エラーコード、正の値=ユーザーID、負の値=システム状態
    
    // 使用時に意味が不明確
    if status < 0 {
        // システムエラー?
    } else if status == 0 {
        // ログアウト?エラー?
    } else {
        // ユーザーID?
    }
    

    改善例:

    // OK: 各変数が明確な責任を持つ
    errorCode := 0
    userID := 12345
    systemStatus := "running"
    
    // 意図が明確
    if errorCode != 0 {
        // エラー処理
    }
    if userID > 0 {
        // ユーザー処理
    }
    if systemStatus != "running" {
        // システム状態チェック
    }
    

    ---

    メンタルモデル

    変数のメンタルモデル: 「ラベル付きボックス」

    コンピューターのメモリを「倉庫」と考える
    
    倉庫(メモリ)
    ┌─────────────────────────────────────┐
    │                                     │
    │  ┌─────┐  ┌─────┐  ┌─────┐       │
    │  │ age │  │name │  │score│       │
    │  │ 25  │  │太郎 │  │ 85  │       │
    │  └─────┘  └─────┘  └─────┘       │
    │                                     │
    │  使われている       空き領域        │
    └─────────────────────────────────────┘
    

    メンタルモデルのルール:

  • 宣言 = ボックスを作成
   var age int  // 「age」という名前のボックスを作成
   

  • 代入 = ボックスに物を入れる
   age = 25  // ボックスに「25」を入れる
   

  • 参照 = ボックスの中身を見る
   fmt.Println(age)  // ボックスの中身(25)を見る
   

  • 再代入 = 中身を交換
   age = 26  // 古い中身(25)を捨てて、新しい中身(26)を入れる
   

型システムのメンタルモデル: 「形が決まっているボックス」

int型のボックス(整数専用)
┌─────────┐
│   age   │
│   25    │  ← 整数しか入らない
└─────────┘

string型のボックス(文字列専用)
┌─────────┐
│  name   │
│  太郎   │  ← 文字列しか入らない
└─────────┘

// 型の不一致 = 形が合わない
age = "25"  // ✗ 文字列は整数型ボックスに入らない

スコープのメンタルモデル: 「部屋の中のボックス」

main関数(大きな部屋)
┌─────────────────────────────┐
│ age := 25  // この部屋全体で使える │
│                               │
│ if文(小部屋)                │
│ ┌─────────────────────┐     │
│ │ message := "Hello"  │     │
│ │ // 小部屋の中だけ使える │     │
│ └─────────────────────┘     │
│                               │
│ fmt.Println(age)     // OK    │
│ fmt.Println(message) // エラー│
└─────────────────────────────┘

---

ビジュアル図解

変数のライフサイクル

1. 宣言(Declaration)
   ┌─────┐
   │ age │  メモリを確保
   │  ?  │  値は未定義(ゼロ値)
   └─────┘

2. 初期化(Initialization)
   ┌─────┐
   │ age │
   │ 25  │  値を設定
   └─────┘

3. 使用(Usage)
   ┌─────┐
   │ age │
   │ 25  │  → 計算、表示、etc.
   └─────┘

4. 再代入(Reassignment)
   ┌─────┐
   │ age │
   │ 26  │  値を更新
   └─────┘

5. スコープ外(Out of Scope)
   ┌─────┐
   │     │  メモリが解放される
   │     │  (ガベージコレクション)
   └─────┘

型変換の流れ

整数から文字列への変換

   int          変換         string
  ┌───┐     ──────→      ┌───────┐
  │25 │   strconv.Itoa   │ "25"  │
  └───┘                  └───────┘
   ↑                        ↓
   │                        │
   └─── fmt.Sprintf("%d") ──┘

文字列から整数への変換

  string        変換          int
 ┌───────┐   ──────→      ┌───┐
 │ "123" │   strconv.Atoi  │123│
 └───────┘                 └───┘

メモリレイアウト(ASCII Art)

32ビットシステムでのメモリレイアウト

アドレス    変数名    値       型
0x1000  ┌─────────┬────────┬────────┐
        │   age   │   25   │  int   │  4バイト
0x1004  ├─────────┼────────┼────────┤
        │  score  │   85   │  int   │  4バイト
0x1008  ├─────────┼────────┼────────┤
        │  valid  │  true  │  bool  │  1バイト
0x1009  ├─────────┼────────┼────────┤
        │ (padding)│   -    │   -    │  3バイト(アライメント)
0x100C  ├─────────┼────────┼────────┤
        │  height │ 175.5  │ float64│  8バイト
0x1014  └─────────┴────────┴────────┘

---

さらなる探求

発展的なトピック

1. メモリ管理の深掘り

スタックとヒープ:

// スタック割り当て(速い、自動管理)
func example() {
    age := 25  // スタックに割り当て
    // 関数終了時に自動的に解放
}

// ヒープ割り当て(遅い、GCで管理)
func example2() *int {
    age := 25
    return &age  // ポインタを返す→ヒープに移動
    // GCが管理、不要になったら回収
}

2. 型のサイズと効率

import "unsafe"

// 各型のサイズ(64ビットシステム)
fmt.Println("int:", unsafe.Sizeof(int(0)))        // 8バイト
fmt.Println("int32:", unsafe.Sizeof(int32(0)))    // 4バイト
fmt.Println("int64:", unsafe.Sizeof(int64(0)))    // 8バイト
fmt.Println("float64:", unsafe.Sizeof(float64(0)))// 8バイト
fmt.Println("bool:", unsafe.Sizeof(true))         // 1バイト
fmt.Println("string:", unsafe.Sizeof(""))         // 16バイト(ヘッダ)

3. コンパイラの最適化

// 定数畳み込み(Constant Folding)
a := 2 + 3        // コンパイル時に5に置換
b := 10 * 20      // コンパイル時に200に置換

// デッドコード削除(Dead Code Elimination)
if false {
    x := 100  // この行は削除される
}

// インライン展開(Inlining)
func add(a, b int) int {
    return a + b
}

result := add(10, 20)  // add(10, 20)がインライン展開される
// ↓ コンパイラが自動的に変換
// result := 10 + 20

4. 型推論の仕組み

// リテラルの型推論規則

a := 42         // int(デフォルト整数型)
b := 42.0       // float64(デフォルト浮動小数点型)
c := "hello"    // string
d := true       // bool
e := 'A'        // rune(int32のエイリアス)

// 複雑な推論
f := []int{1, 2, 3}         // []int(intのスライス)
g := map[string]int{"a": 1} // map[string]int
h := make(chan int)         // chan int

5. ゼロ値の設計哲学

Goのゼロ値は「安全なデフォルト」として設計されています。

// ゼロ値がそのまま使える例
var count int      // 0: 安全にカウント開始できる
var name string    // "": 空文字列として安全
var ready bool     // false: 準備ができていない状態

// 他の言語(C言語など)との比較
// C言語: 初期化しないと未定義値(危険)
// int age;  // ガベージ値(0とは限らない)
// Go: 必ずゼロ値(安全)
// var age int  // 必ず0

---

セルフチェック質問

レベル1: 基礎理解

  • Q: 変数と定数の違いは何ですか?
A: 変数は値を変更できる、定数(const)は一度設定すると変更できない

  • Q: :==の違いは何ですか?
A: :=は宣言+代入、=は代入のみ

  • Q: int型のゼロ値は何ですか?
A: 0

  • Q: string型のゼロ値は何ですか?
A: ""(空文字列)

レベル2: 応用理解

  • Q: なぜ10 / 3は3になるのですか?
A: 両方が整数なので整数割り算が行われ、小数点以下が切り捨てられる

  • Q: グローバル変数を避けるべき理由は?
A: - 予期しない変更のリスク - テストが困難 - 並行処理で競合状態が発生しやすい - コードの依存関係が複雑になる

  • Q: 文字列連結で+演算子よりもstrings.Builderが速い理由は?
A: +は毎回新しい文字列を作成するが、Builderは内部バッファを使い、最後に1回だけ文字列を生成するから

レベル3: 設計判断

  • Q: 次のコードの問題点は何ですか?
   var x = 100
   var y = 200
   var result = x + y
   
A: - 型推論の:=を使うべき(より簡潔) - 変数名が汎用的すぎる - 不要な変数result(直接使えば良い)

  • Q: この設計の問題点を指摘してください
   data := "user123"  // ユーザーIDとして使用
   data = "2024-01-01"  // 日付として再利用
   
A: - 同じ変数を異なる目的で再利用(単一責任原則違反) - 型が同じでも意味が異なる - 別の変数(userIDdate)を使うべき

  • Q: パフォーマンスの観点から改善点は?
    for i := 0; i < 1000; i++ {
        temp := make([]int, 100)
        // tempを使った処理
    }
    
A: - ループ外で1回だけ割り当てるべき - 1000回のメモリ割り当てが無駄
    temp := make([]int, 100)
    for i := 0; i < 1000; i++ {
        // tempを再利用
    }
    

---

まとめ

今日学んだこと

  • 変数の概念
- 値を保存する「名前付きの箱」 - 再利用性と可読性の向上 - メモリ管理の基礎

  • 変数の宣言方法
- :=(短縮宣言)が最も一般的 - var(明示的宣言)も使える - constで定数を定義

  • 基本的なデータ型
- int: 整数(64ビットシステムでは64ビット) - float64: 浮動小数点数(倍精度) - string: UTF-8文字列 - bool: 真偽値(true/false)

  • 変数の操作
- 再代入(=) - 計算と代入(+=, -=,
=, /=) - 型変換(int(x), float64(x)

  • 設計原則
- DRY: 繰り返しを避ける - KISS: シンプルに保つ - YAGNI: 必要なものだけ実装 - 単一責任: 1変数1目的

  • パフォーマンス
- 計算量の理解(O(1), O(n), O(n²)) - メモリ効率 - 文字列操作の最適化

今日のキーワード

用語 意味 重要度
変数 値を保存する名前付きの領域 ★★★★★
データの種類(int, stringなど) ★★★★★
宣言 変数を作ること ★★★★★
代入 変数に値を入れること ★★★★★
型推論 値から型を自動判定すること ★★★★☆
ゼロ値 初期化されていない変数のデフォルト値 ★★★★☆
スコープ 変数が有効な範囲 ★★★★☆
型変換 ある型から別の型への変換 ★★★☆☆
計算量 アルゴリズムの効率の指標 ★★★☆☆

---

お疲れさまでした!

変数をマスターしたことで、プログラムの表現力が大きく広がりました。ここで学んだ概念は、今後のすべての学習の基礎となります。

次のステップ:

  • Day 3で条件分岐を学ぶ
  • 変数と条件分岐を組み合わせて、より賢いプログラムを作成
  • 実際のプロジェクトで変数の設計原則を適用

復習のヒント:

  • コードを実際に書いて実行する
  • エラーメッセージを読む練習をする
  • 小さなプログラムで実験する
  • ベストプラクティスを意識する