

swiftを勉強しているのですが，クラスと構造体についてわからない点があります．

関数aが関数bを呼び出す時に，関数bにクラスのインスタンスを引数として渡した場合，関数b内でクラスのプロパティの値を変更すると，関数a内のクラスのプロパティも変更され，  
関数aが関数bを呼び出す時に，関数bに構造体のインスタンスを引数として渡した場合，関数b内で構造体のプロパティの値を変更しても，関数a内の構造体のプロパティは変更されないそうなのですが，その具体的なプログラムがどんなものか分からないので，具体例を示していただけないでしょうか．

できればプログラミング初心者でも理解できるようなプログラムでお願いします．

swift 4.x です．


CC BY-SA 4.0
2018-06-19T15:03:07.887
44896
2018-06-20T12:45:35.737
2018-06-19T16:16:05.150
19110
question
swift swift4
swiftのクラスと構造体で，プロパティを変更したときの挙動が違う例


質問者さんは`let`と`var`で変数を宣言する事の違いは理解出来るでしょうか？  
`let`でを宣言すると、`let`で宣言した変数は定数となり、変更が不可能（コンパイルエラー）になります。  
構造体についてもこれは同様で、構造体のメンバーを`var`で宣言しても、構造体型の変数を`let`で宣言すると、構造体のメンバーは初期化時にしか変更出来なくなります。

ここまでが前提で、以下がサンプルになります。  
これをプレイグラウンドに貼り付けると、３箇所コンパイルエラーが発生します。  
エラーになる箇所には、何故エラーになるのかコメントしてありますのでそれを読んでみてください。

[code]

    struct BookStruct {
        var title:String
        var author:String
    }// end define struct BookStruct
    
    class BookClass {
        var title:String
        var author:String
    
        init(title:String, author:String) {
            self.title = title
            self.author = author
        }// end init
    }// end define class BookClass
    
    func createBook(titlle:String, author:String) {
        var book1 = BookStruct(title: titlle, author: author)
        book1.title = "def" // OK
        let book1b = BookStruct(title: titlle, author: author)
        book1b.title = "def" // letで宣言したのに内容を変更しようとしているため、NG
        modifyTitle(book: book1, newTitle: "abc")    // コメントbookStruct1のmofifyTitleが呼ばれる
        modifyTitle(book: &book1, newTitle: "abc")    // コメントbookStruct2のmodifyTitleが呼ばれる
    
        let book2 = BookClass(title: titlle, author: author)
        book2.title = "def" // OK
        modifyTitle(book: book2, newTitle: "def")    // コメントbookClassのmodifyTitleが呼ばれる
    
    }
    
    // bookStruct 1
    func modifyTitle(book: BookStruct, newTitle:String) {
        book.title = newTitle // bookは、letでコピーされた値が渡されるので、変更しようとすると、NG
    }
    
    // bookStruct 2
    func modifyTitle(book: inout BookStruct, newTitle:String) {
        book.title = newTitle // bookを受け取るときに inout宣言で値の参照を受け取っているので、 OK
    }
    
    // bookClass
    func modifyTitle(book: BookClass, newTitle:String) {
        book.title = newTitle // book自体は値渡しだが、クラスのインスタンスなので、プロパティの変更は OK
        book =  BookClass(title: "cde", author: "aaa") // book自体を書き替えようとするのは NG
    }
    
[/code]


CC BY-SA 4.0
2018-06-19T21:43:53.500
44900
2018-06-19T21:43:53.500
14745
44896
answer


ご質問にある、

> 関数aが関数bを呼び出す時に，関数bにクラスのインスタンスを引数として渡した場合

の具体例は以下になります。

[code]

    class User {
        var name = "taro"
    }
    
    func 関数a() {
        var user = User()
        関数b(user)
        print(user.name)    // => "hanako"
    }
    
    func 関数b(_ user: User) {
        user.name = "hanako"
    }
    
    関数a()
    
[/code]

また、

> 関数bに構造体のインスタンスを引数として渡した場合

の具体例は、上記のUserを`class`から`struct`へ変更したものになりますが、そうすると関数bでコンパイルエラーが発生します。

（引数で渡ってきた値はletで定義したものとして扱われ、構造体はletで定義したインスタンスのプロパティは変更できないため）

ですのでご質問にある

> 関数b内で構造体のプロパティの値を変更しても，関数a内の構造体のプロパティは変更されない

は、正しくは

> 関数b内で構造体のプロパティの値は直接には変更できない（デフォルトでは）

かと思います。


CC BY-SA 4.0
2018-06-20T07:34:01.847
44910
2018-06-20T07:34:01.847
19759
44896
answer


すでにお二人の方の回答がついているのですが、どちらも「値型の変数を`let`で宣言すると、そのメンバーは変更できない」と言うことを強調する内容で、値型のコピーのプロパティを変更しても、元のプロパティは変更されないが、参照型の(参照の)コピーのプロパティを変更すると元のプロパティも変更されると言う例を。

まずは、シンプルに構造体とクラスを定義しておきます。

[code]

    struct MyStruct {
        var prop: String = "initial"
    }
    
    class MyClass {
        var prop: String = "initial"
    }
    
[/code]

最初は「関数aが関数bを呼び出す時に，関数bに構造体のインスタンスを引数として渡した場合，関数b内で構造体の(コピーの)プロパティの値を変更しても，関数a内の構造体のプロパティは変更されない」例をどうぞ。

※これらのコードをPlaygroundなどで実行させるといっぱい警告が出ますが、今は実験のためなので無視してください。

[code]

    func a1() {
        var structInstance = MyStruct()
        print(structInstance.prop) //-> initial
        b1(structInstance)
        print(structInstance.prop) //-> initial
    }
    
    func b1(_ argInstance: MyStruct) {
        var varInstance = argInstance //そのままではプロパティが変更できないので`var`変数に代入
        varInstance.prop = "changed"
    }
    
[/code]

この状態で`a1()`の呼び出しを行うと、コメントにあるように **`initial`**
が2回表示されます。関数`b1`の中で確実に`varInstance`のプロパティ`prop`の値を変更しているのですが、それはコピー元の`structInstance`には反映されていません。

`var varInstance =
argInstance`のような行があるせいのように見えるかもしれませんが、クラスの場合、全く同じように書いてもそんな動作にはなりません。「関数bにクラスのインスタンスを引数として渡した場合，関数b内でクラスのプロパティの値を変更すると，関数a内のクラスのプロパティも変更され」る場合を見てみましょう。

[code]

    func a2() {
        var classInstance = MyClass()
        print(classInstance.prop) //-> initial
        b2(classInstance)
        print(classInstance.prop) //-> changed
    }
    
    func b2(_ argInstance: MyClass) {
        var varInstance = argInstance //そのままでもプロパティは変更できるが、構造体の時と全く同じコードにしておく
        varInstance.prop = "changed"
    }
    
[/code]

この状態で`a2()`を実行すると、`varInstance`のプロパティ`prop`を書き換えると、確かに`classInstance`のプロパティ`prop`も書き換わっています。

* * *

実際に動かしてみれば違いは明白だと思うのですが、もう少し解説をしておきます。

構造体のような値型の変数の場合、(概念的には)それぞれの変数が値のコピーを持ちます。`b1()`内の`varInstance.prop =
"changed"`を実行する直前には、こんな状態になっています。

[code]

    a1のstructInstance  +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"initial"         |
                        +----+------------------+
    
    b1のargInstance     +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"initial"         |
                        +----+------------------+
    
    b1のvarInstance     +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"initial"         |
                        +----+------------------+
    
[/code]

ここで`varInstance.prop = "changed"`を実行するともうお分かりだと思いますが、このようになるわけです。

[code]

    a1のstructInstance  +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"initial"         |
                        +----+------------------+
    
    b1のargInstance     +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"initial"         |
                        +----+------------------+
    
    b1のvarInstance     +-----------------------+
                        |MyStruct               |
                        +----+------------------+
                        |prop|"changed"         |
                        +----+------------------+
    
[/code]

* * *

これに対して参照型であるクラスのインスタンスの場合、その本体は変数とは別の場所にあり、本体への「参照」だけが変数の中に入っています。`b2()`内で`varInstance.prop
= "changed"`が実行される前はこんな感じですね。

[code]

                                          本体は別の場所(ヒープという)にありコピーされない
    a2のclassInstance   +------+          +-----------------------+
                        |  []--------===> |MyClass                |           
                        +------+    /  /  +----+------------------+
                                   /  /   |prop|"initial"         |
                                  /  /    +----+------------------+
    b2のargInstance     +------+ /  /
                        |  []---/  / 参照がコピーされたのでみんな同じ場所を指す
                        +------+  /
                                 /
    b2のvarInstance     +------+ /
                        |  []---/
                        +------+
    
[/code]

ここで`varInstance.prop =
"changed"`が実行されると、参照型の場合、プロパティの変更は参照をたどって本体のプロパティを変更しますから、こうなるわけです。

[code]

    a2のclassInstance   +------+          +-----------------------+
                        |  []--------===> |MyClass                |           
                        +------+    /  /  +----+------------------+
                                   /  /   |prop|"changed"         |
                                  /  /    +----+------------------+
    b2のargInstance     +------+ /  /
                        |  []---/  / 参照がコピーされたのでみんな同じ場所を指す
                        +------+  /  つまり、みんな同じく変更後の本体を指したまま
                                 /
    b2のvarInstance     +------+ /
                        |  []---/
                        +------+
    
[/code]

* * *

上記の「値型の変数の場合、(概念的には)それぞれの変数が値のコピーを持ちます。」と言うのは、概念的なもので、実際には(大きな構造体をコピーするのは大変なので)最適化により出来るだけコピーが起こらないようにしたりもするのですが、意味的には「それぞれの変数が値のコピーを持ちます」と言うのと同じ結果が出るようにします。


CC BY-SA 4.0
2018-06-20T12:45:35.737
44923
2018-06-20T12:45:35.737
13972
44896
answer