Swift教室 Swift と競技プログラミングの教室

値型と inout

読了目安 約2分

Swift の配列は値型で、代入も関数渡しもコピー。書き換えたいときの inout と &。

この章の目次

関数に配列を渡して、中身を書き換えてもらいたいことがあります。 その書き方を知るには、まず Swift の「値型」を理解する必要があります。

値型 — 代入はコピー

Swift の配列・構造体・数値は値型です。 値型は、代入した時点で中身がまるごとコピーされます。

Swift
let a = [1, 2, 3]
var b = a       // ここでコピーされる
b[0] = 100
print(a)
print(b)

ba のコピーなので、b を書き換えても a は変わりません。

関数に渡してもコピー

関数の引数も同じで、渡した時点でコピーが作られます。 さらに引数は定数扱いなので、関数の中で書き換えようとするとコンパイルエラーになります。

Swift
func double(_ x: Int) {
    x = x * 2   // コンパイルエラー: 引数は書き換えられない
}

inout と &

呼び出し元の変数そのものを書き換えたいときは、引数の型の前に inout を付けます。 呼び出す側は、変数名の前に & を付けて渡します。

Swift
func double(_ x: inout Int) {
    x = x * 2
}

var value = 21
double(&value)
print(value)

inout の約束事は 2 つです。

  • 渡せるのは var で宣言した変数だけ (let の定数やリテラルの 5 は渡せない)
  • 呼び出し時の & は省略できない

試してみよう: double(&value) をもう 1 回呼ぶと、84 が出力されることを確かめてください。

swap を作る

2 つの変数の中身を入れ替える関数は、inout の代表例です。

Swift
func mySwap(_ a: inout Int, _ b: inout Int) {
    let tmp = a
    a = b
    b = tmp
}

var x = 1
var y = 2
mySwap(&x, &y)
print(x, y)

同じ働きの swap(&x, &y) が標準ライブラリに用意されています。

値型という設計

C++ など、関数に渡した配列への書き換えが呼び出し元へそのまま伝わる言語もあります。

Swift では、書き換えが起きる場所に必ず var& が現れます。 「知らないところで配列が書き換わっていた」という事故が起きないことが、値型を基本にした Swift の設計思想です。

学びどころ

概念一言まとめ
値型代入・関数渡しで中身がコピーされる
inout呼び出し元の変数を書き換えられる引数
&inout に変数を渡すときの目印。省略不可
swap(&x, &y)標準ライブラリの入れ替え関数

演習

配列のすべての要素を 2 倍にする関数 doubleAll を完成させてください。

1 行に整数が空白区切りで入力されます。 すべて 2 倍にした値を、空白区切りで 1 行に出力してください。 読み取りと出力はスターターに用意してあるので、関数の中身だけを書きます。

制約: 個数は 1 以上 100 以下、値は -100 以上 100 以下

テキスト
入力例:
1 2 3

出力例:
2 4 6
模範解答
Swift
func doubleAll(_ a: inout [Int]) {
    for i in 0..<a.count {
        a[i] *= 2
    }
}

var a = readLine()!.split(separator: " ").map { Int($0)! }
doubleAll(&a)
print(a.map(String.init).joined(separator: " "))