(環境)
xcode:11.3
version 5.1.3
(参考文献)
詳解 Swift 第5版
著者 萩原剛志
発行者 SBクリエイティブジャブ式会社
上記の文献で以下のコードに遭遇し、戸惑っております。
① case1の+演算子の定義については -> Clock型でreturnで返り値を定めているのに、何故、case2においては、返り値の型もreturnもせずにtac.toString()で返り値の23:00が得られるのでしょうか?両者の違いがどうしてもわかりません。
②上記の①と関連するかもしれませんが、case2については、仮引数のlhs(左辺)について、何故参照渡しのinoutがついているのでしょうか?参照渡しの際に利用して、実際に参照する時は&を付けることは学んでいるのですが、その様な利用はされていませんし、case1との違いについても理解することができません。
基本的なことであれば誠に申し訳ございませんが、1日読み悩み続けて投稿させていただきました。。
よろしくお願い申し上げます。
Swift
1struct Clock { 2 var hour = 0, min = 0 3 mutating func advance(min:Int){ 4 let m = self.min + min 5 if m >= 60 { 6 self.min = m % 60 7 let t = self.hour + m / 60 8 self.hour = t % 24 9 }else{ 10 self.min = m 11 } 12 } 13 14 func toString() -> String { 15 let h = hour < 10 ? " (hour)": "(hour)" 16 let m = min < 10 ? "0(min)": "(min)" 17 return h + ":" + m 18 } 19 20 static func +(lhs: Clock, rhs: Int) -> Clock { //case1 21 var t = lhs 22 t.advance(min: rhs) 23 return t 24 } 25 static func +=(lhs: inout Clock, rhs: Int){ //case2 26 lhs.advance(min: rhs) 27 } 28} 29 30let tick = Clock(hour: 19, min: 40) 31var tac = tick + 75 32print(tac.toString()) //20:55 33tac += 125 34print(tac.toString()) //23:00 35
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
ベストアンサー
Case1
swift
1let a = Clock(hour: 1, min: 1) 2let c = a + 1
としたとき、Swiftはc = a + 1
を、Case1の定義を使って、まるで
c = +(lhs: a, rhs: 1)
と書かれたかのように解釈し、+
関数を呼び出します。
+
演算子の左右のパラメータを、+
関数のそれぞれの引数とみなして呼び出すわけです。
+
関数が戻り値を返すのは、a + 1
が値を返すべき式だからです。戻り値を返さないと、a + 1
が値をもたないという意味になり、普通の+
演算とは違う動作をするものになります。こういう使い方はプログラムを読みにくくするので、よくないとされています。
普通の+
演算子と同じようにふるまうために、値を返すほうが好ましいので、そのように定義したのだと思います。
Case2
var a = Clock(hour: 1, min: 1) a += 1
上記のように書いたとき、Swiftは、case1の場合と同様に、a += 1
を、+=
関数の定義に当てはめます。
a += 1
の+=
の左右のパラメータはa
と1
なので、+=(lhs: &a, rhs: 1)
というように解釈します。
a += 1
はa
に1
を足すという意味であり、a
に値を書き込む必要があります。関数の中で引数そのものを書き換えるには、inout
をつけておかなければなりません。inout
があるので、引数としてa
ではなくて&a
として解釈されています。見た目には分かりませんが、実際の動きでは、参照渡しが行われ、それによってa
を更新することができています。
また、Swiftでは普通の+=
演算子は値を返さないので、ここでは値を返さないように定義されています。
なぜCase1とCase2は戻り値と引数が違うのか
これはSwiftの+
演算子と+=
演算子の動きが違うからです。
普通の+
演算子と+=
演算子の動きに似せるために、この例題での+
関数と+=
関数の定義は変えてあるのだと思います。
蛇足ですが、例えばC/C++みたいに、+=
が値を返すように定義することも可能です。
swift
1 @ discardableResult 2 static func +=(lhs: inout Clock, rhs: Int) -> Clock { //case2 3 lhs.advance(min: rhs) 4 return lhs 5 }
Cが好きなら、こういう定義を喜ぶかもしれませんね。Cと同じように、let b = a += 1
みたいな書き方ができるようになります。
しかし、一般に良い考えではありません。
Swiftの普通の+=
演算値の動きとは違うため、コードが読みにくくなるからです。
応用
ところで、c = a + 1
のところを、c = 1 + a
とすると、Cannot convert value of type 'Clock' to expected argument type 'Int'
と、エラーになります。
これはSwiftが、一つしかない+
関数の定義を使って、+(lhs: 1, rhs: a)
と解釈し、引数の型が合わないので「rhs
の型であるInt
にClock
を変換しようとしたが、できないよ」とエラーにしているものです。
ちょっと変ですね。普通はa + 1
と1 + a
は同じであると期待されると思います。数学で交換法則と言いましたっけ。数学では当たり前すぎて変に思えるような概念でしたが、プログラムの世界ではこれも定義してあげないと、エラーになるわけです。
Clock
の定義に以下を追加すれば、Clockが交換法則を満たすようになります。
swift
1 static func +(lhs: Int, rhs: Clock) -> Clock { 2 return rhs + lhs 3 }
なんでも定義しないといけなくて、一見面倒なようですが、見方を変えると、Swiftは演算子の動きもカスタマイズできる、非常に柔軟で強力な言語であることの裏返しだと思っています。
投稿2020/08/17 14:32
総合スコア803
0
返り値の型もreturnもせずにtac.toString()で返り値の23:00が得られるのでしょうか?
返り値無しなので、返り値使っていませんよね?
+=
の返り値を使うというのは、var xxx = (tac += 125)
のようにすると言うことですよ。そうすると返り値無しなのでエラーになると思います。
何故参照渡しのinoutがついているのでしょうか?
左辺を更新するからでは?
投稿2020/08/16 05:49
総合スコア84557
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/16 11:30
2020/08/16 13:37
2020/08/16 23:30
2020/08/17 11:23
2020/08/18 03:17
0
① case1の+演算子の定義については -> Clock型でreturnで返り値を定めているのに、何故、case2においては、返り値の型もreturnもせずにtac.toString()で返り値の23:00が得られるのでしょうか?両者の違いがどうしてもわかりません。
これは(2)と密接に関係するのですが、case2 の lhs
は参照渡しとなっているためです。
+=
演算子の左側の型(この場合 Clock
) のインスタンスに対して直接 advance(min)
を実行しているため、値が直接書き換わっています。
一方、case1 の場合にはインスタンスのコピーを返しています。
②上記の①と関連するかもしれませんが、case2については、仮引数のlhs(左辺)について、何故参照渡しのinoutがついているのでしょうか?参照渡しの際に利用して、実際に参照する時は&を付けることは学んでいるのですが、その様な利用はされていませんし、case1との違いについても理解することができません。
演算子の左側にあるインスタンスを直接書き換えたいからと思われます。
投稿2020/08/16 05:42
編集2020/08/16 11:12総合スコア5086
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/16 09:16
2020/08/16 11:12
2020/08/16 13:39
0
演算子には値の意味または操作の意味があります。二つともある書き方は禁じられていませんが、分析し難くなるのでオススメしません。
値の意味は戻り値で示します。操作の意味は参照に沿って参照先を変えることで示します。
例えば:
swift
1let tick = Clock(hour: 19, min: 40)
上記のコードを見ながら、下記のコードにはどんな意味を付けたいですか?
swift
1var tac = tick + 75
多分下記のような:
swift
1var tac = Clock(hour: 20, min: 55)
共通の部分を除いて、以下のようになります:
swift
1tick + 75 2Clock(hour: 20, min: 55)
このような前後で値の対応関係がある意味は値の意味です。後の値は戻り値になります。
func 文法で書いてみたら、こうなります:
swift
1func +(tick, 75) { 2 return Clock(hour: 20, min: 55) 3}
ちなみに、tick の意味も値の意味ですから、「戻り値」で書き換えることで、意味を示せます。
swift
1func +(Clock(hour: 19, min: 40), 75) { 2 return Clock(hour: 20, min: 55) 3}
もちろん、tick と 75 の場合だけ使える演算子はあまり役立たないでしょう。その二つを引数化すれば、文献のコードと似てるようになります:
swift
1func +(lhs: Clock, min: Int) -> Clock { 2 var tick = lhs 3 tick.advance(min) 4 return tick 5}
代わりに、+= の場合はどんな意味を付けたいですか?
swift
1tac += 125
多分このようでしょう:
swift
1tac = Clock(hour: 23, min: 00)
共通の部分はありません。先のように並べてみましょう:
swift
1tac += 125 2tac = Clock(hour: 23, min: 00)
このような前後で演算式の対応関係がある意味は操作の意味です。後のものは値ではないため、戻り値はいりません。
func 文法で書いてみたら、こうなります:
swift
1func +=(tac, 125) { 2 tac = Clock(hour: 23, min: 00) 3}
tac をその値で書き換えられなくなります。わざと書き換えてみましょう:
swift
1func +=(Clock(hour: 20, min: 55), 125) { 2 Clock(hour: 20, min: 55) = Clock(hour: 23, min: 00) 3}
意味わからなくなってきました。ここの tac の意味は値ではありませんね。参照しかありえない場合です。
引数化すると:
swift
1func +=(tac: inout Clock, n: Int) { 2 tac.advance(n) 3}
投稿2020/08/17 03:11
編集2020/08/18 13:31総合スコア463
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/17 14:05
2020/08/17 15:10
2020/08/17 22:04
2020/08/18 06:29
2020/08/18 11:58
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/18 03:14
2020/08/18 05:47
2020/08/18 12:54
2020/08/18 13:27
2020/08/18 14:30