質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

4回答

1133閲覧

2項演算子定義における戻り値の有無について

pegy

総合スコア243

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/08/16 05:10

(環境)
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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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+=の左右のパラメータはa1なので、+=(lhs: &a, rhs: 1)というように解釈します。
a += 1a1を足すという意味であり、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の型であるIntClockを変換しようとしたが、できないよ」とエラーにしているものです。
ちょっと変ですね。普通はa + 11 + aは同じであると期待されると思います。数学で交換法則と言いましたっけ。数学では当たり前すぎて変に思えるような概念でしたが、プログラムの世界ではこれも定義してあげないと、エラーになるわけです。
Clockの定義に以下を追加すれば、Clockが交換法則を満たすようになります。

swift

1 static func +(lhs: Int, rhs: Clock) -> Clock { 2 return rhs + lhs 3 }

なんでも定義しないといけなくて、一見面倒なようですが、見方を変えると、Swiftは演算子の動きもカスタマイズできる、非常に柔軟で強力な言語であることの裏返しだと思っています。

投稿2020/08/17 14:32

eytyet

総合スコア803

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pegy

2020/08/18 03:14

eytyet様 コメントを寄せていただき誠にありがとうございます。 咀嚼をすることに時間がかかってしまいコメントバックが遅くなってしまったことについてお詫びさせていただきます。 やはりfunc +()や func +=()でそれぞれの演算子を定義するにしてSwiftでもともと備えている加算演算であったり代入結合演算を踏まえた上での再定義であるというように解釈致しました。 それであれば、その結果として値を返すべきかそうではないのかというのがどちらが自然であるのかということが納得がいく部分はあります。 おそらく当初私の発想としてはfunc +()や func +=()の演算子の再定義はもともとの記号の意味とは全くことなる性質を定義するのかもしれないと思って読んでいました。 おそらく実際にSwiftにおいては+と言いながらも 10 + 1 でreturn 9をするように-の意味に書き換えるような演算子の定義もできると理解しておりますが、今回のような記法は0から再定義するのではなく、加算や結合代入の性質を利用した上でのものだと解釈致しました。 そのうえで例えばcase1の加算演算子は通常の加算演算子と何が違うためここでfunc +(){}をしているのだろうと考えているのですが、 "普通の+演算子と+=演算子の動きに似せるために、この例題での+関数と+=関数の定義は変えてあるのだと思います" のようにコメントを頂いていたと思います。 実はこの点は直感的には理解ができなかったのですが、 「普通の加算演算子+では例えば Int型 + Int型でないと Int型の結果をreturnしないので、 Clock型 + Int型 でreturn Clock型という特殊な演算をするために、加算演算のオリジナルの左辺と右辺の加えるという性質(つまり左辺と右辺を加算した結果をreturnするという性質)を利用しつつ、異なる方でも左辺と右辺で演算できるような特殊な+演算を定義した」 と解釈しておりました。その点では頂いたコメントと齟齬が生じるため誤りがあればご指摘頂ければと思います。 ともすれば、確かに本コードで定義している内容は交換の法則まで定義していない、つまり Clock型 + Int型 return Clock型であるとは定義しているが Int型 + Clock型 return Clock型とは定義していない のでご指摘のある交換の法則については、なるほどと感じました。 また、蛇足となるかもしれませんが、 また、サーバー再度はPHPしか書いたことがないため、C/C++などでは代入結合演算をさらに特定の変数や定数に代入するような記法や発想があることに単純に驚かされました。 最後にご指摘頂いた、Swiftの演算のカスタマイズもPHPしかしらない私にとってはどのくらい他の言語と比較して特殊なのかという感覚はまだ分かりませんが(もしかしたらコンパイル言語ではよくある仕様?)これから学ぶ上でその感覚は念頭に置きたいと考えます。 *返信コメントがいつも見づらくて申し訳なく、このコメント欄でも##MarkDwon記法が使えればな~と思うのですが、お詫びいたします。
eytyet

2020/08/18 05:47

丁寧にご返答をありがとうございます。また、読み込んで頂いて、感謝申し上げます。 > func +()や func +=()の演算子の再定義はもともとの記号の意味とは全くことなる性質を定義するのかもしれない その可能性があります。私もそう疑って読みます。 しかし、実際そうだったら、それは分かりにくくメンテナンスしにくいコードですよね。 わざわざ演算子オーバーロードを定義するなら、見た目の通りに動くように設計する事が大事だと思います。見ただけで期待する動作が明確で、実際そのように動くから有用なのであって、そうでないならプログラマに混乱を与え、ビルド時間を長くするだけになってしまいます。 実は、このClockの例は、a + 1が、1分進めるのか1時間進めるのか、はたまた1秒進めるのかが分からない、という課題を持っています。a.advance(min: 1)のままなら、誤解が生じず分かりやすいと、私は思います。その意味で、+演算子を定義する事に利点があるかというと、ちょっと疑問です。 > case1の加算演算子は通常の加算演算子と何が違うためここでfunc +(){}をしているのだろう 違う、または変えたいから定義しているのではなくて、存在しないから定義しています。case1の定義をしないと、a + 1は単にエラーになります。ClockとIntの加算は定義されていないので。SwiftはPHPのような暗黙の型変換を一切行わず、型が違えばエラーにします。 >「普通の加算演算子+では例えば Int型 + Int型でないと Int型の結果をreturnしないので、Clock型 + Int型 でreturn Clock型という特殊な演算をするために、加算演算のオリジナルの左辺と右辺の加えるという性質(つまり左辺と右辺を加算した結果をreturnするという性質)を利用しつつ、異なる方でも左辺と右辺で演算できるような特殊な+演算を定義した」 私が正確にご質問の点を理解できているか自信がありませんが、以下で回答になっているでしょうか。 この設計者は「ソースコードに'a.advance(min: 1)'と書くよりも、'a = a + 1'または'a += 1'と書きたかった」という事だと思います。その方が(読みやすいとか書きやすいとか分かりやすいとか)利点があると考えたのでしょう。そこで+関数や+=関数を定義した、という想定だと思います。 Swiftではプロトコルに適合させるためにプロトコルが要求する演算子を定義するということがよくあります。例えばEquatableとか。これはプロトコルが定義を要求してくるので、それに従う必要があります。しかし、ここではIntなどの+演算子の定義と合わせる必要はありません。自由に定義できます。自由ですが、一番便利な定義にしたら結局普通の+演算子と同じ形に落ち着くでしょうし、分かりやすさのためにも同じにするのが良いのでそうなっているのでしょう。 本では単に言語機能の紹介のためにこのようなサンプルにしたのかなとも思いますが、正しい設計といえるかというと、そうでもない気がします。その意味で、pegyさんが「なぜこんな設計なのか」と疑問に思い、困ってしまうのは正統な事のようにも思います。 サンプルのようなClockとIntの足し算ではなくて、Clock同士の足し算だったら、もっと納得がいくのではないでしょうか。試しにClock同士の+や+=を定義してみると、引数や戻り値について、納得できるのではないかと思います。 こちらも長いコメントですいません。
pegy

2020/08/18 12:54

コメントありがとうございます。ここまで来てご指摘で飛んだ勘違いに気づかされました。 今回の様な演算子のオーバーロードは元々の+や+=の意味が一部利用されていると解釈してしまっていました。 ここでは+演算子のオーバーロードは必ずreturnを必要とする、一方で+=演算子は必要とせず左辺が変化する物という先入観で読み解いていました。以下のコードを試せば結果は明明白白でした。 func +=(lhs:Int, rhs:Int) ->Int { return lhs + rhs } var x:Int = 3, y:Int = 5; print(3 += 5); // 8 全くそんなことはないですね、+=か加算演算子として転生されました・・・ただ、ご指摘にもある様にあえてこんなオーバーロードする場面は普通は考えづらいですし、可読性を害するだけかと思います。 さて、ここで一つの当初の疑問の一つの答えに行き着いたのですが例えば func +=(){}を定義した → +=という演算子を使用しているため → returnを取ることはない という論理は全く正しくないということがわかりました。 この様な演算子のオーバーロードにおいては、引数の取り方や、演算子とオペランドを用いて、どの様な意味を持たせたにのかという視点でreturnを取ることや取らないことが決まってくるという視点を得ることができました。 概ね理解に近づいたことについて、皆様に感謝をいたします。 また、参照渡しの動作については、自分の中で課題が残りましたが、邁進して解決していきたいと思います! 改めまして御礼申し上げます!
eytyet

2020/08/18 13:27

疑問が解消したならなによりです。 演算子の優先順位は言語定義の影響をうけてしまいますが、それ以外はどんな処理でも自由に定義可能です。 operatorというキーワードを使うと、自分で演算子を作ることもできます。全角の×に掛け算を定義したら、1×2とコードに書けるようになったりします。 そういえばご質問にお答えしていませんでした。演算子オーバーロードは、最近の言語にはよく実装されている機能だと思います。 なお、Swiftは正式には演算子オーバーロードとは呼んでいなくて、オペレーターメソッドと呼んでいます。オーバーロードに限定される機能ではなく、より柔軟なためだと思っています。
pegy

2020/08/18 14:30

了解いたしました、ありがとうございます!
guest

0

返り値の型もreturnもせずにtac.toString()で返り値の23:00が得られるのでしょうか?

返り値無しなので、返り値使っていませんよね?
+=の返り値を使うというのは、var xxx = (tac += 125)のようにすると言うことですよ。そうすると返り値無しなのでエラーになると思います。

何故参照渡しのinoutがついているのでしょうか?

左辺を更新するからでは?

投稿2020/08/16 05:49

otn

総合スコア84557

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pegy

2020/08/16 09:29

コメントありがとうございます。 んんーどうしても頭が混乱してしまうのですが +=(左辺:型, 右辺:型)で2項演算子としての+=を本来の代入結合演算子とは違う意味で定義するということは(仮に左辺:型をAとして右辺:型をBとした場合) A += B の演算をした結果何を返しますか?ということを定義しているのかと考えておりました。 これが+=ではなくswiftで認められる例えば.&.の様な演算子の定義でもいいのですが、 A .&. B の結果を何を返しますか?という定義と等価であると思っていました。 従って、当然その結果を定義の中で返り値としてreturnしてあげるcase1の様な形になるのかと考え、 return 右辺.advance(引数:右辺)の結果が独自に定義した、+=の演算で与えられる返り値と解釈しています。 var xxx = (tac += 125)が向こうであるとすると、この+=には本来の代入結合演算子の意味も残っているということになろうかと思いますが、ともすれば演算子の再定義について、私に誤認があるということな李ますでしょうか?
otn

2020/08/16 11:30

代入演算子についての根本的な勘違いがあるようです。代入演算子・代入結合演算子は値を返しません。 おそらく、値が左辺に代入されることと、演算子が値を返すことを混同しているのでしょう。
pegy

2020/08/16 13:37

コメントありがとうございます。なるほど、当たり前のことかもしれませんが、確かにあまり整理できていなかった様な気がいたします。 return a + b は加算演算子として、その結果をreturnによって返しますが a = a +b や a += b は値を返すのではなく、左辺に代入することだけを意味するとうことかと思います。 従って return a += b なんていう記述は適切ではないということかと思います。 翻って、上記の+=は演算子の再定義であるため代入の概念がもはやないと思っていたのですが、ここでreturnをしていないということは再定義というよりも、本来の結合代入演算子の性格を引き継いでいるものと解釈をいたしました。確かに static func .&.(lhs: inout Clock, rhs: Int){ //case2 lhs.advance(min: rhs) } では実行できなかったため、+=結合代入演算子の性格を前提にしていることがわかりました。これはこれで演算子の再定義が少し分からなくなってきましたが、もう少し復習してみます。
otn

2020/08/16 23:30

> 上記の+=は演算子の再定義であるため代入の概念がもはやないと思っていたのですが、 数値型の+=演算子と似た機能として定義しています。
TsukubaDepot

2020/08/17 11:23

> +=の返り値を使うというのは、var xxx = (tac += 125)のようにすると言うことですよ。そうすると返り値無しなのでエラーになると思います。 ちなみに、Swift では上記の構文はエラーとならず、xxx には () (からのタプル、Swift では Void)が代入されます。Swift の場合、返り値がない、つまり暗黙的を含め Void を返すと定義された関数の戻り値は全て () となります。 https://developer.apple.com/documentation/swift/void ちなみに、エラーではありませんが、Warning として[Variable 'xxx' inferred to have type '()', which may be unexpected] という警告は出ます。もっとも、警告にしたがって修正したとしても、 var xxx: () = (tac += 125) と、明示的に型名を書かせるだけですが。
pegy

2020/08/18 03:17

TsukubaDepot様 コメントありがとうございます。 確かに実際のコードを動かしてみて、Syntax Errorにはならない点を確認することができました。Emptyのtuple型が返ってくるのですね。一つ勉強になりました。ありがとうございます!
guest

0

① case1の+演算子の定義については -> Clock型でreturnで返り値を定めているのに、何故、case2においては、返り値の型もreturnもせずにtac.toString()で返り値の23:00が得られるのでしょうか?両者の違いがどうしてもわかりません。

これは(2)と密接に関係するのですが、case2lhs は参照渡しとなっているためです。

+=演算子の左側の型(この場合 Clock) のインスタンスに対して直接 advance(min) を実行しているため、値が直接書き換わっています。

一方、case1 の場合にはインスタンスのコピーを返しています。

②上記の①と関連するかもしれませんが、case2については、仮引数のlhs(左辺)について、何故参照渡しのinoutがついているのでしょうか?参照渡しの際に利用して、実際に参照する時は&を付けることは学んでいるのですが、その様な利用はされていませんし、case1との違いについても理解することができません。

演算子の左側にあるインスタンスを直接書き換えたいからと思われます。

投稿2020/08/16 05:42

編集2020/08/16 11:12
TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pegy

2020/08/16 09:16

コメントありがとうございます。 目下、頭を抱えながら考え中なのですが、一点確認として "これは(2)と密接に関係するのですが、case2 の rhs は参照渡しとなっているためです。" とおっしゃっていただいていますが static func +=(lhs: inout Clock, rhs: Int) でinoutはlhsの左辺についているのでlhsが参照渡しではないのでしょうか?
TsukubaDepot

2020/08/16 11:12

すみません、lhs の間違いです。
pegy

2020/08/16 13:39

コメントありがとうございます。また、全く私が知らない概念が存在すると、少しどきっとしたので確認ができてよかったです。初心者ということで、想像もしないこともよく出てくるので、確認させていただき失礼いたしました。
guest

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
YufanLou

総合スコア463

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pegy

2020/08/17 14:05

YufanLou様 丁寧に噛み砕いてコード付きでわかりやすくご教示いただき本当にありがとうございます。何度も何度も重ねて読み直しました。殆ど教えていただいた内容を理解することができたのですが、どうしても最後だけ少し躓いてしまいます。 前者について少し言い換えるとすれば ① 演算子に値の意味を付したい ② 前(左辺+右辺)後に値の対応関係がある e.g. 1 @ 1 = 2の様な関係 ③ 後がreturnによる戻り値になる。 後者についていえば ① 演算子に操作の意味を付したい ② 前(左辺+=右辺)後に演算子の対応関係がある e.g. 1 @ 1 左辺と右辺は等しい様な関係 ③ 戻り値はいらない ④ 参照しかあり得ない? 実は最後のここの④だけが腹落ちしません。おそらくここでいう「参照」というのが具体的にどの様なことを指しているのか自分の言葉でうまく説明できない点に問題があると認識しています。何が何を参照していると説明するのが正しいのでしょうか? ------------------------------------------------------------------------------------------ func +=(Clock(hour: 20, min: 55), 125) { Clock(hour: 20, min: 55) = Clock(hour: 23, min: 00) } が右辺を左辺にに代入するのではなく、ここでに引数や左辺が「参照」であるというのは具体的にどういう意味(動作)を指しているのでしょうか? もう少しで理解ができそうという事情で申し訳ございませんが、お付き合いいただけるととてもありがたいです。 よろしくお願い申し上げます。
YufanLou

2020/08/17 15:10

自分も完全に把握していないせいで、説明の具体さが欠けてしまい、ごめんなさい。もうちょっと詳しく説明してみます。 func +=(Clock(hour: 20, min: 55), 125) { Clock(hour: 20, min: 55) = Clock(hour: 23, min: 00) } このコードは tac の意味が値の意味であると仮説して、tac をその値で書き換えて得たものです。書き換えてみたら、意味がわからなくなっています。なぜかというと、= の左側には値がありえないので、 = 演算子の意味と矛盾してきたからです。矛盾に導かれたから、「tac の意味は値の意味である」という仮説を否定することができます。つまり、tac の意味は値の意味ではありません。ですが、意味を付けたいでしょう?値の意味を付けれなければ、余りの操作の意味しか付けれません。ですから、tac の意味は操作の意味であり、「参照」であります。 以上、背理法で証明をしてみました。 一歩戻しのコードを見てみましょう func +=(tac, 125) { tac = Clock(hour: 23, min: 00) } ここの tac は値であると矛盾にいくから、参照であるしかあり得ないということです。何が何を参照しているかと、tac がこの演算子の外のどこかの Clock 型の変数を参照しています。
pegy

2020/08/17 22:04

コメントありがとうございます。いえ、一重に私の理解不足によるものです、 結局徹夜で考えながら、自身が考えすぎなのか余計頭が混乱をしてしまいました(反省)。好きなことにこの様な時間を費やすことは私自身は楽しいゆえ、問題はないのですが、お付き合いただいていることには、誠に申し訳なく思っております。 演算子の値や操作の意味を持たせるというところから、途中で変数のtacに値を持たせるというどういうことなのかなど、意味を持たせる対象はなんであったのか?!などいろいろ思考を巡らせ、細かいところはあるのですがおそらく最終的に一番、「参照」ということがやはり理解ができないことに原因があると整理いたしました。 論点がずれてしまっていたら申し訳ございませんが、例えば以下の様なfuncがあると思います。 ------------------------------------- func tt(a:inout Int, b:Int) { a = a * b } var x = 3, y = 5 tt(a: &x, b: y) print(x) ------------------------------------- まさに、参照渡しの例で、元の変数を変化させるために引数にinoutを利用していて、特にreturnで戻り値を得るものではないと思います。 ふと、こんな単純なものでもなぜ、引数aを参照渡しにしなければいけないのかということが理解できていない気がします。 もちろん構文上、仮引数は実際には自動的にletにより宣言されているため、letで宣言されているaを変化させることは許されないことは理解できるのですが、 (動作) ①ここでいう参照とは inout aはメモリ上、実際に格納されている値ではなくその場所(メモリ上の番地)を指している ②従って、let で宣言されるinout aの番地自体は変更することができないが、参照渡しにより、a = a * bにおけるaは参照先の具体的な値を指していて、これを変更することは許容される ③ tt(a: &x, b: y)が実行されるときには、(例えば無理やり関数に置き換えて考えると) func tt(let x: inout Int, y:Int) { //ここのxの値ではなく実際のxの格納場所を指していて x = x * y //ここのxは実際のxの値 } として代入され結果var xの変数の値は3から15に変更される? (理由) この様な参照にしなくてはいけない理由はfunctionが実行されるときの{x = x * y}ブロックのかではが元の変数のアクセスできないスコープの問題? という具合に参照渡しがの動作やまたなぜ参照渡しにしなければこの様な変数の変更が実装できないのかが正確には説明できません・・・ これをinoutで参照となっている、tacに置き換えれば、この問題の理解に少し近づけるかもと考えたのですが、理解に誤りがあるのでしょうか? そもそも本件とは関連がなく大きく論点がそれている場合には、ご放念いただければと思います。 よろしくお願い申し上げます。
eytyet

2020/08/18 06:29

Swiftでは、関数の中から引数はletとして見えます。つまり定数で、書き換え不可能です。書き換えようとするとビルドエラーになります。 しかし、渡された引数そのものを書き換えたい、というニーズのために、inoutが用意されています。inoutをつけた引数は、関数から戻ったときに元の変数に上書きされ、あたかも元の変数を関数内部から操作したかのように振る舞います。 inoutがあるくらいなので、技術的にできないのではありませんが、デフォルトではあえてしていません。そのように、呼び出し元と呼び出し先を分離するほうがパフォーマンス上、セキュリティ上、コード品質、機能性などの観点で有利だからそういう設計にしているのかなと思います。
pegy

2020/08/18 11:58

こちらでもコメントをいただきありがとうございます。具体的に参照渡し(inout)で内部的にどの様に動作するのかはまだ、理解できていない部分ではございますが、 引数自体の中身を書き換えたい → 参照渡しにしないといけない → 引数にinoutをつける必要がある → で一旦受け止めようと思います。 いつが、実際の経験を積んだときにどの様な動作なのか理解できることを楽しみにしています。 ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問