関数

関数

関数は日常生活の「自動販売機」のようなものです — パラメータを入れると、あらかじめ定められたルールに従って処理し、結果を返します。Goでは、関数は第一級市民です:変数に代入でき、引数として渡せ、値として返せ、名前がないこともあります。関数をマスターすることは、エレガントなGoコードを書くための重要なステップです。


1. コアコンセプト

コンセプト 説明
func定義 funcキーワードで関数を宣言し、パラメータと戻り値の型をサポート
複数戻り値 Goの関数は複数の値を返せます。結果 + エラーでよく使用
名前付き戻り値 戻り値に名前を付け、関数本体で直接使用でき、returnで自動的に返す
可変長パラメータ ...Typeで任意の数の引数を受け取る
匿名関数 名前のない関数。即座の呼び出しや変数への代入によく使用
クロージャ 外部変数をキャプチャする匿名関数。クロージャを形成
init関数 各パッケージは複数のinit関数を持て、プログラム起動時に自動実行
defer 遅延実行。関数が返る前にスタック順序で実行

2. 基本構文/使い方

関数定義

GO
// 基本的な関数定義
func functionName(parameterList) returnType {
    // 関数本体
    return value
}

// パラメータなし、戻り値なし
func sayHello() {
    fmt.Println("Hello!")
}

// パラメータと戻り値付き
func add(a int, b int) int {
    return a + b
}

// パラメータ型が同じ場合の省略
func add(a, b int) int {
    return a + b
}

複数戻り値

GO
// 2つの値を返す:結果とエラー
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数はゼロにできません")
    }
    return a / b, nil
}

// 呼び出し時に両方の戻り値を受け取る
result, err := divide(10, 3)

名前付き戻り値

GO
// 名前付き戻り値:関数本体で変数名を直接使用
func rectangleInfo(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return // 名前付き戻り値を自動的に返す
}

可変長パラメータ

GO
// 可変長パラメータ:...Typeで任意の数の引数を受け取る
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// 呼び出し
sum(1, 2, 3)       // 6
sum(1, 2, 3, 4, 5) // 15

匿名関数とクロージャ

GO
// 匿名関数を変数に代入
greet := func(name string) string {
    return "Hello, " + name
}

// 即座に呼び出される関数
func() {
    fmt.Println("即座に実行")
}()

// クロージャ:外部変数をキャプチャ
func counter() func() int {
    count := 0
    return func() int {
        count++ // 外部のcount変数をキャプチャ
        return count
    }
}

init関数

GO
// init関数:プログラム起動時に自動実行。手動呼び出しは不要
// 各パッケージは複数のinit関数を持てる
func init() {
    fmt.Println("init関数が実行されました")
}

defer

GO
// defer:遅延実行。関数が返る前にLIFO(後入れ先出し)順序で実行
func readFile(filename string) {
    f, _ := os.Open(filename)
    defer f.Close() // 関数終了前にファイルを閉じる
    // ... ファイル読み取り
}
💡 ヒント: Goでは、全ての関数引数は値渡しです。スライス、map、チャネル、ポインタなどは参照のコピーを渡しますが、それらが指すデータは共有されます。

💡 ヒント: defer文の引数は宣言時に評価され、実行時ではありません。これはよくある落とし穴です。

💡 ヒント: init関数は他の関数から呼び出すことはできず、mainの前に自動的に実行されます。複数のinit関数がある場合、ソースファイルのアルファベット順に実行されます。


3. 基本構文/使い方(コード例)

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

目的: 基本的な関数定義、呼び出し、複数戻り値を学びます。

GO
package main

import (
    "errors"
    "fmt"
)

// greetは名前を受け取り、挨拶を返します
func greet(name string) string {
    return "Hello, " + name + "!"
}

// addは2つの整数の合計を返します
func add(a, b int) int {
    return a + b
}

// swapは2つの文字列をスワップします(複数戻り値のデモ)
func swap(a, b string) (string, string) {
    return b, a
}

// divideはエラー処理をデモンストレーションします
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数はゼロにできません")
    }
    return a / b, nil
}

func main() {
    // 単一戻り値の関数を呼び出し
    fmt.Println(greet("Alice")) // 出力:Hello, Alice!
    fmt.Println(add(3, 5))      // 出力:8

    // 複数戻り値の関数を呼び出し
    x, y := swap("hello", "world")
    fmt.Println(x, y) // 出力:world hello

    // エラー処理付きの関数を呼び出し
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Printf("10 / 3 = %.2f\n", result) // 出力:10 / 3 = 3.33
    }

    // ゼロ除算エラーのテスト
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("エラー:", err) // 出力:エラー: 除数はゼロにできません
    }
}
▶ 試してみよう

出力:

TEXT
Hello, Alice!
8
world hello
10 / 3 = 3.33
エラー: 除数はゼロにできません

要点:


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

目的: 名前付き戻り値、可変長パラメータ、匿名関数、クロージャを学びます。

GO
package main

import "fmt"

// ============ 名前付き戻り値 ============

// rectangleは長方形の面積と周長を計算します(名前付き戻り値を使用)
func rectangle(length, width float64) (area, perimeter float64) {
    area = length * width           // 名前付き変数を直接使用
    perimeter = 2 * (length + width)
    return // 名前付き戻り値を自動的に返す。return area, perimeterと書く必要なし
}

// ============ 可変長パラメータ ============

// sumは任意の数の整数の合計を計算します
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// averageは平均を計算し、可変長パラメータと他のパラメータの混在をデモンストレーションします
func average(first float64, rest ...float64) float64 {
    total := first
    count := 1.0
    for _, v := range rest {
        total += v
        count++
    }
    return total / count
}

// ============ 匿名関数 ============

// applyは整数に関数を適用し、結果を返します
func apply(n int, f func(int) int) int {
    return f(n)
}

func main() {
    // 名前付き戻り値
    area, perimeter := rectangle(5, 3)
    fmt.Printf("面積: %.1f、周長: %.1f\n", area, perimeter)
    // 出力:面積: 15.0、周長: 16.0

    // 可変長パラメータ
    fmt.Println("sum(1,2,3):", sum(1, 2, 3))           // 6
    fmt.Println("sum(1,2,3,4,5):", sum(1, 2, 3, 4, 5)) // 15

    // 可変長パラメータ:スライスの渡し方
    nums := []int{10, 20, 30}
    fmt.Println("sum(nums...):", sum(nums...)) // 60(...でスライスを展開)

    // 可変長パラメータと他のパラメータの混在
    fmt.Printf("average: %.2f\n", average(10, 20, 30)) // 20.00

    // 引数としての匿名関数
    double := func(n int) int { return n * 2 }
    square := func(n int) int { return n * n }

    fmt.Println("apply(5, double):", apply(5, double)) // 10
    fmt.Println("apply(5, square):", apply(5, square)) // 25

    // 匿名関数の即座呼び出し
    msg := "Hello"
    func(m string) {
        fmt.Println(m)
    }(msg)

    // ============ クロージャ ============

    // counterはカウンタークロージャを返します
    counter := func() func() int {
        count := 0
        return func() int {
            count++ // 外部変数countをキャプチャ
            return count
        }
    }()

    fmt.Println("counter:", counter()) // 1
    fmt.Println("counter:", counter()) // 2
    fmt.Println("counter:", counter()) // 3

    // アキュムレーターを実装するクロージャ
    adder := func() func(int) int {
        sum := 0
        return func(n int) int {
            sum += n
            return sum
        }
    }()

    fmt.Println("adder(10):", adder(10)) // 10
    fmt.Println("adder(20):", adder(20)) // 30
    fmt.Println("adder(30):", adder(30)) // 60
}
▶ 試してみよう

出力:

TEXT
面積: 15.0、周長: 16.0
sum(1,2,3): 6
sum(1,2,3,4,5): 15
sum(nums...): 60
average: 20.00
apply(5, double): 10
apply(5, square): 25
Hello
counter: 1
counter: 2
counter: 3
adder(10): 10
adder(20): 30
adder(30): 60

要点:


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

目的: リアルワールドの関数デザインパターン:ファンクショナルオプションパターン、ミドルウェアチェーン、エラー処理付きdefer。

GO
package main

import (
    "fmt"
    "strings"
    "time"
)

// ============ ファンクショナルオプションパターン ============

// サーバー設定
type Server struct {
    host    string
    port    int
    timeout time.Duration
    maxConn int
}

// Optionは設定オプションの関数型
type Option func(*Server)

// WithHostはホストアドレスを設定します
func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

// WithPortはポートを設定します
func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

// WithTimeoutはタイムアウト時間を設定します
func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

// WithMaxConnは最大接続数を設定します
func WithMaxConn(max int) Option {
    return func(s *Server) {
        s.maxConn = max
    }
}

// NewServerはオプションパターンでサーバーを作成します
func NewServer(opts ...Option) *Server {
    // デフォルト設定
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        maxConn: 100,
    }
    // 全てのオプションを適用
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// StringはStringerインターフェースを実装します
func (s *Server) String() string {
    return fmt.Sprintf("Server{host:%s, port:%d, timeout:%v, maxConn:%d}",
        s.host, s.port, s.timeout, s.maxConn)
}

// ============ ミドルウェアチェーンパターン ============

// Handlerは処理関数型
type Handler func(string) string

// Middlewareはミドルウェア型
type Middleware func(Handler) Handler

// loggingMiddlewareは入力と出力をログに記録します
func loggingMiddleware(next Handler) Handler {
    return func(input string) string {
        fmt.Printf("[ログ] 入力: %s\n", input)
        result := next(input)
        fmt.Printf("[ログ] 出力: %s\n", result)
        return result
    }
}

// upperMiddlewareは入力を大文字に変換します
func upperMiddleware(next Handler) Handler {
    return func(input string) string {
        return next(strings.ToUpper(input))
    }
}

// wrapMiddlewareは出力を括弧で囲みます
func wrapMiddleware(next Handler) Handler {
    return func(input string) string {
        return "【" + next(input) + "】"
    }
}

// chainは複数のミドルウェアをチェーンに結合します
func chain(handler Handler, middlewares ...Middleware) Handler {
    // 後ろから前にラップし、最初のミドルウェアが最初に実行されるようにする
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ============ 実践的なdefer:リソースクリーンアップとエラーリカバリ ============

// simulateDBはデータベース操作をシミュレートします
func simulateDB() (err error) {
    fmt.Println("1. データベース接続を取得中")

    // deferはLIFO順序で実行
    defer fmt.Println("5. データベース接続を解放(最後に実行)")
    defer fmt.Println("4. 操作ログを書き込み中")
    defer fmt.Println("3. カーソルを閉じ中")

    fmt.Println("2. クエリを実行中")

    // panicリカバリをシミュレート
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("  [リカバリ] panicをキャッチ: %v\n", r)
            err = fmt.Errorf("操作失敗: %v", r)
        }
    }()

    // panicをシミュレート
    panic("接続タイムアウト")

    // 以下のコードは実行されない
    // fmt.Println("クエリ完了")
    // return nil
}

func main() {
    // ===== ファンクショナルオプションパターン =====
    fmt.Println("=== ファンクショナルオプションパターン ===")

    // デフォルト設定を使用
    s1 := NewServer()
    fmt.Println(s1)

    // カスタム設定
    s2 := NewServer(
        WithHost("0.0.0.0"),
        WithPort(9090),
        WithTimeout(60*time.Second),
        WithMaxConn(500),
    )
    fmt.Println(s2)

    // ===== ミドルウェアチェーン =====
    fmt.Println("\n=== ミドルウェアチェーン ===")

    // 基本ハンドラ
    handler := func(s string) string {
        return "処理済み: " + s
    }

    // ミドルウェアを結合
    final := chain(handler, loggingMiddleware, upperMiddleware, wrapMiddleware)
    result := final("hello world")
    fmt.Println("最終結果:", result)

    // ===== deferとエラーリカバリ =====
    fmt.Println("\n=== defer実行順序 ===")
    err := simulateDB()
    if err != nil {
        fmt.Println("メイン関数がエラーをキャッチ:", err)
    }
}
▶ 試してみよう

出力:

TEXT
=== ファンクショナルオプションパターン ===
Server{host:localhost, port:8080, timeout:30s, maxConn:100}
Server{host:0.0.0.0, port:9090, timeout:1m0s, maxConn:500}

=== ミドルウェアチェーン ===
[ログ] 入力: HELLO WORLD
[ログ] 出力: 【処理済み: HELLO WORLD】
最終結果: 【処理済み: HELLO WORLD】

=== defer実行順序 ===
1. データベース接続を取得中
2. クエリを実行中
  [リカバリ] panicをキャッチ: 接続タイムアウト
3. カーソルを閉じ中
4. 操作ログを書き込み中
5. データベース接続を解放(最後に実行)
メイン関数がエラーをキャッチ: 操作失敗: 接続タイムアウト

要点:


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

ケース1:エラー処理のカプセル化

実際のプロジェクトでは、統一されたエラー処理ロジックをカプセル化する必要があります:

GO
package main

import (
    "fmt"
    "strconv"
)

// Resultは統一された結果ラッパー
type Result struct {
    Data    interface{}
    Err     error
}

// parseJSONはJSONパースをシミュレートし、結果とエラーを返します
func parseJSON(jsonStr string) (map[string]interface{}, error) {
    // 簡略化デモ:実際のプロジェクトではencoding/jsonを使用
    if jsonStr == "" {
        return nil, fmt.Errorf("JSON文字列は空にできません")
    }
    return map[string]interface{}{"key": "value"}, nil
}

// parseIntは文字列を安全に整数に変換します
func parseInt(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("整数 '%s' のパースに失敗: %w", s, err)
    }
    return n, nil
}

// withRetryは関数をリトライします
func withRetry(attempts int, f func() error) error {
    var err error
    for i := 0; i < attempts; i++ {
        err = f()
        if err == nil {
            return nil
        }
        fmt.Printf("  試行 %d 失敗: %v\n", i+1, err)
    }
    return fmt.Errorf("%d回の試行後に失敗: %w", attempts, err)
}

func main() {
    // エラー処理
    result, err := parseJSON(`{"name": "test"}`)
    if err != nil {
        fmt.Println("パースエラー:", err)
        return
    }
    fmt.Println("パース結果:", result)

    // 安全な型変換
    num, err := parseInt("123")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("変換結果:", num)

    // リトライメカニズム
    attempt := 0
    err = withRetry(3, func() error {
        attempt++
        if attempt < 3 {
            return fmt.Errorf("シミュレーション失敗")
        }
        return nil
    })
    if err != nil {
        fmt.Println("最終失敗:", err)
    } else {
        fmt.Println("最終成功!")
    }
}

ケース2:関数をデータ処理パイプラインとして使用

GO
package main

import (
    "fmt"
    "strings"
)

// Transformはデータ変換関数型
type Transform func(string) string

// Pipelineはデータ処理パイプライン
type Pipeline struct {
    steps []Transform
}

// NewPipelineは新しいパイプラインを作成します
func NewPipeline() *Pipeline {
    return &Pipeline{}
}

// Addは処理ステップを追加します
func (p *Pipeline) Add(steps ...Transform) *Pipeline {
    p.steps = append(p.steps, steps...)
    return p
}

// Executeはパイプラインを実行します
func (p *Pipeline) Execute(input string) string {
    result := input
    for _, step := range p.steps {
        result = step(result)
    }
    return result
}

func main() {
    // 変換関数を定義
    trim := func(s string) string { return strings.TrimSpace(s) }
    lower := func(s string) string { return strings.ToLower(s) }
    replace := func(s string) string { return strings.ReplaceAll(s, " ", "-") }
    prefix := func(s string) string { return "processed-" + s }

    // 処理パイプラインを構築
    pipeline := NewPipeline().
        Add(trim, lower, replace, prefix)

    // パイプラインを実行
    result := pipeline.Execute("  Hello World Go  ")
    fmt.Println(result) // 出力:processed-hello-world-go

    // 複数の入力にパイプラインを再利用
    inputs := []string{"  Foo Bar  ", "  HELLO  ", "  Go Lang  "}
    for _, input := range inputs {
        fmt.Printf("%-20s => %s\n", input, pipeline.Execute(input))
    }
}

❓ よくある質問

質問1:関数パラメータは値渡しですか、参照渡しですか?

回答: Goでは、全ての関数パラメータは値渡しです。ただし、スライス、map、チャネル、ポインタなどの型は「参照のコピー」を渡します — それらが指すデータの変更は元のデータに影響しますが、再代入は元の変数に影響しません。

GO
package main

import "fmt"

// modifySliceはスライスの内容を変更します(元のスライスに影響)
func modifySlice(s []int) {
    s[0] = 999 // 要素の変更は元のスライスに影響
    fmt.Println("関数内:", s)
}

// reassignSliceはスライスを再代入します(元のスライスに影響しない)
func reassignSlice(s []int) {
    s = append(s, 4, 5, 6) // appendは新しい基本配列を作成する可能性がある
    fmt.Println("関数内:", s)
}

func main() {
    nums := []int{1, 2, 3}

    modifySlice(nums)
    fmt.Println("関数外:", nums) // [999 2 3]、元のスライスが変更された

    reassignSlice(nums)
    fmt.Println("関数外:", nums) // [999 2 3]、元のスライスは変更されない
}

質問2:deferの引数はいつ評価されますか?

回答: defer文の引数は宣言時に評価され、実行時ではありません。

GO
package main

import "fmt"

func main() {
    x := 10

    // defer引数は宣言時に評価、x = 10
    defer fmt.Println("defer内のx =", x)

    x = 20
    fmt.Println("変更後のx =", x)

    // defer実行時に評価が必要な場合はクロージャを使用
    defer func() {
        fmt.Println("クロージャdefer内のx =", x) // x = 20
    }()

    x = 30
}

出力:

TEXT
変更後のx = 20
クロージャdefer内のx = 30
defer内のx = 10

質問3:パッケージに複数のinit関数は持てますか?実行順序は?

回答: はい。パッケージ(甚至是単一ファイル)には複数のinit関数を持てます。実行順序:

  1. まず、インポートされたパッケージのinit関数を実行
  2. 同じパッケージ内では、ソースファイルのアルファベット順に実行
  3. 同じファイル内では、init関数の出現順に実行
GO
package main

import "fmt"

func init() {
    fmt.Println("最初のinit")
}

func init() {
    fmt.Println("2番目のinit")
}

func main() {
    fmt.Println("main関数")
}

出力:

TEXT
最初のinit
2番目のinit
main関数

質問4:オプションパラメータはどう実装しますか?

回答: Goはオプションパラメータを直接サポートしていません。以下の3つの方法が一般的に使用されます:

GO
package main

import "fmt"

// 方法1:構造体を使用
type Config struct {
    Host string
    Port int
    Debug bool
}

func connect(cfg Config) {
    fmt.Printf("%s:%dに接続中 (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

// 方法2:ファンクショナルオプションパターン(推奨)
type Option func(*Config)

func WithHost(host string) Option {
    return func(c *Config) { c.Host = host }
}

func WithPort(port int) Option {
    return func(c *Config) { c.Port = port }
}

func WithDebug(debug bool) Option {
    return func(c *Config) { c.Debug = debug }
}

func newConnect(opts ...Option) {
    cfg := &Config{Host: "localhost", Port: 8080, Debug: false}
    for _, opt := range opts {
        opt(cfg)
    }
    fmt.Printf("%s:%dに接続中 (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

func main() {
    // 構造体方式
    connect(Config{Host: "127.0.0.1", Port: 3306})

    // オプションパターン
    newConnect()
    newConnect(WithHost("127.0.0.1"), WithPort(3306), WithDebug(true))
}

📖 まとめ


📝 演習

演習1(⭐):基本的な関数演習

要件: 以下の関数を書き、テストしてください:

  1. max(a, b int) int — 2つの整数の大きい方を返す
  2. isEven(n int) bool — 整数が偶数かチェック
  3. swap(a, b *int) — ポインタを使って2つの整数をスワップ
GO
// 参考フレームワーク
package main

import "fmt"

func max(a, b int) int {
    // ここに実装
    return 0
}

func isEven(n int) bool {
    // ここに実装
    return false
}

func swap(a, b *int) {
    // ここに実装(ヒント:ポインタを使用)
}

func main() {
    fmt.Println(max(3, 5))     // 5が出力されるはず
    fmt.Println(isEven(4))     // trueが出力されるはず
    fmt.Println(isEven(7))     // falseが出力されるはず

    x, y := 10, 20
    swap(&x, &y)
    fmt.Println(x, y)          // 20 10が出力されるはず
}

演習2(⭐⭐):可変長パラメータとクロージャ

要件:

  1. filter(nums []int, predicate func(int) bool) []intを書き、条件を満たす要素をフィルタリング
  2. makeMultiplier(factor int) func(int) intを書き、乗算クロージャを返す
  3. filterとクロージャを使って、スライスから偶数を全て選択
GO
// 参考フレームワーク
package main

import "fmt"

func filter(nums []int, predicate func(int) bool) []int {
    // 実装:numsをイテレーションし、predicateを満たす要素を結果スライスに追加
    return nil
}

func makeMultiplier(factor int) func(int) int {
    // 実装:入力にfactorを掛けるクロージャを返す
    return nil
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 偶数をフィルタリング
    evens := filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("偶数:", evens) // [2 4 6 8 10]

    // 5より大きい数をフィルタリング
    big := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println("5より大きい:", big) // [6 7 8 9 10]

    // クロージャで乗算器を作成
    double := makeMultiplier(2)
    triple := makeMultiplier(3)

    fmt.Println("double(5):", double(5)) // 10
    fmt.Println("triple(5):", triple(5)) // 15
}

演習3(⭐⭐⭐):実践プロジェクト — コマンドパターン付き電卓

要件: コマンド登録をサポートする電卓システムを実装:

  1. コマンドマップmap[string]func([]int) intを持つCalculator構造体を定義
  2. Register(name string, op func([]int) int)でコマンドを登録
  3. Execute(name string, args []int) (int, error)でコマンドを実行
  4. 以下のコマンドを登録:add(合計)、mul(積)、max(最大値)
  5. init関数で全コマンドを自動登録
GO
// 参考フレームワーク
package main

import (
    "errors"
    "fmt"
)

type Calculator struct {
    // ここにフィールドを定義
}

func NewCalculator() *Calculator {
    // ここに実装
    return nil
}

func (c *Calculator) Register(name string, op func([]int) int) {
    // ここに実装
}

func (c *Calculator) Execute(name string, args []int) (int, error) {
    // 実装:コマンドを探して実行。見つからない場合はエラーを返す
    return 0, nil
}

func (c *Calculator) ListCommands() []string {
    // 実装:登録済みの全コマンド名を返す
    return nil
}

func main() {
    calc := NewCalculator()

    // コマンドを登録
    calc.Register("add", func(args []int) int {
        sum := 0
        for _, v := range args {
            sum += v
        }
        return sum
    })

    calc.Register("mul", func(args []int) int {
        product := 1
        for _, v := range args {
            product *= v
        }
        return product
    })

    calc.Register("max", func(args []int) int {
        if len(args) == 0 {
            return 0
        }
        m := args[0]
        for _, v := range args[1:] {
            if v > m {
                m = v
            }
        }
        return m
    })

    // テスト
    tests := []struct {
        cmd  string
        args []int
    }{
        {"add", []int{1, 2, 3, 4, 5}},
        {"mul", []int{2, 3, 4}},
        {"max", []int{10, 3, 7, 9, 1}},
    }

    for _, t := range tests {
        result, err := calc.Execute(t.cmd, t.args)
        if err != nil {
            fmt.Printf("エラー: %v\n", err)
        } else {
            fmt.Printf("%s(%v) = %d\n", t.cmd, t.args, result)
        }
    }

    // 全コマンドを一覧表示
    fmt.Println("利用可能なコマンド:", calc.ListCommands())

    // 存在しないコマンドをテスト
    _, err := calc.Execute("sqrt", []int{16})
    if err != nil {
        fmt.Println("エラー:", err)
    }
}

期待される出力:

TEXT
add([1 2 3 4 5]) = 15
mul([2 3 4]) = 24
max([10 3 7 9 1]) = 10
利用可能なコマンド: [add mul max]
エラー: 不明なコマンド: sqrt

次のレッスン

次のレッスン:配列とスライス →

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%