詳解Swift 第5版 第2章まとめ -関数定義における設定-


こんにちは、マっさんです!

この記事は筆者に必要な部分を切り出し、まとめたものです。
詳しい内容を知りたい方は、ご購入をおすすめします。

詳解 Swift 第5版

詳解 Swift 第5版

-関数定義における設定-

inout引数

関数内での処理によって呼び出し側の変数の値を書き換えたい場合は、
inout引数
を利用します。

func mySwap(_ a: inout Int, _ b: inout Int){

    // 二つの引数の値を入れ替える
    let t = a; a =b; b =t

}

inout引数を指定した関数を呼び出して使うために、"&"を付けた変数を指定する必要があります。

var x =100
var y =0
mySwap(&x, &y) //実行後、x =0, y =100 となる

関数の引数に既定値を指定する

関数の引数の中には、決まった値をが指定される引数が存在することがありますが、それを
既定値
として決まった値を割り当てておき、呼び出す際には実引数の指定を省略出来るようにしておけば、便利になります。
例としまして、

  • print(_:separator:terminator:)

の指定が挙げられます。
また、以下のプログラムが引数に既定値が指定された関数になります。

let fontSize: Float = 12.0
func setFont(name:String, size:Float = fontSize, bold:Boolean = false) {

    print("\(name)\(size)" + (bold ? " [B]" : ""))

}

func setGray(level:Int = 255, _ alpha:Float = 1.0) {

    print("Gray=\(level), Alpha=\(alpha)")

}


// 関数の呼び出し、及び結果
// この関数は第一引数意外を省略することができます
setFont(name:"RaglanPunch")            // RaglanPunch 12.0
setFont(name:"Courier", bold:true)    // Courier 12.0 [B]
setFont(name:"Times", size:16.0, bold:true) // Times 16.0 [B]

// この関数は二つの引数を省略できますが、引数の順番は固定されています
setGray()                 // Gray=255, Alpha=1.0
setGray(level:240)        // Gray=240, Alpha=1.0
setGray(level:128, 0.5) // Gray=128, Alpha=0.5

以下が指定された引数の定義と使い方に関するルールです。

  • 既定値を持つ引数は引数リストのどこにあっても構いません。しかし、その引数よりも後ろ(右)の既定値を持たない引数には必ず引数ラベルを付けてください。

  • 引数ラベルを持つ複数の引数に、既定値が指定されている場合、引数リストで定義されている順序が守られているかぎり、どれをラベル指定しても、省略しても構いません

  • 引数ラベルを持たない引数に既定値が指定されている場合、ある引数から後ろ(右)をまとめて省略できますが、途中の引数だけを省略することはできません

まとめるとこうです。

既定値が指定された引数の定義と使い方は、自由度は高いが、紛らわしい為、引数ラベルを必ずつけるようにすべきです。

引数の値は関数内で変更できない

Swiftでは、関数の仮引数に与えられた値は、基本的に処理中に変更はできません。 変更して使いたい場合には、以下のようにすれば良いです。

    var y = yy, m = mm

つまり、値を変更すれば良いのです。
この考えを用いたプログラムが以下です。

// このプログラムは日付から曜日を計算するツェラーの公式をプログラムにしたものです。

func daytOfWeek(_ y : Int, _ m : Int, _ d:Int) -> Int {

    var y = y, m = m     // !!変数y, m は仮引数とは違います!!!

    if m < 3 {             //1,2 月だったら
       m += 12; y -= 1  // 前年の13月、14月として計算する
    }

    let leap = y + y/4 - y/100 + y/400

    return (leap + (13 * m + 8) / 5 + d) % 7

}

print(dayOfWeek(1994, 2, 1))  // 1994.02.01  -> result:2(火曜日)
print(dayOfWeek(2019, 11, 30)) // result: 6(土曜日)

返り値を使わない場合を許す指定

Swiftの場合、関数呼び出しの結果の値が使われない場合、コンパイラが警告を出します。
本当に値を使う必要がないことを示すには、"_"に対する代入を記述すべきです。

ただし、必要な時だけ返り値を使えば十分で、本来なら無視して良い値の場合、定義の直前に
属性
を指定します。やり方は以下の通りです。

@discardableResult // 属性(attribute)
func sayHello(to n:String) -> Bool { ... }

このような書き方をすれば、コンパイラやインタプリタは特に警告を表示しません。

関数内の関数(ネスト関数)

ネスト関数とは、その関数だけが下請けとして使用できる関数定義を、自分自身の定義の中に含めることができます。
関数内で定義された関数は外部からはアクセスできません。
以下がプログラム例となります。

// ひと月のカレンダーを表示する関数

func printMonth(first fday:Int, days:Int) {
    var d = 1 - fday             // 月のはじまりの空白は負と0で表す
    func daystr() -> String {    // 関数内のローカルな関数
        if d <= 0 {              // 変数dは上で定義したもの
            return "    "        // 月初めの空白
        }else {
            return (d < 10 ? "   \(d)" : "  \(d)")
        }
    }

    while d <= days {
        var line = ""             // 一週間分の日付を並べる
        for _ in 0 ..< 7 {
            line += daystr()      // ネスト関数を使用
            d += 1
            if d > days { break } // 月末になったら抜ける
        }
        print(line)
    }
}

printMonth(first:1, days:31)

結果は以下のようになります。

       1   2   3   4   5   6
   7   8   9  10  11  12  13
  14  15  16  17  18  19  20
  21  22  23  24  25  26  27
  28  29  30  31

このネスト関数はクロージャーとしての性質も持ちます。(クロージャーに関しては後ほど。)

考察

今回は関数の定義についてまとめました。
Swiftの開発を初めて半年が過ぎた私にとって、こういった書き方をまとめられているのはありがたいですw
実際の開発を行い、文法レイヤーではわかりますが、なぜこの様な書き方になっているのか?なぜこの書き方ができるのか?というところ はどの様に調べたら良いかいまいち掴めていませんでした。
ここでは、引数ラベルの部分や、ネスト関数に当たります。
引数にラベルつけること自体初めて見ましたし、ネスト関数は基本的に他の言語では使ったことはありませんでした。
やはり現場レベルでの実装経験や、その様な知識をつけることは必要と感じたこの頃でした。

今回は以上!

詳解 Swift 第5版

詳解 Swift 第5版