tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトル構造体型の変数について
記事No6259
投稿日: 2007/09/09(Sun) 16:43
投稿者
プログラミングを独学趣味で始めて1ヶ月目の初心者です。
VB2005以外の知識はありません。パソコンについても詳しくありません。
本をよんでいてよく理解できない点がありましたので詳しい方教えてください。
クラスと構造体のことについてです。

Class Person
    Dim FirstName As string
    Dim LastName As string

            Function CompName() As String
        Retrun FirstName & LastName
              End Function
End Class

Structure PersonStruct
    Dim FirstName As string
    Dim LastName As string

            Function CompName() As String
        Retrun FirstName & LastName
              End Function
End Structure

という二つのクラスと構造体が比較のためにあります。
「VBでは構造体もクラスと同じようにメソッドを持ち、その違いは値型と参照型である」とありました。
クラスの場合は
    Dim pClsObj  As  New Person
とすることでPersonクラス型の変数pClsObjを「スタック」に作成。
そして変数pClsObjには「ヒープ」に新しく作成されたインスタンスを「参照」するためのデータが格納される。
次に
    Dim pClsObj2 As Person
と新しくインスタンスを「ヒープ」上に作成せずに、2個目のクラス型変数を宣言した場合には、pClsObjとpClsObj2は同じインスタンスを参照することになる。
よって値を変えたりすると互いに影響がある。(これがポインタということでしょうか・・)
"さらに、ヒープ上にあるインスタンスには「スタティック」にあるCompNameメソッドとの関連付けがなされるようなデータも付加されている"
ということなんだとおもいます(ちがっていたら指摘してください)

構造体での使われ方が納得いかないのです。
"構造体は「値型」である。値型の変数は「参照」を格納するのではなく「値」そのものを保持する」
のようなことが書いてありました。
構造体型の変数を利用する場合も
    Dim pStrucObj  As  (New) PersonStruct
のように構造体PersonStrcut型の変数を宣言することでこの構造体のメンバ変数を利用するということだと思います。
「New」というキーワードはつけてもつけなくても同じであり「冗長」であるとありました。
そこで判らなくなりました。
クラスであればNewを宣言すれば「ヒープ上にインスタンス」が作成されて、そのインスタンスには関連するメソッドなどとの関連付けのデータも付加される。
ということだと思います。
しかし「構造体型の変数pStrucObjは値型なので「参照ポインタ」ではなく値そのものが格納される」ということならば・・・構造体PersonStruct型の変数であるpStrucObj

にはいったい何が格納されているのですか?構造体の値とは何でしょうか・・。
推測ですがもし仮に構造体変数のpStrucObjに、この構造体のメンバ変数である「FirstName」や「LastName」の値そのものが格納されているのであれば、
この構造体のメンバ変数と関連するメソッドGompName()との関連付けはどうなっているのでしょうか・・・。
クラスならヒープ上にインスタンスとメソッドとの関連付けをするようなデータが付加されているように、スタックにある構造体型の変数にもそういう関連付けのデータが付加されるのでしょうか・・
非常に長くなりあいまいな点が多すぎるのでわかりにくいかもしれないですが、詳しい方がいたら「意味を汲み取って」いただいて説明をお願いします。

[ツリー表示へ]
タイトルRe: 構造体型の変数について
記事No6260
投稿日: 2007/09/10(Mon) 10:31
投稿者魔界の仮面弁士
本題に入る前に、まずは細かい話から。

> Dim FirstName As string
アクセス修飾子が省略されて、単に Dim とだけ書かれたフィールド変数というのは、
それが Class 内のものであれば「Private FirstName As String」の意味となり、
それが Structure 内のものであれば、「Public FirstName As String」の意味となります。

このような差異があるため、フィールド変数を Dim のみで宣言するのは、あまり感心しません。
混乱を避けるため、常に Private / Public 等のアクセス修飾子を付けるようにしましょう。


> Retrun FirstName & LastName
Retrun ではなく、Return (リターン)です。


さて、ここからが本題。

> Dim pClsObj2 As Person
> と新しくインスタンスを「ヒープ」上に作成せずに、2個目のクラス型変数を宣言した場合には、
> pClsObjとpClsObj2は同じインスタンスを参照することになる。
> よって値を変えたりすると互いに影響がある。(これがポインタということでしょうか・・)
pClsObj2 ではなく、pClsObj2 のことだと思いますが、そもそもこの時点では、
pClsObj2 には何のインスタンスも割り当てられていません(Nothing 状態)ので、
同一インスタンスを参照することになっているわけではありませんし、
ましてや、値の変更が互いに影響を及ぼしあうようなこともありません。

もし、それぞれの変数に、同じインスタンスを参照させたいのであれば、
 pClsObj2 = pClsObj
のようにして、同一インスタンスへの参照を持たせるようにする必要があります。


少し定義を変えて説明すると、
  Public Class Class1
     Public Value As Integer
  End Class

  Public Structure Structure1
     Public Value As Integer
  End Structure
という型定義があったとします。この時、
  Dim val1, val2 As Structure1
  val1.Value = 987
  val2 = val1         '値型なので、オブジェクトそのものがコピーされて渡される。
  MsgBox(val1.Value)  'この時点では「987」という値である。
  val2.Value = 654    'コピー元の内容を書き換えても、コピー先には影響を与えない。
  MsgBox(val1.Value)  '結果として、「987」のままである。
'-----------------------
  Dim ref1, ref2 As Class1
  ref1 = New Class1() 'インスタンスを生成
  ref1.Value = 123
  ref2 = ref1         '参照型なので、オブジェクトへの参照がコピーされて渡される。
  MsgBox(ref1.Value)  'この時点では「123」という値である。
  ref2.Value = 456    '同一インスタンスを参照しているので、ref1 にも影響がある。
  MsgBox(ref1.Value)  '結果として、「456」という値に書き換わっている。
という動作になるのが、『値型』と『参照型』の最大の違いとなります。


> "さらに、ヒープ上にあるインスタンスには「スタティック」にあるCompNameメソッドとの
> 関連付けがなされるようなデータも付加されている"
この場合の「スタティック」とは、何を意味した言葉でしょうか?
それによって、話の流れが変わってきます。

もし、Visual Basic の「Static キーワード」を意味しているのであれば、それは
「Dim FirstName As String」ではなく『Static FirstName As String』のように
宣言された変数を意味します。しかし、実際にはそうなっていませんよね。

また、Java/C#/J#/JScript/MSIL などでいうとこの「static (静的)メソッド」の意味ならば、
「Dim FirstName As String」ではなく、『Shared Function CompName() As String』のように
宣言された、「Shared (共有)メソッド」のことを指す事になります。
しかし、こちらもそのような宣言にはなっていませんね。


> "構造体は「値型」である。値型の変数は「参照」を格納するのではなく「値」そのものを保持する
その認識は正しいです。


> 「New」というキーワードはつけてもつけなくても同じであり「冗長」であるとありました。
そのとおり、この場合は New を付けなくても同じ結果になります。その意味では冗長です。
ただし、「引数付きコンストラクタ」を受ける場合などにおいては、それが構造体であっても、
New が意味を持ってくることになります。


> しかし「構造体型の変数pStrucObjは値型なので「参照ポインタ」ではなく
> 値そのものが格納される」ということならば・・・構造体PersonStruct型の変数である
> pStrucObj にはいったい何が格納されているのですか?構造体の値とは何でしょうか・・。
何を聞かれているのかが読み取れなかったので、説明にズレがあるかも知れませんが御容赦を。

値型のオブジェクトは、ヒープ上ではなく、アプリケーションのデータセグメントまたは
スタック上に割り当てられます。


> 推測ですがもし仮に構造体変数のpStrucObjに、この構造体のメンバ変数である
> 「FirstName」や「LastName」の値そのものが格納されているのであれば、
いいえ、そうではありません。

FirstName や LastName が、String 型という「参照型(System.String クラス)」であることに
注意してください。構造体内の String 型メンバは、ヒープ上の特定のポインタとして
格納されるだけであり、構造体そのものの中には格納されません。

[ツリー表示へ]
タイトルありがとうございました!
記事No6267
投稿日: 2007/09/10(Mon) 22:01
投稿者
丁寧に説明していただいてありがとうございました。
とてもよくわかりました。

胸につかえていたことがなくなりました。
ありがとうございました〜〜

[ツリー表示へ]
タイトルまた不明な点が出てきてしまいました・・。
記事No6269
投稿日: 2007/09/10(Mon) 23:40
投稿者
すいません、再度同じ事になるかもしれませんがお聞きします。

Public Structure PersonStruct
  Dim FirstName As String
   略
End Structure

という構造体があります。
Dim aPersonStruct As New PersonStruct 'Newはオプション
aPersonStruct.FirstName="John"

Dim aPersonStruct2 As PersonStruct
aPersonStruct2=aPersonStruct   '構造体は値型なので新しい変数には元の構造体のコピーが設定される
aPersonStruct2.FirstName ="Ann"
`変更は元の構造体に反映されない
Console.WriteLine(aPersonStruct.FirstName)  `⇒John

抜粋しましたが重要な部分だけ本文から正確に抜き出して見ました。
構造体PersonStructのメンバ変数である「FirstName」は「構造体内の String 型メンバは、ヒープ上の特定のポインタとして格納されるだけである」とありました。
そこでまた判らなくなってしまいました。

aPersonStruct2=aPersonStruct
という部分で値がコピーされているとあります。
ということは構造体のメンバであるFirstNameの指し示すアドレスもコピーされるのではないのでしょうか・・・。
aPersonStruct2=aPersonStruct
とした段階でそのメンバ変数であるFirstNameが指し示すポインタもコピーされる。
ということですよね・・・。
ということはどちらも同じ参照先を指し示すわけですよね。
そして
aPersonStruct2.FirstName ="Ann"とすると
FirstNameの参照先は互いに”Ann"になるのではないのでしょうか・・?
それとも文字列の場合は何か特殊なことがあるのでしょうか・・・

追記です

もしかするとこうなのでしょうか?
aPersonStruct.FirstName  ’構造体内でFirstNameはString型(参照型)で定義

aPersonStruct2.FirstName
も「ヒープの"John"や"Ann”の値が格納されている参照先アドレスが格納されているのではなく、aPersonSTruct、aPersonStruct2が構造体変数(値型)である段階でメンバ変数が参照型であっても「値(AnnやJohn)」そのものが格納される」

そうすると強引かもしれないですがつじつまがあってくるですが。

aPersonStruct.FirstName ="john" ’FirstNameは参照アドレス画はいるのではなく、値である「John」そのものが入る
aPersonStruct2=aPersonStruct   ` メンバ変数のFistNameはString参照型だが「値」である”John"だけが渡される
aPersonStruct2.FirstName ="Ann"   'aPersonStruct2.FirstNameには参照アドレスがコピーされたわけではないので影響はない

魔界の仮面弁士様の例ですと
構造体のメンバ変数は「値型」のIntegerですよね・・。
これの場合はなんとなくわかるんです。
コピーが渡されることが・・・
構造体の変数である段階でそのメンバ変数が参照型でも値型でも、すべて「値」そのものが渡されるのでしょうか・・・

また長くなりましたがよろしくお願いします。
C言語などを勉強したほうがよいのでしょうか?

[ツリー表示へ]
タイトルRe: また不明な点が出てきてしまいました・・。
記事No6270
投稿日: 2007/09/11(Tue) 01:39
投稿者魔界の仮面弁士
コメント箇所は、「`」ではなく「'」で記述していただけるとありがたいです。


> Dim aPersonStruct2 As PersonStruct
> aPersonStruct2=aPersonStruct   '構造体は値型なので新しい変数には元の構造体のコピーが設定される
> aPersonStruct2.FirstName ="Ann"
> `変更は元の構造体に反映されない
> Console.WriteLine(aPersonStruct.FirstName)  `⇒John

「クラス」と「構造体」を比較する際に、String 型を使うと
話がややこしくなってしまいます。
# だからこそ先の例では、あえて Integer 型に置き換えたわけですが……。(^^;

String 型は参照型ですが、「生成後はインスタンスの内容を変更できない」仕様で
ある上に、幾らかの最適化が施されている関係上、今回のような実験目的には、
比較が行いにくいからです。


実際の所、String 型の解説には以下のように書かれていますよね。
>> この値は変更できません。
>> String オブジェクトは、作成時点以降に値を変更できないことから、
>>不変 (読み取り専用) と呼ばれます。
http://msdn2.microsoft.com/ja-jp/library/system.string%28VS.80%29.aspx


以上を踏まえた上で:


> 「構造体内の String 型メンバは、ヒープ上の特定のポインタとして格納されるだけである」とありました。
もしメンバが Integer 型であった場合は、構造体そのものの中に格納されていたのですが、
今回のメンバは String 型であったため、構造体とは別の領域にて管理されたわけです。


> aPersonStruct2=aPersonStruct
> という部分で値がコピーされているとあります。
> ということは構造体のメンバであるFirstNameの指し示すアドレスもコピーされるのではないのでしょうか・・・。
コピーされます。というよりは、アドレスのみがコピーされることになり、
アドレスが示す値(インスタンス)はコピーされないという事です。
# インスタンスが 2 つできるのではなく、1 つのインスタンスが
# 2 箇所から参照される事になるという意味です。

実際、
> aPersonStruct2=aPersonStruct
を実行後に下記を試してみると、いずれも「True」を示す事から、
FirstName フィールドが、同一インスタンスを参照している事を確認することができます。
 MsgBox(aPersonStruct.FirstName Is aPersonStruct2.FirstName)
 MsgBox(ReferenceEquals(aPersonStruct.FirstName, aPersonStruct2.FirstName))


> aPersonStruct2.FirstName ="Ann"とすると
> FirstNameの参照先は互いに”Ann"になるのではないのでしょうか・・?
なりません。先に書いたように、String 型は変更できないという点に注意してください。

上記は、『FirstName が参照しているインスタンスの値を書き変えている』のではなく、
『FirstName に、新たに "Ann" というインスタンスを代入しなおしている』のです。


> それとも文字列の場合は何か特殊なことがあるのでしょうか・・・

FirstName という String 型のメンバに対して "Ann" を代入するという行為を、
先の No.6260 で例示した Class1 という参照型で言い換えるなら、
 val1.Value = 123     'val1 が参照しているインスタンスの値を 123 に設定
 val1.Value = 654     'val1 が参照しているインスタンスの値を 654 に変更
という処理を行っているのではなく、
 val1 = New Class1()  '新たなインスタンスを生成し、それを参照させている
 val1 = New Class1()  '別のインスタンスを再生成し、それを参照させている
という動作を行っている事になります。


> C言語などを勉強したほうがよいのでしょうか?
多数の言語を知っておくと、各言語の特徴が見えてくるでしょうし、他の言語を
併せて学ぶ事は良い事だと思います。ですが、VB の勉強に C言語が必要という
わけではありません。実際、私は C を勉強していませんしね。

# C 言語のソースを読むぐらいはできますが、書く方はかなり自信無し。(^^;

[ツリー表示へ]
タイトルクラスの場合は・・
記事No6271
投稿日: 2007/09/11(Tue) 10:53
投稿者
String型は不変で新たにインスタンスが生成されるということはわかりました!

それは「クラス内部のString型メンバ変数」でも「構造体内部のString型メンバ変数」でもおなじように新たな文字が代入されれば「新たなインスタンス」が生成されるということではないのでしょうか。
とすればクラス、構造体のどちらのString型変数FirstNameも新たに生成されるインスタンスを指し示すわけではないのですか・・・


> aPersonStruct2.FirstName ="Ann"とすると
> FirstNameの参照先は互いに”Ann"になるのではないのでしょうか・・?
なりません。先に書いたように、String 型は変更できないという点に注意してください。

そもそもFirstNameには「住所」みたいなものが格納されているとおもっているのですが・・
それは間違いなのでしょうか。
FirstName=「ヒープ上にある「値」がある住所」

 構造体型変数@.FirstName=構造体型変数A.FirstName
としても
 クラス型変数@.FirstName=クラス型変数A.FirstName
としても「同じ住所が格納される」ことになるので、
新たに
 構造体変数@.FirstName="新文字"

 クラス型変数@.FirstName="新文字"
と「文字列」が代入されたとして、新たな文字列インスタンスが生まれたとしてもその新たな文字列の住所(アドレス)が代入される」わけではないのですか・・


もしかすると
> aPersonStruct2=aPersonStruct
という構造体型の代入と
aPersonStruct2.FirstName=aPersonStruct.FirstName
という構造体型のメンバ変数の代入
の後に
aPersonStruct2.FirstName="新文字"
とすることには多少の違いがあるのでしょうか?

[ツリー表示へ]
タイトルRe: クラスの場合は・・
記事No6274
投稿日: 2007/09/11(Tue) 14:14
投稿者魔界の仮面弁士
あら。いつの間にか質問に書き換わっている…? (^^;

> 新たな文字が代入されれば「新たなインスタンス」が生成されるということではないのでしょうか。
その通りです。

Visual Basic の言語仕様においては、String 型のインスタンスを生成するには、
通常のクラスのように New を使ってインスタンスを生成するのではなく、
"〜" というリテラルを使って生成できるようになっているということです。


> とすればクラス、構造体のどちらのString型変数FirstNameも
> 新たに生成されるインスタンスを指し示すわけではないのですか・・・

下記のコメントで分かりますでしょうか。

'====== 構造体の中にある String メンバの場合 ======

Dim aPersonStruct1, aPersonStruct2 As PersonStruct
aPersonStruct1 = New PersonStruct()
aPersonStruct1.FirstName = "xyz"

'値型なので、値のコピーが渡される
aPersonStruct2 = aPersonStruct1

'コピー先も、同じ文字列インスタンスを参照しているので、True になる。
Console.WriteLine(aPersonStruct2.FirstName Is aPersonStruct1.FirstName)

'コピー元にだけ、別の文字列インスタンスを参照させる。
aPersonStruct1.FirstName = "XYZ"

'コピー元とコピー先で異なるインスタンスを指すため、False になる。
Console.WriteLine(aPersonStruct2.FirstName Is aPersonStruct1.FirstName)


'====== クラスの中にある String メンバの場合 ======

Dim aPersonClass1, aPersonClass2 As Person
aPersonClass1 = New Person()
aPersonClass1.FirstName = "abc"

'参照型なので、インスタンスはコピーされず、参照情報のみがコピーされる
aPersonClass2 = aPersonClass1

'両者は、同一の Person インスタンスを指すため、True になる。
Console.WriteLine(aPersonClass2 Is aPersonClass1)

'同じインスタンスなのだから、その中のメンバも当然同一であり、True になる。
Console.WriteLine(aPersonClass2.FirstName Is aPersonClass1.FirstName)

'その同一インスタンスの FirstName に、別の文字列インスタンスを参照させる。
aPersonClass1.FirstName = "ABC"

'両者が同一のインスタンスを指している事実は変わらないので、引き続き True のまま。
Console.WriteLine(aPersonClass2 Is aPersonClass1)

'同じインスタンスなのだから、その中のメンバに別のインスタンスを参照させた場合には、
'両者のメンバから見えるインスタンスも等しく書き換わるため、True となる。
Console.WriteLine(aPersonClass2.FirstName Is aPersonClass1.FirstName)

'=================================================


> そもそもFirstNameには「住所」みたいなものが格納されているとおもっているのですが・・
では、例え話にしてみましょうか。


文字列データの実態を『家』だと仮定しましょう。
その場合、String 変数とは、『住所が書れた紙』を意味します。
クラス内/構造体内の String 変数とは、『その紙が机の引出しに保存された状態』です。

String 型が不変であるという事は
 その紙に書かれた住所は消しゴムでは消せず、固定的であるということです。
Sring 変数に別の文字列を代入できるという事は、
 引き出し内の『紙』を、別のものに差し替える事ならばできるということです。


前提条件はここまで。
では、「参照情報のコピー」と「値のコピー」の違いについて見ていきましょう。


クラスのコピーすなわち
 aPersonClass2 = aPersonClass1
の場合、両者は『同じ机を眺めている状態』です。
同じ机なので、引き出しの中にある紙も同じであり、書かれている住所も同一です。
ゆえにコピー直後は、aPersonClass1.FirstName と aPersonClass2.FirstName は同一です。

ここで、別のインスタンスの代入、すなわち
 aPersonClass1.FirstName = "ABC"
の処理を行ったとしましょう。これは、引き出し内の『紙』を差し替えるという作業です。

aPersonClass1 さんが見ている机の引き出しを開いて、中の紙を別の紙に差し替えた場合、
aPersonClass2 さんが見ている机の引き出しの中にあった紙も、別のものに差し替わります。
(なぜならば、両者は同じ机を見ているのですから)
そのため、aPersonClass1.FirstName と aPersonClass2.FirstName は常に同じインスタンスです。


構造体のコピーすなわち
 aPersonStruct2 = aPersonStruct1
の場合、『その机の複製が生成された状態』です。
両者の机の中にある紙は別の紙ですが、書かれている住所は同じ家を指しています。
ゆえにコピー直後は、aPersonStruct1.FirstName と aPersonStruct2.FirstName は同一です。

ここで、別のインスタンスの代入、すなわち
 aPersonStruct1.FirstName = "XYZ"
の処理を行ったとしましょう。これは、引き出し内の『紙』を差し替えるという作業でしたね。

aPersonStruct1 さんが見ている机の引き出しを開いて、中の紙を別の紙に差し替えた場合でも、
aPersonStruct2 さんが見ている机の引き出しの中にあった紙は変化せず、元のままです。
(なぜならば、両者は、複製された異なる机を見ているからです)
そのため、aPersonStruct1.FirstName と aPersonStruct2.FirstName は異なるインスタンスになります。

[ツリー表示へ]
タイトル御礼
記事No6281
投稿日: 2007/09/11(Tue) 18:22
投稿者
本当に私のような子供の馬鹿げた質問に対しても、親切丁寧にわかりやすく書いていただきありがとうございました。
やさしさと実力が文面から伝わってきます。

今回、解説していただいたことでかなり道は開けた気がします。
◎「机」の存在する場所である「参照情報」を渡すのがクラス型変数の代入
◎「机」そのものをコピーさせる(同じ内容が書かれた紙が入っている机は2つできる)ことが構造型変数の代入
ということなんですね。

まとめると
たくさんの「紙(メンバ変数)」がつまった机という塊(クラス、構造体)の"場所の情報"を渡すのか、"塊そのもののコピー"を渡すのか、という違いが構造体とクラスの違いの一部としてあるわけですね。

学校から戻って、本屋さんに立ち読みに行きました。
そこで”構造体の中のポインタ”というところを読みました。
C言語の本だったのでいまいちよくわからなかったのですが”構造体の代入は「薄いコピー」”とかかれていました。
この意味がわかりました。
机の中にポインタがあったとしても、机が違うのでいくら机内部のポインタ(紙)を変えてもその先までは影響されない。
ということなんですね。

本当に感激です!この記事はすべて保存させていただきました。
携帯電話のゲームが作れるようにがんばります〜

[ツリー表示へ]