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はプラットフォームによってサイズが変わります。
int64は常に64ビットです。通常はintを使えば十分です。
Q2: floatとfloat64の違いは?
A: Goにはfloat型はありません。float32とfloat64があり、デフォルトは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を終える前に、以下を確認してください:
:=で変数を宣言できる=で値を再代入できる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: 変数と定数の違いは何ですか?
- Q:
:=と=の違いは何ですか?
:=は宣言+代入、=は代入のみ- Q: int型のゼロ値は何ですか?
- Q: string型のゼロ値は何ですか?
レベル2: 応用理解
- Q: なぜ
10 / 3は3になるのですか?
- Q: グローバル変数を避けるべき理由は?
- Q: 文字列連結で
+演算子よりもstrings.Builderが速い理由は?
+は毎回新しい文字列を作成するが、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:
- 同じ変数を異なる目的で再利用(単一責任原則違反)
- 型が同じでも意味が異なる
- 別の変数(userID、date)を使うべき- 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))- 設計原則
- パフォーマンス
今日のキーワード
| 用語 | 意味 | 重要度 |
|---|---|---|
| 変数 | 値を保存する名前付きの領域 | ★★★★★ |
| 型 | データの種類(int, stringなど) | ★★★★★ |
| 宣言 | 変数を作ること | ★★★★★ |
| 代入 | 変数に値を入れること | ★★★★★ |
| 型推論 | 値から型を自動判定すること | ★★★★☆ |
| ゼロ値 | 初期化されていない変数のデフォルト値 | ★★★★☆ |
| スコープ | 変数が有効な範囲 | ★★★★☆ |
| 型変換 | ある型から別の型への変換 | ★★★☆☆ |
| 計算量 | アルゴリズムの効率の指標 | ★★★☆☆ |
---
お疲れさまでした!
変数をマスターしたことで、プログラムの表現力が大きく広がりました。ここで学んだ概念は、今後のすべての学習の基礎となります。
次のステップ:
- Day 3で条件分岐を学ぶ
- 変数と条件分岐を組み合わせて、より賢いプログラムを作成
- 実際のプロジェクトで変数の設計原則を適用
復習のヒント:
- コードを実際に書いて実行する
- エラーメッセージを読む練習をする
- 小さなプログラムで実験する
- ベストプラクティスを意識する