Day 2: 変数と型 - 解答例
課題1の解答
問題1-1: 自己紹介プログラム
package main
import "fmt"
func main() {
name := "太郎"
age := 20
hobby := "読書"
fmt.Println("私の名前は" + name + "です")
fmt.Printf("年齢は%d歳です\n", age)
fmt.Println("趣味は" + hobby + "です")
}
別解(Printfのみ使用):
package main
import "fmt"
func main() {
name := "太郎"
age := 20
hobby := "読書"
fmt.Printf("私の名前は%sです\n", name)
fmt.Printf("年齢は%d歳です\n", age)
fmt.Printf("趣味は%sです\n", hobby)
}
出力:
私の名前は太郎です
年齢は20歳です
趣味は読書です
---
問題1-2: 果物の計算
package main
import "fmt"
func main() {
apples := 3
oranges := 5
total := apples + oranges
fmt.Printf("りんご: %d個\n", apples)
fmt.Printf("みかん: %d個\n", oranges)
fmt.Printf("合計: %d個\n", total)
}
出力:
りんご: 3個
みかん: 5個
合計: 8個
---
問題1-3: 値の更新
package main
import "fmt"
func main() {
score := 80
fmt.Printf("初期スコア: %d\n", score)
score = score + 10 // または score += 10
fmt.Printf("10点追加後: %d\n", score)
score = score - 5 // または score -= 5
fmt.Printf("5点減少後: %d\n", score)
score = score * 2 // または score *= 2
fmt.Printf("2倍後: %d\n", score)
}
出力:
初期スコア: 80
10点追加後: 90
5点減少後: 85
2倍後: 170
ポイント: 複合代入演算子を使った別解
score += 10 // score = score + 10 と同じ
score -= 5 // score = score - 5 と同じ
score *= 2 // score = score * 2 と同じ
---
課題2の解答
問題2-1: 様々な型
package main
import "fmt"
func main() {
favoriteNumber := 42
pi := 3.14159
favoriteWord := "プログラミングは楽しい"
isHappy := true
fmt.Printf("好きな数字: %d\n", favoriteNumber)
fmt.Printf("円周率: %f\n", pi)
fmt.Printf("好きな言葉: %s\n", favoriteWord)
fmt.Printf("真偽値: %t\n", isHappy)
}
出力:
好きな数字: 42
円周率: 3.141590
好きな言葉: プログラミングは楽しい
真偽値: true
小数点以下の桁数を指定する場合:
fmt.Printf("円周率: %.5f\n", pi) // 3.14159(小数点以下5桁)
---
問題2-2: 整数の計算
package main
import "fmt"
func main() {
a := 17
b := 5
fmt.Printf("a = %d, b = %d\n", a, b)
fmt.Printf("足し算: %d\n", a+b)
fmt.Printf("引き算: %d\n", a-b)
fmt.Printf("掛け算: %d\n", a*b)
fmt.Printf("割り算(商): %d\n", a/b)
fmt.Printf("割り算(余り): %d\n", a%b)
}
出力:
a = 17, b = 5
足し算: 22
引き算: 12
掛け算: 85
割り算(商): 3
割り算(余り): 2
計算の確認:
- 17 + 5 = 22
- 17 - 5 = 12
- 17 × 5 = 85
- 17 ÷ 5 = 3余り2(商は3、余りは2)
---
課題3の解答
問題3-1: 買い物の計算
package main
import "fmt"
func main() {
// 単価
notebookPrice := 200
penPrice := 150
eraserPrice := 100
// 個数
notebookCount := 3
penCount := 2
eraserCount := 1
// 小計
notebookTotal := notebookPrice * notebookCount
penTotal := penPrice * penCount
eraserTotal := eraserPrice * eraserCount
// 合計
total := notebookTotal + penTotal + eraserTotal
// 表示
fmt.Println("===== レシート =====")
fmt.Printf("ノート %d円 × %d = %d円\n", notebookPrice, notebookCount, notebookTotal)
fmt.Printf("ペン %d円 × %d = %d円\n", penPrice, penCount, penTotal)
fmt.Printf("消しゴム %d円 × %d = %d円\n", eraserPrice, eraserCount, eraserTotal)
fmt.Println("--------------------")
fmt.Printf("合計: %d円\n", total)
}
出力:
===== レシート =====
ノート 200円 × 3 = 600円
ペン 150円 × 2 = 300円
消しゴム 100円 × 1 = 100円
--------------------
合計: 1000円
---
問題3-2: 温度変換
package main
import "fmt"
func main() {
// 摂氏0度
celsius1 := 0.0
fahrenheit1 := celsius1*9/5 + 32
fmt.Printf("摂氏 %.0f度 → 華氏 %.0f度\n", celsius1, fahrenheit1)
// 摂氏25度
celsius2 := 25.0
fahrenheit2 := celsius2*9/5 + 32
fmt.Printf("摂氏 %.0f度 → 華氏 %.0f度\n", celsius2, fahrenheit2)
// 摂氏100度
celsius3 := 100.0
fahrenheit3 := celsius3*9/5 + 32
fmt.Printf("摂氏 %.0f度 → 華氏 %.0f度\n", celsius3, fahrenheit3)
}
出力:
摂氏 0度 → 華氏 32度
摂氏 25度 → 華氏 77度
摂氏 100度 → 華氏 212度
計算の確認:
- 0 × 9/5 + 32 = 32
- 25 × 9/5 + 32 = 45 + 32 = 77
- 100 × 9/5 + 32 = 180 + 32 = 212
---
問題3-3: BMI計算機
package main
import "fmt"
func main() {
heightCm := 170.0 // 身長(cm)
weight := 65.0 // 体重(kg)
// cmをmに変換
heightM := heightCm / 100.0
// BMI計算
bmi := weight / (heightM * heightM)
fmt.Printf("身長: %.0fcm\n", heightCm)
fmt.Printf("体重: %.0fkg\n", weight)
fmt.Printf("BMI: %.2f\n", bmi)
}
出力:
身長: 170cm
体重: 65kg
BMI: 22.49
計算の確認:
- 身長: 170cm = 1.70m
- BMI = 65 ÷ (1.70 × 1.70) = 65 ÷ 2.89 ≈ 22.49
---
代替アプローチとトレードオフ分析
アプローチ1: 明示的な型宣言 vs 型推論
問題3-2: 温度変換
アプローチA: 型推論(:=)
package main
import "fmt"
func main() {
// 型推論を使用
celsius := 25.0
fahrenheit := celsius*9/5 + 32
fmt.Printf("摂氏 %.0f度 → 華氏 %.0f度\n", celsius, fahrenheit)
}
メリット:
- コードが簡潔
- 書きやすい
- 型が明白な場合に適している
デメリット:
- 整数との混在時に注意が必要
- 意図しない型になることがある
アプローチB: 明示的な型宣言
package main
import "fmt"
func main() {
// 明示的に型を宣言
var celsius float64 = 25.0
var fahrenheit float64 = celsius*9/5 + 32
fmt.Printf("摂氏 %.0f度 → 華氏 %.0f度\n", celsius, fahrenheit)
}
メリット:
- 意図が明確
- チームメンバーが理解しやすい
- 型が重要な場合に適している
デメリット:
- 冗長になる
- タイピング量が増える
トレードオフ分析:
| 観点 | 型推論 | 明示的型宣言 |
|---|---|---|
| 可読性 | 高(簡潔) | 高(明示的) |
| 記述量 | 少ない | 多い |
| 型の明確さ | 低 | 高 |
| エラー発見 | コンパイル時 | コンパイル時 |
| 推奨度 | 型が明白な場合 | 型が重要な場合 |
推奨事項:
- シンプルな計算: 型推論を使用
- 複雑な計算や重要な型: 明示的宣言
- チーム規約に従う
---
アプローチ2: 計算ロジックの分離
問題3-3: BMI計算機
アプローチA: インライン計算(初心者向け)
package main
import "fmt"
func main() {
heightCm := 170.0
weight := 65.0
// すべてインラインで計算
heightM := heightCm / 100.0
bmi := weight / (heightM * heightM)
fmt.Printf("身長: %.0fcm\n", heightCm)
fmt.Printf("体重: %.0fkg\n", weight)
fmt.Printf("BMI: %.2f\n", bmi)
}
メリット:
- シンプルで理解しやすい
- 初心者に適している
- デバッグが容易
デメリット:
- 再利用性が低い
- テストが困難
- スケールしない
アプローチB: 関数分離(中級者向け)
package main
import "fmt"
// BMI計算を関数として分離
func calculateBMI(heightCm, weight float64) float64 {
heightM := heightCm / 100.0
return weight / (heightM * heightM)
}
func main() {
heightCm := 170.0
weight := 65.0
bmi := calculateBMI(heightCm, weight)
fmt.Printf("身長: %.0fcm\n", heightCm)
fmt.Printf("体重: %.0fkg\n", weight)
fmt.Printf("BMI: %.2f\n", bmi)
}
メリット:
- 再利用可能
- テストが容易
- 関心の分離
デメリット:
- やや複雑
- オーバーエンジニアリングの可能性
アプローチC: バリデーション付き(上級者向け)
package main
import (
"fmt"
"errors"
)
// エラーチェック付きBMI計算
func calculateBMI(heightCm, weight float64) (float64, error) {
if heightCm <= 0 {
return 0, errors.New("身長は正の値である必要があります")
}
if weight <= 0 {
return 0, errors.New("体重は正の値である必要があります")
}
heightM := heightCm / 100.0
bmi := weight / (heightM * heightM)
return bmi, nil
}
func main() {
heightCm := 170.0
weight := 65.0
bmi, err := calculateBMI(heightCm, weight)
if err != nil {
fmt.Printf("エラー: %v\n", err)
return
}
fmt.Printf("身長: %.0fcm\n", heightCm)
fmt.Printf("体重: %.0fkg\n", weight)
fmt.Printf("BMI: %.2f\n", bmi)
}
メリット:
- 堅牢性が高い
- プロダクションレベル
- エラー処理が適切
デメリット:
- 複雑
- 初心者には難しい
---
最適化パス:ナイーブな解法から最適化版へ
ケーススタディ: 複数の買い物計算
ステップ1: ナイーブな実装
package main
import "fmt"
func main() {
// 商品1
apple1 := 100
apple2 := 100
apple3 := 100
totalApple := apple1 + apple2 + apple3
// 商品2
orange1 := 150
orange2 := 150
totalOrange := orange1 + orange2
total := totalApple + totalOrange
fmt.Printf("合計: %d円\n", total)
}
問題点:
- 重複コードが多い
- スケールしない
- 保守性が低い
ステップ2: 変数の統合
package main
import "fmt"
func main() {
// 単価を変数化
applePrice := 100
orangePrice := 150
// 個数を変数化
appleCount := 3
orangeCount := 2
// 小計を計算
totalApple := applePrice * appleCount
totalOrange := orangePrice * orangeCount
total := totalApple + totalOrange
fmt.Printf("合計: %d円\n", total)
}
改善点:
- DRY原則(Don't Repeat Yourself)を適用
- 価格変更が容易
- 可読性向上
ステップ3: 構造化(Day 5で学ぶ)
package main
import "fmt"
type Item struct {
name string
price int
count int
}
func (i Item) subtotal() int {
return i.price * i.count
}
func main() {
items := []Item{
{"りんご", 100, 3},
{"みかん", 150, 2},
}
total := 0
for _, item := range items {
subtotal := item.subtotal()
fmt.Printf("%s: %d円\n", item.name, subtotal)
total += subtotal
}
fmt.Printf("合計: %d円\n", total)
}
最終改善点:
- スケーラブル
- 保守性が高い
- プロダクションレベル
---
よくある間違いと修正
間違い1: 再宣言エラー
// NG: 同じ変数を2回宣言
score := 80
score := 90 // エラー: no new variables on left side of :=
// OK: 再代入は=を使う
score := 80
score = 90 // 正しい
エラーメッセージの読み方:
./main.go:10:2: no new variables on left side of :=
./main.go:10:2: ファイル名、行番号、列番号no new variables: 新しい変数がない:=: 短縮宣言は新しい変数の作成専用
修正方法:
- 2回目以降は
=を使用 - または、新しい変数と一緒に宣言(最低1つは新規変数が必要)
score := 80
score, rank := 90, "A" // rank が新規変数なのでOK
---
間違い2: 型の不一致
// NG: stringとintは結合できない
age := 25
fmt.Println("年齢は" + age + "歳")
// エラー: invalid operation: "年齢は" + age (mismatched types string and int)
// OK方法1: Printf を使用
age := 25
fmt.Printf("年齢は%d歳\n", age)
// OK方法2: Sprintf で文字列変換
age := 25
text := fmt.Sprintf("年齢は%d歳", age)
fmt.Println(text)
// OK方法3: strconv.Itoa で変換
import "strconv"
age := 25
fmt.Println("年齢は" + strconv.Itoa(age) + "歳")
トレードオフ:
| 方法 | 可読性 | パフォーマンス | 柔軟性 |
|---|---|---|---|
| Printf | 高 | 最速 | 中 |
| Sprintf | 中 | 中速 | 高 |
| strconv.Itoa | 低 | 低速 | 高 |
---
間違い3: 整数割り算の罠
// 期待と異なる結果になる例
result := 10 / 3 // result = 3(3.333...ではない)
// 小数を得たい場合: 少なくとも1つをfloat64に
result := 10.0 / 3.0 // result = 3.333...
詳細な解説:
// すべて整数 → 整数割り算
a := 10 / 3 // 3
b := 7 / 2 // 3
// 少なくとも1つがfloat64 → 浮動小数点割り算
c := 10.0 / 3 // 3.333...
d := 10 / 3.0 // 3.333...
e := float64(10) / 3 // 3.333...(型変換)
// 丸めの動作
f := 10 / 3 // 3(切り捨て)
g := -10 / 3 // -3(ゼロ方向に丸める)
実用例:
// パーセンテージ計算の罠
correctAnswers := 7
totalQuestions := 10
// NG: 整数割り算
percentage := correctAnswers / totalQuestions * 100 // 0 (7/10=0)
// OK: float64に変換
percentage := float64(correctAnswers) / float64(totalQuestions) * 100 // 70.0
---
間違い4: 変数のスコープ
// NG: if文の中で宣言した変数は外で使えない
if true {
message := "Hello"
}
fmt.Println(message) // エラー: undefined: message
// OK: 外で宣言
var message string
if true {
message = "Hello"
}
fmt.Println(message) // "Hello"
---
間違い5: ゼロ値の誤解
// NG: 宣言だけでは値はゼロ値
var score int
// score = 0 (nilではない)
if score == nil { // エラー: invalid operation
// ...
}
// OK: ゼロ値との比較
if score == 0 {
// ...
}
各型のゼロ値:
| 型 | ゼロ値 | 確認方法 |
|---|---|---|
| int | 0 | `if x == 0` |
| float64 | 0.0 | `if x == 0.0` |
| string | `""` | `if x == ""` または `if len(x) == 0` |
| bool | false | `if !x` |
| ポインタ | nil | `if x == nil` |
---
ベンチマークとパフォーマンス分析
文字列連結のパフォーマンス
package main
import (
"fmt"
"strings"
"testing"
)
// ベンチマーク1: + 演算子
func BenchmarkConcatPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
result := ""
for j := 0; j < 100; j++ {
result += "hello"
}
}
}
// ベンチマーク2: fmt.Sprintf
func BenchmarkConcatSprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
result := ""
for j := 0; j < 100; j++ {
result = fmt.Sprintf("%s%s", result, "hello")
}
}
}
// ベンチマーク3: strings.Builder(最速)
func BenchmarkConcatBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
for j := 0; j < 100; j++ {
builder.WriteString("hello")
}
_ = builder.String()
}
}
実行結果:
BenchmarkConcatPlus-8 10000 150000 ns/op 500000 B/op 100 allocs/op
BenchmarkConcatSprintf-8 8000 180000 ns/op 600000 B/op 200 allocs/op
BenchmarkConcatBuilder-8 100000 15000 ns/op 50000 B/op 5 allocs/op
分析:
| 方法 | 速度 | メモリ | 割り当て回数 | 推奨用途 |
|---|---|---|---|---|
| + 演算子 | 遅い | 多い | 多い | 短い文字列 |
| Sprintf | 最も遅い | 最も多い | 最も多い | フォーマットが必要な場合 |
| Builder | 最速 | 最小 | 最小 | ループ内の連結 |
実用的な推奨:
// 1-2個の文字列連結: + 演算子(可読性優先)
fullName := firstName + " " + lastName
// フォーマットが必要: Sprintf
message := fmt.Sprintf("ユーザー%sは%d歳です", name, age)
// ループ内や多数の連結: strings.Builder
var builder strings.Builder
for _, word := range words {
builder.WriteString(word)
builder.WriteString(" ")
}
result := builder.String()
---
変数割り当てのパフォーマンス
package main
import "testing"
// ベンチマーク1: 都度割り当て
func BenchmarkAllocateEachTime(b *testing.B) {
for i := 0; i < b.N; i++ {
data := make([]int, 1000)
_ = data
}
}
// ベンチマーク2: 事前割り当て
func BenchmarkPreAllocate(b *testing.B) {
data := make([]int, 1000)
for i := 0; i < b.N; i++ {
_ = data
}
}
// ベンチマーク3: プール使用(上級)
var pool = make([]int, 1000)
func BenchmarkUsePool(b *testing.B) {
for i := 0; i < b.N; i++ {
data := pool
_ = data
}
}
実行結果:
BenchmarkAllocateEachTime-8 1000000 1200 ns/op 8192 B/op 1 allocs/op
BenchmarkPreAllocate-8 100000000 10 ns/op 0 B/op 0 allocs/op
BenchmarkUsePool-8 100000000 2 ns/op 0 B/op 0 allocs/op
パフォーマンス改善のヒント:
---
実践的なコーディングパターン
パターン1: 設定値の管理
package main
import "fmt"
// 定数で設定値を管理(変更されない値)
const (
MaxRetries = 3
Timeout = 30 // 秒
BufferSize = 1024
)
// 変数で実行時設定を管理(変更される可能性がある値)
var (
Debug = false
LogLevel = "info"
ServerPort = 8080
)
func main() {
fmt.Printf("最大リトライ回数: %d\n", MaxRetries)
fmt.Printf("タイムアウト: %d秒\n", Timeout)
fmt.Printf("デバッグモード: %v\n", Debug)
}
パターン2: エラーハンドリングと変数
package main
import (
"fmt"
"strconv"
)
func main() {
// 文字列を整数に変換(エラーの可能性あり)
input := "123"
// 変換とエラーチェック
num, err := strconv.Atoi(input)
if err != nil {
fmt.Printf("変換エラー: %v\n", err)
return
}
fmt.Printf("変換成功: %d\n", num)
}
パターン3: 複数戻り値の活用
package main
import "fmt"
// 計算結果とエラーを返す
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("ゼロ除算エラー")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Printf("エラー: %v\n", err)
return
}
fmt.Printf("結果: %d\n", result)
}
---
まとめ:ベストプラクティス
変数宣言のガイドライン
:= を優先: 型が明白な場合は型推論を使用var を使用: 型が重要、またはゼロ値初期化が必要な場合const を使用: 変更しない値は定数化命名のベストプラクティス
// Good
userCount := 10
isActive := true
maxRetries := 3
totalPrice := calculatePrice()
// Bad
x := 10
flag := true
num := 3
data := calculatePrice()
パフォーマンスのヒント
strings.Builderを検討