【Swift入門 #19】柔軟な関数を作る - 引数ラベル、可変長引数、inoutパラメータ

Swiftの関数をより柔軟に使いこなすための、引数ラベル、可変長引数、inoutパラメータなどの高度な機能を初心者向けに解説します。

📚 Swift入門シリーズ: #17 オプショナル型#18 関数の基本


前回は関数の基本的な定義と呼び出し方を学びました。今回は、関数の引数をより柔軟に扱う方法について学びましょう。

著者
著者: Sera
大学院でAI作曲に関して研究中!
来春からデータサイエンティストとして働く予定の技術オタク。
初心者
登場人物: あかり
流行りのAIやWeb技術に興味津々!
『知りたい』気持ちで質問を止められない、好奇心旺盛な学生。
normalの表情
初心者

前回は関数の基本を学んだね!結構簡単だったよ!

専門家

良かったね!今回は、関数の引数をもっと柔軟に扱う方法を学んでいくよ。前回より少し高度だけど、Swiftの関数をより効果的に使えるようになるんだ。

引数ラベルの詳細

専門家

前回、引数ラベルについて少し触れたけど、もっと詳しく見ていこう。Swiftの関数には、引数ラベル(外部名)パラメータ名(内部名) の2つの名前があるんだ。

func greet(to name: String) {
    print("こんにちは、\(name)さん")
}

greet(to: "田中")

to が引数ラベルで、name がパラメータ名なんだ。

confusedの表情
初心者

なんで2つも名前が必要なの?

専門家

引数ラベルを使うことで、関数の呼び出しを自然な英文のように読める んだ。例を見てみよう。

// 引数ラベルなし
func move(x: Int, y: Int) {
    print("(\(x), \(y))に移動")
}
move(x: 5, y: 10)

// 引数ラベルあり
func move(to x: Int, and y: Int) {
    print("(\(x), \(y))に移動")
}
move(to: 5, and: 10)  // 「5に移動して、10」みたいに読める

2番目の方が、何をしているのか分かりやすいよね。

引数ラベルの省略

専門家

引数ラベルを省略したい場合は、_(アンダースコア)を使うんだ。

func greet(_ name: String) {
    print("こんにちは、\(name)さん")
}

greet("田中")  // 引数ラベルなしで呼び出せる

引数ラベルを _ にすると、呼び出し時に名前を書かなくて良くなるんだよ。

normalの表情
初心者

いつ _ を使えばいいの?

専門家

パラメータの意味が明らかな場合1つだけのパラメータ の場合に使うことが多いんだ。

// 良い例:意味が明らか
func print(_ message: String) {
    print(message)
}
print("Hello")

// 良い例:1つだけのパラメータ
func square(_ number: Int) -> Int {
    return number * number
}
print(square(5))  // 25

// 悪い例:複数のパラメータで意味が不明瞭
func calculate(_ a: Int, _ b: Int) -> Int {  // 何を計算しているか分からない
    return a + b
}

引数ラベルとパラメータ名を同じにする

専門家

引数ラベルとパラメータ名を同じにすることもできるんだ。

func greet(name: String) {
    print("こんにちは、\(name)さん")
}

greet(name: "田中")

この場合、name が引数ラベルとパラメータ名の両方になるんだよ。これが 最も一般的な書き方 なんだ。

デフォルト引数値の活用

専門家

前回、デフォルト引数値について学んだけど、もっと実践的な使い方を見てみよう。

func createUser(name: String, age: Int = 18, country: String = "日本") {
    print("ユーザー作成: \(name)さん(\(age)歳、\(country))")
}

createUser(name: "田中")
// ユーザー作成: 田中さん(18歳、日本)

createUser(name: "佐藤", age: 25)
// ユーザー作成: 佐藤さん(25歳、日本)

createUser(name: "鈴木", age: 30, country: "アメリカ")
// ユーザー作成: 鈴木さん(30歳、アメリカ)

デフォルト値を設定することで、呼び出し側で省略できるようになるんだ。

happyの表情
初心者

これは便利!よく使う値をデフォルトにしておけるね!

専門家

そうなんだ。デフォルト引数値は、通常 最後のパラメータから順に設定する のが良い慣習なんだよ。

// 良い例:デフォルト値は後ろから
func createMessage(text: String, sender: String = "システム", priority: Int = 0) {
    print("[\(priority)] \(sender): \(text)")
}

// 悪い例:デフォルト値が途中にある(混乱しやすい)
func createMessage(text: String = "メッセージ", sender: String, priority: Int = 0) {
    print("[\(priority)] \(sender): \(text)")
}

可変長引数

confusedの表情
初心者

パラメータの数が決まっていない場合はどうするの?例えば、何個でも数値を受け取って合計を計算したい場合とか。

専門家

そういう場合は、可変長引数 を使うんだ。...(3つのドット)を使って定義するんだよ。

func sum(_ numbers: Int...) -> Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}

print(sum(1, 2, 3))           // 6
print(sum(1, 2, 3, 4, 5))     // 15
print(sum(10, 20, 30, 40, 50, 60))  // 210

Int... で、「Int型の可変長引数」という意味になるんだ。何個でも引数を渡せるんだよ。

excitedの表情
初心者

すごい!これは便利!

専門家

関数の中では、可変長引数は 配列として扱われる んだ。

func printAll(_ items: String...) {
    print("アイテム数: \(items.count)")
    for (index, item) in items.enumerated() {
        print("\(index + 1). \(item)")
    }
}

printAll("りんご", "バナナ", "オレンジ")
// アイテム数: 3
// 1. りんご
// 2. バナナ
// 3. オレンジ

関数の中では、items[String] 型の配列として扱えるんだよ。

可変長引数の制限

専門家

可変長引数には、いくつか制限があるんだ。

  1. 1つの関数に1つだけ 可変長引数を持てる
  2. 可変長引数の 後のパラメータにはデフォルト値が必要
// OK:可変長引数が1つだけ
func average(_ numbers: Double...) -> Double {
    let sum = numbers.reduce(0, +)
    return sum / Double(numbers.count)
}

// OK:可変長引数の後にデフォルト値付きパラメータ
func format(_ numbers: Int..., separator: String = ", ") -> String {
    return numbers.map { String($0) }.joined(separator: separator)
}

print(format(1, 2, 3))              // 1, 2, 3
print(format(1, 2, 3, separator: " | "))  // 1 | 2 | 3

// NG:可変長引数が2つ(エラー)
// func combine(_ numbers: Int..., _ strings: String...) { }

// NG:可変長引数の後にデフォルト値なし(エラー)
// func process(_ numbers: Int..., multiplier: Int) { }

inoutパラメータ

confusedの表情
初心者

関数の中で、引数の値を変更したいんだけど、できないの?

専門家

通常、関数のパラメータは 定数 として扱われるから、変更できないんだ。

func increment(number: Int) {
    // number += 1  // エラー!パラメータは変更できない
}

でも、inoutパラメータ を使えば、引数の値を変更できるんだよ。

func increment(number: inout Int) {
    number += 1
}

var count = 5
increment(number: &count)  // & を付ける
print(count)  // 6

inout キーワードを付けると、引数の値を変更できるようになるんだ。呼び出し時には & を付ける必要があるよ。

surprisedの表情
初心者

へー!& って何?

専門家

& は「この変数への参照を渡す」という意味なんだ。inoutパラメータは、引数の コピー ではなく 参照 を渡すから、関数の中で変更すると元の変数も変更されるんだよ。

もう1つ例を見てみよう。

func swap(a: inout Int, b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 10
var y = 20

print("交換前: x=\(x), y=\(y)")  // 交換前: x=10, y=20

swap(a: &x, b: &y)

print("交換後: x=\(x), y=\(y)")  // 交換後: x=20, y=10

2つの変数の値を入れ替えることができるんだ。

inoutパラメータの制約

専門家

inoutパラメータには、いくつか制約があるんだ。

  1. 定数や文字列リテラルは渡せない(変数だけ)
  2. デフォルト値は設定できない
  3. 可変長引数にはできない
func increment(number: inout Int) {
    number += 1
}

var count = 5
increment(number: &count)  // OK

// let constant = 5
// increment(number: &constant)  // エラー!定数は渡せない

// increment(number: &10)  // エラー!リテラルは渡せない
normalの表情
初心者

いつinoutを使えばいいの?

専門家

inoutパラメータは、複数の値を変更して返したい 場合や、大きなデータ構造を効率的に変更したい 場合に使うんだ。

でも、通常は 戻り値で返す方が分かりやすい から、inoutは控えめに使うのがおすすめだよ。

// 通常はこちらの方が良い(戻り値で返す)
func increment(number: Int) -> Int {
    return number + 1
}

let result = increment(number: 5)

// inoutを使う場合(複数の値を変更)
func calculateStats(numbers: [Int], sum: inout Int, average: inout Double) {
    sum = numbers.reduce(0, +)
    average = Double(sum) / Double(numbers.count)
}

var totalSum = 0
var totalAverage = 0.0
calculateStats(numbers: [1, 2, 3, 4, 5], sum: &totalSum, average: &totalAverage)
print("合計: \(totalSum), 平均: \(totalAverage)")  // 合計: 15, 平均: 3.0

関数型(Function Type)

専門家

Swiftでは、関数自体も として扱えるんだ。

func add(a: Int, b: Int) -> Int {
    return a + b
}

func multiply(a: Int, b: Int) -> Int {
    return a * b
}

// 関数を変数に代入
var mathOperation: (Int, Int) -> Int

mathOperation = add
print(mathOperation(3, 5))  // 8

mathOperation = multiply
print(mathOperation(3, 5))  // 15

(Int, Int) -> Int関数型 なんだ。「2つのIntを受け取ってIntを返す関数」という意味だよ。

surprisedの表情
初心者

関数を変数に入れられるの?

専門家

そうなんだ!関数を パラメータとして渡す こともできるんだよ。

func calculate(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

func add(a: Int, b: Int) -> Int { a + b }
func subtract(a: Int, b: Int) -> Int { a - b }
func multiply(a: Int, b: Int) -> Int { a * b }

print(calculate(a: 10, b: 5, operation: add))       // 15
print(calculate(a: 10, b: 5, operation: subtract))  // 5
print(calculate(a: 10, b: 5, operation: multiply))  // 50

operation パラメータに、異なる関数を渡すことで、同じ calculate 関数で異なる計算ができるんだ。

実践練習

専門家

じゃあ、練習問題をやってみよう。

問題1: 可変長引数を使って、複数の文字列を受け取って、それらを指定した区切り文字で連結する関数 join を作ってみて。区切り文字はデフォルトで ", " にしてね。

excitedの表情
初心者

やってみる!

func join(_ strings: String..., separator: String = ", ") -> String {
    var result = ""
    for (index, string) in strings.enumerated() {
        result += string
        if index < strings.count - 1 {
            result += separator
        }
    }
    return result
}

print(join("りんご", "バナナ", "オレンジ"))
// りんご, バナナ, オレンジ

print(join("A", "B", "C", separator: " | "))
// A | B | C

できた!

専門家

素晴らしい!可変長引数とデフォルト引数値を正しく使えているね。

Swiftの標準機能を使うと、もっと短く書けるよ。

func join(_ strings: String..., separator: String = ", ") -> String {
    return strings.joined(separator: separator)
}

配列の joined メソッドを使えば、1行で書けるんだ。

happyの表情
初心者

すごい!そんな方法があるんだ!

専門家

問題2: inoutパラメータを使って、2つの整数の最大公約数(GCD)と最小公倍数(LCM)を計算する関数を作ってみて。

normalの表情
初心者

えーと...

func calculateGcdLcm(a: Int, b: Int, gcd: inout Int, lcm: inout Int) {
    // ユークリッドの互除法でGCDを計算
    var x = a
    var y = b
    while y != 0 {
        let temp = y
        y = x % y
        x = temp
    }
    gcd = x

    // LCMを計算
    lcm = (a * b) / gcd
}

var resultGcd = 0
var resultLcm = 0

calculateGcdLcm(a: 12, b: 18, gcd: &resultGcd, lcm: &resultLcm)
print("GCD: \(resultGcd)")  // GCD: 6
print("LCM: \(resultLcm)")  // LCM: 36

こう?

専門家

完璧だね!inoutパラメータを使って、2つの結果を同時に返せているよ。ユークリッドの互除法も正しく実装できているね。

ただし、この場合は タプルで返す方が分かりやすい かもしれないよ。

func calculateGcdLcm(a: Int, b: Int) -> (gcd: Int, lcm: Int) {
    // ユークリッドの互除法でGCDを計算
    var x = a
    var y = b
    while y != 0 {
        let temp = y
        y = x % y
        x = temp
    }
    let gcd = x
    let lcm = (a * b) / gcd

    return (gcd, lcm)
}

let result = calculateGcdLcm(a: 12, b: 18)
print("GCD: \(result.gcd)")  // GCD: 6
print("LCM: \(result.lcm)")  // LCM: 36

どちらの方法も正しいから、状況に応じて使い分けられるといいね。

まとめ

この記事では、関数の引数について詳しく学びました。

引数ラベル:

  • 外部名(引数ラベル)と内部名(パラメータ名)
  • _ で引数ラベルを省略
  • 自然な英文のように読める関数呼び出し

デフォルト引数値:

  • パラメータにデフォルト値を設定
  • 呼び出し時に省略可能
  • 通常は後ろのパラメータから設定

可変長引数:

  • 型... で何個でも引数を受け取れる
  • 関数内では配列として扱える
  • 1つの関数に1つだけ

inoutパラメータ:

  • inout で引数の値を変更できる
  • 呼び出し時に & が必要
  • 複数の値を変更する場合に便利

関数型:

  • 関数自体が型として扱える
  • (引数の型) -> 戻り値の型
  • 関数をパラメータとして渡せる

次回は「クロージャの基本」として、無名関数やクロージャの記法について学びます。より柔軟なコードが書けるようになりましょう!

← ブログ一覧に戻る