📚 Swift入門シリーズ: #17 オプショナル型 → #18 関数の基本
前回は関数の基本的な定義と呼び出し方を学びました。今回は、関数の引数をより柔軟に扱う方法について学びましょう。
来春からデータサイエンティストとして働く予定の技術オタク。
『知りたい』気持ちで質問を止められない、好奇心旺盛な学生。
前回は関数の基本を学んだね!結構簡単だったよ!
良かったね!今回は、関数の引数をもっと柔軟に扱う方法を学んでいくよ。前回より少し高度だけど、Swiftの関数をより効果的に使えるようになるんだ。
引数ラベルの詳細
前回、引数ラベルについて少し触れたけど、もっと詳しく見ていこう。Swiftの関数には、引数ラベル(外部名) と パラメータ名(内部名) の2つの名前があるんだ。
func greet(to name: String) {
print("こんにちは、\(name)さん")
}
greet(to: "田中")
to が引数ラベルで、name がパラメータ名なんだ。
なんで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("田中") // 引数ラベルなしで呼び出せる
引数ラベルを _ にすると、呼び出し時に名前を書かなくて良くなるんだよ。
いつ _ を使えばいいの?
パラメータの意味が明らかな場合 や 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歳、アメリカ)
デフォルト値を設定することで、呼び出し側で省略できるようになるんだ。
これは便利!よく使う値をデフォルトにしておけるね!
そうなんだ。デフォルト引数値は、通常 最後のパラメータから順に設定する のが良い慣習なんだよ。
// 良い例:デフォルト値は後ろから
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)")
}
可変長引数
パラメータの数が決まっていない場合はどうするの?例えば、何個でも数値を受け取って合計を計算したい場合とか。
そういう場合は、可変長引数 を使うんだ。...(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型の可変長引数」という意味になるんだ。何個でも引数を渡せるんだよ。
すごい!これは便利!
関数の中では、可変長引数は 配列として扱われる んだ。
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つだけ 可変長引数を持てる
- 可変長引数の 後のパラメータにはデフォルト値が必要
// 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パラメータ
関数の中で、引数の値を変更したいんだけど、できないの?
通常、関数のパラメータは 定数 として扱われるから、変更できないんだ。
func increment(number: Int) {
// number += 1 // エラー!パラメータは変更できない
}
でも、inoutパラメータ を使えば、引数の値を変更できるんだよ。
func increment(number: inout Int) {
number += 1
}
var count = 5
increment(number: &count) // & を付ける
print(count) // 6
inout キーワードを付けると、引数の値を変更できるようになるんだ。呼び出し時には & を付ける必要があるよ。
へー!& って何?
& は「この変数への参照を渡す」という意味なんだ。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パラメータには、いくつか制約があるんだ。
- 定数や文字列リテラルは渡せない(変数だけ)
- デフォルト値は設定できない
- 可変長引数にはできない
func increment(number: inout Int) {
number += 1
}
var count = 5
increment(number: &count) // OK
// let constant = 5
// increment(number: &constant) // エラー!定数は渡せない
// increment(number: &10) // エラー!リテラルは渡せない
いつ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を返す関数」という意味だよ。
関数を変数に入れられるの?
そうなんだ!関数を パラメータとして渡す こともできるんだよ。
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 を作ってみて。区切り文字はデフォルトで ", " にしてね。
やってみる!
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行で書けるんだ。
すごい!そんな方法があるんだ!
問題2: inoutパラメータを使って、2つの整数の最大公約数(GCD)と最小公倍数(LCM)を計算する関数を作ってみて。
えーと...
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で引数の値を変更できる- 呼び出し時に
&が必要 - 複数の値を変更する場合に便利
関数型:
- 関数自体が型として扱える
(引数の型) -> 戻り値の型- 関数をパラメータとして渡せる
次回は「クロージャの基本」として、無名関数やクロージャの記法について学びます。より柔軟なコードが書けるようになりましょう!