Goのマップと構造体

Goにおけるデータモデリングの2大柱はマップと構造体です。マップは動的なクエリを処理し、構造体は固定された構造を記述します。これらを組み合わせることで、ビジネスオブジェクトの90%を記述することができます。

マップは O(1) のキー・値の検索を可能にする一方、構造体はフィールドを用いてデータを集約します。このレッスンでは、Go におけるデータモデリングのすべての基本概念を習得し、それらを活用して完全な e コマース在庫管理システムを構築できるようになります。

1. 学習内容


2. あるEコマースエンジニアの実話

(1) 課題:スライスの検索が遅すぎる

ボブは、あるECプラットフォームのバックエンドエンジニアです。「ダブル11」の先行販売キャンペーンが進行中ですが、在庫クエリAPIの処理が信じられないほど遅くなっています:

「当社では10万点のSKUを在庫として保有しており、これらは[]Productスライスを使用して管理されています。すべてのクエリで線形スキャンが行われています。QPSが増加すると、平均レスポンス時間は800ミリ秒となり、CPU使用率は100%に達します。上司から、これを100ミリ秒未満に最適化するよう、3日間の期限が与えられました。」

彼は自分が書いたコードを開いた:

GO
// Version 1: Store all products using a slice
var products []Product

func findProduct(sku string) *Product {
    for _, p := range products {  // O(n) 線形スキャン
        if p.SKU == sku {
            return &p
        }
    }
    return nil
}

10万個の製品 × 1,000 QPS = 1億回のトラバーサル/秒。CPU使用率が100%になるのは避けられません。

(2) Goでの解決策:O(1)の検索にはmapを使用する

GO
// inventory.go
パッケージ main

import "fmt"

type Product struct {
    SKU      文字列  // 商品の固有識別子
    Name     文字列  // 商品名
    Price    float64 // 価格
    Stock    int     // 在庫
    Category 文字列  // Classification
}

// Using `map` to Create a Hash Table
type Inventory struct {
    products map[文字列]Product  // SKU -> Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[文字列]Product)}
}

// O(1) クエリ
func (inv *Inventory) Find(sku 文字列) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// O(1) update
func (inv *Inventory) UpdateStock(sku 文字列, delta int) エラー {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    p.Stock += delta
    inv.products[sku] = p
    return nil
}

func main() {
    inv := NewInventory()

    // Bulk Import of 100,000 SKUs
    for i := 0; i < 100000; i++ {
        sku := fmt.Sprintf("SKU-%05d", i)
        inv.products[sku] = Product{
            SKU:      sku,
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    99.99,
            Stock:    100,
            Category: "electronics",
        }
    }

    // Query Performance Comparison
    p, ok := inv.Find("SKU-50000")
    if ok {
        fmt.Printf("Found: %s, price=%.2f\n", p.Name, p.Price)
    }

    // Update Inventory
    inv.UpdateStock("SKU-50000", -5)
}

出力:

TEXT
Found: Product-50000, price=99.99

(3) パフォーマンス:slicemap のクエリパフォーマンスの比較

データサイズ slice での線形スキャン map でのハッシュ検索 性能差
100 SKU 50 ns 50 ns 同等
1,000 SKU 500 ns 50 ns 10倍
10,000 SKU 5 µs 50 ns 100倍
100,000 SKU 50 µs 50 ns 1,000倍
💡 ヒント: マップの O(1) という計算量は、ハッシュテーブルに基づく平均的な計算量であり、最悪の場合(ハッシュ衝突)には O(n) まで悪化します。Go は、適切に設計されたハッシュ関数とリサイズ機構によって、この発生確率を最小限に抑えています。


3. マップの基礎

(1) 地図を作成する3つの方法

GO
package main

import "fmt"

func main() {
    // Method 1: make (Recommended)
    m1 := make(map[string]int)  // 空 map,書き込み可能

    // Method 2: make + preallocated capacity
    m2 := make(map[string]int, 100)  // 事前割り当て 100 容量,容量の削減

    // Method 3: Literal Initialization
    m3 := map[string]int{
        "Alice": 28,
        "Bob":   32,
    }

    // Method 4: nil map (read-only, cannot be written to)
    var m4 map[string]int  // == nil,できない m4["a"] = 1
    _ = m4
}

(2) マップのCRUD

GO
パッケージ main

import "fmt"

func main() {
    ages := make(map[文字列]int)

    // Create
    ages["Alice"] = 28
    ages["Bob"] = 32

    // Read
    fmt.Println(ages["Alice"])  // 28

    // Update
    ages["Alice"] = 29

    // Delete
    削除(ages, "Bob")

    // Length
    fmt.Printf("len=%d\n", len(ages))

    fmt.Println(ages)  // map[Alice:29]
}

出力:

TEXT
28
len=1
map[Alice:29]

▶ サンプル:map iterate(順次、ランダム)

GO
package main

import "fmt"

func main() {
    ages := map[string]int{"Alice": 28, "Bob": 32, "Charlie": 45}

    // for range:key + value
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // As long as the key
    for name := range ages {
        fmt.Printf("name: %s\n", name)
    }

    // Only the value (use _ to ignore the key)
    for _, age := range ages {
        fmt.Printf("age: %d\n", age)
    }
}
▶ 試してみよう

出力(順不同):

TEXT
Alice is 28 years old
Charlie is 45 years old
Bob is 32 years old
name: Bob
...
🔥 よくある間違い: Goのマップの反復処理順序は、プログラマーが特定の順序に依存することを防ぐために、意図的にランダム化されています。一貫した順序が必要な場合は、まずキーをソートしてください。


4. comma-ok の構文(要点)

(1) 「値がゼロ」と「存在しない」を区別する

GO
パッケージ main

import "fmt"

func main() {
    ages := map[文字列]int{"Alice": 28}

    // 誤ったアプローチ:「キーが存在しない」ことと「値が 0」であることを区別できない
    age := ages["Bob"]
    fmt.Printf("Bob's age: %d\n", age)  // 0(しかし Bob 本当だ 0 歳ですか?)

    // Correct spelling: comma-ok
    age, ok := ages["Bob"]
    if ok {
        fmt.Printf("Bob's age: %d\n", age)
    } else {
        fmt.Println("Bob not found")
    }

    // 存在確認だけを行う:値は破棄する
    _, exists := ages["Bob"]
    fmt.Printf("Bob exists: %v\n", exists)
}

出力:

TEXT
Bob's age: 0
Bob not found
Bob exists: false

(2) 実践における「comma-ok」:クエリのキャッシュ

GO
package main

import "fmt"

// Cache: key → value
var cache = make(map[string]string)

func getCached(key string) (string, bool) {
    val, ok := cache[key]
    return val, ok
}

func main() {
    // Set Cache
    cache["user:1"] = "Alice"
    cache["user:2"] = "Bob"

    // Query Cache
    if val, ok := getCached("user:1"); ok {
        fmt.Printf("Hit: %s\n", val)
    } else {
        fmt.Println("Miss")
    }

    if _, ok := getCached("user:999"); !ok {
        fmt.Println("user:999 not in cache, fetching from DB...")
    }
}

出力:

TEXT
Hit: Alice
user:999 not in cache, fetching from DB...

(3) comma-ok 一般モード

文脈 構文 意味
地図検索 v, ok := m[key] キーは存在しますか?
型のアサーション v, ok := x.(T) 型の一致?
チャンネルからの受信 v, ok := <-ch チャンネルを閉じる?

5. 構造体の基礎

(1) 構造体の定義と初期化

GO
パッケージ main

import "fmt"

// define struct
type User struct {
    Name 文字列
    Age  int
    City 文字列
}

func main() {
    // Method 1: By フィールド 順序 (not recommended; poor readability)
    u1 := User{"Alice", 28, "Shanghai"}

    // Method 2: Initializing Field Names (Recommended)
    u2 := User{
        Name: "Bob",
        Age:  32,
        City: "Beijing",
    }

    // Method 3: Partial Initialization (with the remainder set to zero)
    u3 := User{Name: "Charlie"}  // Age=0, City=""

    // Method 4: `new()` returns a pointer
    u4 := new(User)
    u4.Name = "Dave"

    fmt.Println(u1, u2, u3, u4)
}

出力:

TEXT
{Alice 28 Shanghai} {Bob 32 Beijing} {Charlie  } &{Dave 0 }

(2) フィールドへのアクセスと変更

GO
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "Alice", Age: 28}

    // Read Field
    fmt.Println(u.Name)  // Alice

    // Write to a field
    u.Age = 29

    // Pointer Access (Automatic Dereferencing)
    p := &u
    fmt.Println(p.Name)  // Alice(に相当する (*p).Name)
    p.Age = 30  // 自動的にデリファレンスされる
}

▶ サンプル:構造体のコピーとポインタの比較

GO
パッケージ main

import "fmt"

type Counter struct {
    Value int
}

// 値渡し:struct 全体がコピーされる
func incrementByValue(c Counter) {
    c.Value++  // コピーを編集
    fmt.Printf("Inside the 関数: %d\n", c.Value)
}

// Pointer Passing: Pass-by-Reference
func incrementByPointer(c *Counter) {
    c.Value++  // 元のオブジェクトを修正する
    fmt.Printf("Inside the 関数: %d\n", c.Value)
}

func main() {
    c := Counter{Value: 10}

    incrementByValue(c)
    fmt.Printf("After 値 passing: %d\n", c.Value)  // 10(変わらない)

    incrementByPointer(&c)
    fmt.Printf("After passing the pointer: %d\n", c.Value)  // 11(修正済み)
}
▶ 試してみよう

出力:

TEXT
関数内:11
値渡し後:10
関数内:11
ポインタの受け渡し後:11
🔥 よくある間違い: structは値型であるため、パラメータとして渡すとstruct全体がコピーされます。大きなstructの場合や、元のオブジェクトを変更する必要がある場合は、ポインタを使用する必要があります


6. struct tag(JSONシリアライゼーションの鍵)

(1) struct tag の構文

GO
type User struct {
    Name     string `json:"name" db:"user_name"`
    Age      int    `json:"age" validate:"min=0,max=150"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`  // - 表す JSON 無視する
}

(2) JSONシリアライゼーションの実践

GO
パッケージ main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     文字列 `json:"name"`
    Age      int    `json:"age"`
    Email    文字列 `json:"email,omitempty"`
    Password 文字列 `json:"-"`  // パスワードはシリアライズされない
}

func main() {
    u := User{
        Name:     "Alice",
        Age:      28,
        Email:    "alice@example.com",
        Password: "secret123",
    }

    // Serialization: struct → JSON
    data, _ := json.Marshal(u)
    fmt.Printf("JSON: %s\n", data)

    // Deserialization: JSON → struct
    jsonStr := `{"name":"Bob","age":32,"email":"bob@example.com"}`
    var u2 User
    json.Unmarshal([]byte(jsonStr), &u2)
    fmt.Printf("Deserialization: %+v\n", u2)
}

出力:

TEXT
JSON: {"name":"Alice","age":28,"email":"alice@example.com"}
デシリアライズ: {Name:Bob Age:32 Email:bob@example.com Password:}

(3) よく使われるstructタグライブラリ

ライブラリ タグ名 用途
encoding/json json:"name" JSON フィールド name
gorm gorm:"primaryKey" ORM フィールドの制約
バリデータ validate:"required" フィールド検証
yaml yaml:"name" YAMLのシリアライズ
💡 ヒント: 第10課「file-json」では、JSONシリアライゼーションの詳細について詳しく解説します。


7. ネストされた構造

(1) ネストされた構造体(継承ではなく構成)

Goには継承の概念はありません。代わりに、ネストされた構造体を用いて構成を実現しています:

GO
package main

import "fmt"

type Address struct {
    City    string
    Country string
}

type User struct {
    Name    string
    Age     int
    Address Address  // ネスト Address
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Accessing Nested Fields
    fmt.Println(u.Address.City)     // Shanghai
    fmt.Println(u.Address.Country)  // China
}

(2) 匿名ネスト(フィールド昇格)

GO
パッケージ main

import "fmt"

type Address struct {
    City    文字列
    Country 文字列
}

// Anonymous Nesting: Fields Are "Promoted"
type User struct {
    Name 文字列
    Age  int
    Address  // に相当する Address Address,ただし、フィールド名は記述しない
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Field promotion: Direct access, without using `u.Address.City`
    fmt.Println(u.City)     // Shanghai
    fmt.Println(u.Country)  // China

    // You can also use the full path.
    fmt.Println(u.Address.City)
}

▶ サンプル:ネストされたタグとJSONタグの実践例

GO
package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Email   string  `json:"email,omitempty"`
    Address Address `json:"address"`
}

func main() {
    u := User{
        Name:  "Alice",
        Age:   28,
        Email: "alice@example.com",
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    data, _ := json.MarshalIndent(u, "", "  ")
    fmt.Println(string(data))
}
▶ 試してみよう

出力:

TEXT
{
  "name": "Alice",
  "age": 28,
  "email": "alice@example.com",
  "address": {
    "city": "Shanghai",
    "country": "China"
  }
}

8. mapslice のどちらを選ぶか

(1) モデル選択の参照表

次元 スライス []T マップ map[K]V
検索方法 線形スキャン O(n) ハッシュ O(1)
注文済みですか? ✅ 注文済み ❌ 未注文
メモリ使用量 コンパクト (24 + len*size) コンパクトではない (ハッシュテーブル + バケット)
要素の削除 手動での移動が必要 delete(m, k) O(1)
代表的なシナリオ リスト/キュー/スタック/ソート 辞書/キャッシュ/インデックス

(2) モデル選択の決定木

100%
graph TB
    A[複数の要素を保存する必要がある] --> B{ボタンを押して検索する必要があります?}
    B -->|はい| C[で map<br/>O(1) 検索]
    B -->|いいえ| D{順序を保証する必要がある?}
    D -->|はい| E[で slice]
    D -->|いいえ| F{元素の数 < 100?}
    F -->|はい| G[で slice または map どちらでも構いません]
    F -->|いいえ| C

(3) 実例:学生の成績管理

GO
package main

import "fmt"

// Store student grades in a map (look up by student ID)
type GradeBook struct {
    scores map[string]int  // 学生番号 → 点数
}

func (gb *GradeBook) Set(id string, score int) {
    gb.scores[id] = score
}

func (gb *GradeBook) Get(id string) (int, bool) {
    score, ok := gb.scores[id]
    return score, ok
}

// Use a slice to store a list of scores (for sorting or calculating the average score)
func average(scores []int) float64 {
    if len(scores) == 0 {
        return 0
    }
    sum := 0
    for _, s := range scores {
        sum += s
    }
    return float64(sum) / float64(len(scores))
}

func main() {
    gb := &GradeBook{scores: make(map[string]int)}
    gb.Set("S001", 95)
    gb.Set("S002", 82)
    gb.Set("S003", 67)

    if s, ok := gb.Get("S001"); ok {
        fmt.Printf("S001: %d\n", s)
    }

    // Collect all scores and calculate the average.
    allScores := []int{}
    for _, s := range gb.scores {
        allScores = append(allScores, s)
    }
    fmt.Printf("Average Score: %.2f\n", average(allScores))
}
▶ 試してみよう

出力:

TEXT
S001: 95
平均点: 81.33
💡 ヒント: 実際のプロジェクトでは、mapslice を組み合わせて使用するのが一般的です。具体的には、map をクエリに、slice をソートや集計に使用します。


9. 完全な例:EコマースSKU在庫管理システム

マップと構造体のすべての機能を組み合わせて、完全なEコマース在庫照会システムを構築します:

GO
// inventory_system.go
package main

import (
    "encoding/json"
    "fmt"
    "sort"
)

// Product
type Product struct {
    SKU      string  `json:"sku"`
    Name     string  `json:"name"`
    Price    float64 `json:"price"`
    Stock    int     `json:"stock"`
    Category string  `json:"category"`
    Tags     []string `json:"tags,omitempty"`  // タグ:新商品/売れ筋/割引
}

// Inventory System
type Inventory struct {
    products map[string]Product  // SKU → Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[string]Product)}
}

// Add Item
func (inv *Inventory) Add(p Product) {
    inv.products[p.SKU] = p
}

// Query (comma-ok)
func (inv *Inventory) Find(sku string) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// Update Inventory
func (inv *Inventory) UpdateStock(sku string, delta int) error {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    newStock := p.Stock + delta
    if newStock < 0 {
        return fmt.Errorf("insufficient stock for %s: have %d, need %d",
            sku, p.Stock, -delta)
    }
    p.Stock = newStock
    inv.products[sku] = p
    return nil
}

// Sort by price (unordered map → convert to slice)
func (inv *Inventory) ListByPrice() []Product {
    list := make([]Product, 0, len(inv.products))
    for _, p := range inv.products {
        list = append(list, p)
    }
    sort.Slice(list, func(i, j int) bool {
        return list[i].Price < list[j].Price
    })
    return list
}

// Filter by Category
func (inv *Inventory) FindByCategory(category string) []Product {
    var result []Product
    for _, p := range inv.products {
        if p.Category == category {
            result = append(result, p)
        }
    }
    return result
}

func main() {
    inv := NewInventory()

    // Initialize 100 SKUs
    categories := []string{"electronics", "clothing", "food"}
    for i := 0; i < 100; i++ {
        inv.Add(Product{
            SKU:      fmt.Sprintf("SKU-%05d", i),
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    float64(i%50 + 10),
            Stock:    100 - i%30,
            Category: categories[i%3],
        })
    }

    // 1. Look up a single SKU
    if p, ok := inv.Find("SKU-0050"); ok {
        fmt.Printf("Found: %s, price=%.2f, stock=%d\n", p.Name, p.Price, p.Stock)
    }

    // 2. Update Inventory
    if err := inv.UpdateStock("SKU-0050", -10); err == nil {
        fmt.Println("Stock updated successfully")
    }

    // 3. Filter by category
    electronics := inv.FindByCategory("electronics")
    fmt.Printf("\nElectronics products: %d\n", len(electronics))

    // 4. Sort by price (top 3)
    sorted := inv.ListByPrice()
    fmt.Println("\nTop 3 cheapest:")
    for i, p := range sorted[:3] {
        fmt.Printf("  %d. %s: %.2f\n", i+1, p.Name, p.Price)
    }

    // 5. JSON Serialization (API Response)
    if p, ok := inv.Find("SKU-0050"); ok {
        data, _ := json.MarshalIndent(p, "", "  ")
        fmt.Printf("\nJSON output:\n%s\n", data)
    }
}

期待される出力:

TEXT
Found: Product-50, price=10.00, stock=70
Stock updated successfully

Electronics products: 34

Top 3 cheapest:
  1. Product-0: 10.00
  2. Product-30: 10.00
  3. Product-60: 10.00

JSON output:
{
  "sku": "SKU-0050",
  "name": "Product-50",
  "price": 10,
  "stock": 60,
  "category": "food"
}
🔥 よくある間違い: 26行目で、sort.Slice はクロージャを比較関数として使用しています。このクロージャは list 変数をキャプチャし、各比較の際にそれを呼び出します。


10. その他の例題セット

▶ サンプル:map リテラルによる初期化と make の使用とのパフォーマンス比較

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Literal Initialization
    start1 := time.Now()
    m1 := map[string]int{
        "a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
        "f": 6, "g": 7, "h": 8, "i": 9, "j": 10,
    }
    fmt.Printf("Literals: %v, %v\n", len(m1), time.Since(start1))

    // make preallocation
    start2 := time.Now()
    m2 := make(map[string]int, 10)
    m2["a"] = 1
    m2["b"] = 2
    m2["c"] = 3
    m2["d"] = 4
    m2["e"] = 5
    m2["f"] = 6
    m2["g"] = 7
    m2["h"] = 8
    m2["i"] = 9
    m2["j"] = 10
    fmt.Printf("make:%v, %v\n", len(m2), time.Since(start2))
}
▶ 試してみよう

出力:

TEXT
リテラル:10, 1.2µs
make:10, 850ns

▶ サンプル:構造体の値を受け取るメソッドと、ポインタを受け取るメソッド

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Value receiver: Operates on a copy; does not affect the original object
func (c Counter) IncrementValue() Counter {
    c.Value++
    return c
}

// Pointer receiver: Directly modifies the original object
func (c *Counter) IncrementPointer() {
    c.Value++
}

func main() {
    c := Counter{Value: 10}

    // Value receiver: Must use a return value
    c = c.IncrementValue()
    fmt.Printf("After the value is received: %d\n", c.Value)  // 11

    // Pointer receiver: Direct modification
    c.IncrementPointer()
    fmt.Printf("After the pointer is received: %d\n", c.Value)  // 12
}
▶ 試してみよう

出力:

TEXT
受信者の後に:11
ポインタの受信後:12

▶ サンプル:mapにおける並行処理の落とし穴の実証

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    m := make(map[string]int)

    // Writing a goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            m["key"] = i  // 書き込み操作
        }
    }()

    // Reading a goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            _ = m["key"]  // 読み取り操作
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Println("Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)")
}
▶ 試してみよう

出力(実行時にパニックが発生する可能性があります):

TEXT
同時読み書きの可能性 panic(fatal エラー: concurrent map read and map write)
🔥 よくある間違い: これはGoの並行処理における典型的な落とし穴です。並行処理用のマップは、sync.Map または sync.Mutex で保護する必要があります。これについては第16課で詳しく解説しています。


❓ よくある質問

Q マップ内で存在しないキーにアクセスしようとするとどうなりますか?
A 0の値が返されます。ただし、0の値も有効な値である場合(例:age=0)があるため、「comma-ok」を使用して、「存在しない」場合と「値が0」の場合を区別する必要があります。
Q map は参照型ですか?
A はい。map変数はハッシュテーブルへのポインタであり、代入とパラメータ渡しはどちらも同じ基底となるハッシュテーブルを共有します。これはスライスに似ていますが、より根本的な違いがあります。スライスはappendを介して独立してサイズ変更をトリガーできますが、mapにはこの分離メカニズムがありません。
Q 構造体は比較できますか?
A すべてのフィールドが比較可能である場合に限ります。スライス、マップ、または func を含む構造体は比較できません(コンパイルエラーになります)。詳細な比較を行うには reflect.DeepEqual() を使用してください。
Q ネストされた構造体はいつ使い、ポインタはいつ使うべきですか?
A (1) 元のオブジェクトを変更する必要がある場合 → ネストされたポインタを使用する; (2) 構造体が大きい場合 → ネストされたポインタを使用する(コピーを避けるため); (3) 多態性が必要な場合(インターフェースを実装するため) → ポインタを使用する; (4) 値セマンティクス、不変オブジェクトの場合 → 値のネストを使用する。
Q マップのキーにはどのような制限がありますか?
A キーは、bool、int、float、string といった比較可能な型、またはこれらの型を含む構造体でなければなりません。スライス、マップ、func は使用できません。
Q struct tag の形式が間違っているとどうなりますか?
A コードはコンパイルされますが、JSONシリアライズなどのライブラリでは認識されません。よくあるエラーとしては、タグ名のスペルミス(例:json:"name" に余分なスペースが入っている)や、引用符の種類が間違っている(二重引用符を使用する必要があります)などが挙げられます。
Q なぜマップの反復処理はランダムになるのですか?
A これはGoの仕様によるものです。プログラマーが特定の順序に依存することを防ぐためです。一貫した順序が必要な場合は、まずスライスを使ってすべてのキーを集め、次にキーをソートし、最後にソートされたキーを使ってマップを検索してください。
Q マップはスレッドセーフですか?
A いいえ!複数のゴルーチンが同時に同じマップから読み込みや書き込みを行うと、パニックが発生します(「マップの同時読み込みと書き込み」)。並行処理のシナリオでは、sync.Map(第16課)を使用するか、ミューテックスを追加してください(第16課)。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐)map[string]int を使用して、あるテキスト中に各単語が何回出現するかを数えてください。入力として "the quick brown fox jumps over the lazy dog the" が与えられた場合、出力は map[the:3 quick:1 brown:1 ...] となるようにしてください。

  2. 上級問題(難易度 ⭐⭐)Student 構造体 (Name string, Scores []int) を定義し、以下のメソッドを実装してください:(1) 平均スコアを計算する Average() float64;(2) 成績(A/B/C/D/F)を返す Grade() string; (3) 少なくとも3人の生徒を対象にテストを行う。

  3. 課題(難易度 ⭐⭐⭐)連絡先リストアプリを実装する:map[string]Contact を使用して連絡先を保存する(Contact には名前、電話番号、メールアドレス、グループが含まれる)。(1) 追加、削除、検索機能、(2) グループ(家族/友人/仕事)によるフィルタリング機能を実装する; (3) JSONファイルへのエクスポート(os.WriteFileを使用)。要件:完全なエラー処理+JSONタグ+テスト用連絡先を少なくとも10件。

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%