-
通常プログラムを組んでいて計算処理を行う場合にはそこまで気にすることは無いのですが、 小数点以下の計算で誤差が生じない様にするため金額計算では Decimal 型変数を使い、 科学計算的な場合には Double 型変数を使用する様にしています。
また、ループの指標等、少ない桁の整数の場合には Integer 型変数を使い、 桁数が多い整数の計算では Long 型変数を使います。
尚、各データ型の内容ですが以下の様になっています。■各データ型について
データ型 型説明 サイズ 有効桁数 値の範囲 Integer 整数型 4バイト -2147483648 ~ 2147483647 Long 長整数型 8バイト -9223372036854775808 ~ 9223372036854775807 Single 単精度浮動小数点型 4バイト 7桁 -3.40282347E+38 ~ 3.40282347E+38 Double 倍精度浮動小数点型 8バイト 15桁 -1.7976931348623157E+308 ~ 1.7976931348623157E+308 Decimal 10進型 16バイト 28桁 -7.9228162514264337594E+28 ~ 7.9228162514264337594E+28 ■Single 型と Double 型について
浮動小数点型は内部的に符号部、指数部、仮数部に分けてデータを持っているそうです。 Single 型は符号部(1ビット)、指数部(8ビット)、仮数部(23ビット) で Double 型は符号部(1ビット)、指数部(11ビット)、仮数部(52ビット) で構成されているらしいです。 (詳しくは IEEE 754 の説明にあたって下さい。)
Single 型 Double 型の計算を行わせてどのくらいの精度があるのかを見てみます。
以下のソースでは小数「0.1」を10回加算して結果を有効桁20桁までで表示させています。 「0.1」と言う数値は2進数にすると「0.00011001100110011…」の循環小数になることはよく知られています。 これを10回加算して結果が数学的に「1」になるでしょうか?Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '単精度数値変数 Dim sngVal As Single = 0 '倍精度数値変数 Dim dblVal As Double = 0 'それぞれ「0.1」を10回加算 For i As Integer = 1 To 10 sngVal += 0.1F 'リテラルで「F」指定は単精度数値 dblVal += 0.1D 'リテラルで「D」指定は倍精度数値 Next '結果が「1」になることを期待? Console.WriteLine("Single Data = {0}", sngVal.ToString("G20")) Console.WriteLine("Double Data = {0}", dblVal.ToString("G20")) End Sub
実行結果がコンソールには以下様に表示されます。 やはり「1」にはなりません。 リテラルで加算される「0.1」は循環小数なので内部的にはどこかで丸めが発生しているからです。
Single Data = 1.00000012 Double Data = 0.99999999999999989
Single 型では有効桁数が少ないので、小数以下4桁どうしの乗算を行うと小数以下8桁まで必要になりますが、 小数以下8桁以降は信頼できない値になります。これでは科学計算には向かないので、 Double 型を使うことになります。
■Decimal 型について
上記の処理を Decimal 型に変えて処理します。
Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '10新型数値変数 Dim decVal As Decimal = 0 '「0.1」を10回加算 For i As Integer = 1 To 10 decVal += 0.1D 'リテラルで「D」指定は10新型数値 Next '結果が「1」になることを期待? Console.WriteLine("Decimal Data = {0}", decVal.ToString("G20")) decVal = 0.1D Console.WriteLine("Decimal Data = {0}", decVal.ToString("G30")) End Sub
実行結果がコンソールには以下様に表示されます。 確かにこの程度の小数以下の精度では問題無い様です。
Decimal Data = 1 Decimal Data = 0.1
Decimal 型はデータサイズも大きく内部での計算は他の型よりも時間が掛かりますが、 会計処理などでは小数点以下の精度に正確性が求められますので、この型を使います。
私が組むプログラムは OA 系の処理が多いので数値は全て Decimal 型を使っています。 まれに、工場のライン監視などで計測データが必要なときは Double 型も使いますが、 そこまで速度が要求されなければ Decimal 型を使います。
■リテラル(直値)宣言の注意
プログラム上で直接値(リテラル)を宣言する場合に注意が必要です。 値を正しく宣言しないと期せずして異なるデータ型で処理されてることがあります。
以下のソースは小数点付きで宣言した場合と、小数点無し(整数)で宣言した場合に、数値の後ろにデータ型を指示する文字を 付加する場合としない場合の例を示します。Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click '各種データ型を入れるため「Object」型 Dim objTest As Object = Nothing 'Decimal型のリテラル objTest = 0.1 Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Single型のリテラル objTest = 0.1F Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Integer型のリテラル objTest = 12345 Console.WriteLine("Type = {0}", objTest.GetType.ToString) 'Long型のリテラル objTest = 12345L Console.WriteLine("Type = {0}", objTest.GetType.ToString) End Sub
実行結果がコンソールには以下様に表示されます。
Type = System.Double Type = System.Single Type = System.Int32 Type = System.Int64
■各データ型のリテラル指定について
データ型 型説明 リテラル指定 Integer 整数型 I Long 長整数型 L Single 単精度浮動小数点型 F Double 倍精度浮動小数点型 R Decimal 10進型 D 関連する記事
⇒指定した精度の桁数に数値を切り上げ :[Math.Ceiling,Math.Floor]
⇒文字列から数値型への変換(parse - tryparse)
⇒オブジェクト型から数値型への変換(TryParse)
PR -
バイナリファイルからのバイト配列への読込処理と、バイト配列からバイナリファイルへの書込処理について記します。 通常テキストファイルはバイナリファイルとは異なるものとして扱いますが、 ファイルの中身は文字コードのみで構成されたバイナリファイルとして取り扱いができます。 (データファイルは全てバイナリファイルであるとも言えますが)
■バイト配列からバイナリファイルへの書込処理
それではファイルを読込むためにファイルが存在しないと始まらないので、 バイナリファイルをバイト配列から書込む処理を説明します。
バイト配列は適当にデータを生成するとして、書込処理には System.IO.FileStream クラスの Write メソッドを使用します。Private Sub btnWrite_Click(sender As Object, e As EventArgs) Handles btnWrite.Click '書込むバイト型配列:Shift-JISの文字 "I" "J" "K" "L" "0" "1" "2" Dim arrByte As Byte() = New Byte() {&H49, &H4A, &H4B, &H4C, &H30, &H31, &H32} 'ファイルを作成して書込む (存在する場合は上書き) Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Create, System.IO.FileAccess.Write) 'バイト型配列の内容をすべて書き込む fs.Write(arrByte, 0, arrByte.Length) '閉じる fs.Close() End Sub
■バイナリファイルからバイト配列への読込処理
バイナリファイルからの読込処理では最初に System.IO.FileStream クラスを生成することで対象ファイルをオープンします。 System.IO.FileStream クラスの長さのプロパティ Length 分のバイト配列を宣言し Read メソッドで読込を行います。
Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click 'ファイルを開く Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read) 'ファイルを読み込むバイト型配列を作成 Dim arrByte(fs.Length - 1) As Byte 'ファイルの内容を全て読込 fs.Read(arrByte, 0, arrByte.Length) 'ファイルを閉じる fs.Close() 'バイト配列を16進数表記の文字列で表示(各要素を「-」で連結) Console.WriteLine(BitConverter.ToString(arrByte)) End Sub
実行結果がコンソールには以下様に表示されます。確かに書込み処理されたデータが読み込まれています。
49-4A-4B-4C-30-31-32
■大きなバイナリファイルの分割読込処理
上記の読込処理では System.IO.FileStream クラスでオープンした後でファイルの大きさでバイト配列を宣言しましたが、 ファイルサイズが非常に大きい場合、メモリ確保に負担が掛かります。 そのため以下の様に、固定のバイト数での読込を複数回処理することで全ての読込処理が行えます。
Read メソッドは順次読込を行いますが、ファイルの最後まで読込、更にこのメソッドを呼び出すと結果として「0」が返ります。 よって、「0」が返った時点で処理を終了します。Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click 'ファイルを開く Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read) 'ファイルを16バイトずつ読み込むバイト型配列を作成 Dim arrByte(16 - 1) As Byte While True 'ファイルの内容を全て読込 Dim nRead As Integer = fs.Read(arrByte, 0, arrByte.Length) If nRead = 0 Then Exit While End If '部分読込データ処理 'バイト配列を16進数表記の文字列で表示(各要素を「-」で連結) Console.WriteLine(BitConverter.ToString(arrByte)) End While 'ファイルを閉じる fs.Close() End Sub
実行結果がコンソールには以下様に表示されます。 書込まれているデータが7バイトのため、16バイト以下の読込となり1回で処理が終わることになります。
49-4A-4B-4C-30-31-32-00-00-00-00-00-00-00-00-00
■バイナリファイルのコピー処理
上記の分割読込処理を元にバイナリファイルコピーの処理を関数としてまとめてみました。
'''
''' バイナリファイルコピー ''' ''' <param name="SrcPath">コピー元のフルパスファイル名</param> ''' <param name="DesPath">コピー先のフルパスファイル名</param> ''' <param name="nSize">コピー分割処理バイト数</param> '''True:正常終了, False:NG '''エラーは詳細に処理していません Public Function CopyBinaryFile(ByVal SrcPath As String, ByVal DesPath As String, ByVal nSize As Integer) As Boolean CopyBinaryFile = True '読み書きの FileStream 宣言 Dim fsRead As System.IO.FileStream = Nothing Dim fsWrite As System.IO.FileStream = Nothing Try '読込ファイルを開く fsRead = New System.IO.FileStream(SrcPath, System.IO.FileMode.Open, System.IO.FileAccess.Read) '書込ファイルを開く fsWrite = New System.IO.FileStream(DesPath, System.IO.FileMode.Create, System.IO.FileAccess.Write) 'バイト配列宣言 Dim arrByte = New Byte(nSize - 1) {} 'コピー処理ループ While True '元のファイル読込 Dim nRead As Integer = fsRead.Read(arrByte, 0, nSize) If nRead = 0 Then Exit While End If 'コピー先に書込 fsWrite.Write(arrByte, 0, nRead) End While Catch ex As Exception 'エラー処理 Return False Finally 'ファイルクローズ If Not fsRead Is Nothing Then fsRead.Close() End If If Not fsWrite Is Nothing Then fsWrite.Close() End If End Try End Function 'バイナリファイルコピーのテスト用ボタンクリック処理 Private Sub btnCopy_Click(sender As Object, e As EventArgs) Handles btnCopy.Click Dim blnRet As Boolean = CopyBinaryFile("sample.txt", "sample2.txt", 4) Console.WriteLine("CopyBinaryFile...{0}", blnRet) End Sub関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
-
配列から範囲を指定して要素を抽出し、新しい配列を作成する方法について記します。
それでは以下の方法を説明したいと思います。■新規配列を生成し Array.Copy でコピー
Array.Copy の引数の説明は以下の通りです。
Array.Copy(sourceArray As System.Array, sourceIndex As Integer, destinationArray As System.Array, destinationIndex As Integer, length As Integer) ・sourceArray :コピー元の配列データ。 ・sourceIndex :コピー操作の開始位置となる sourceArray 内のインデックスを表す 32 ビット整数。 ・destinationArray:コピー先の配列データ。 ・destinationIndex:格納を開始する destinationArray のインデックスを表す 32 ビット整数。 ・length :コピーする要素の数を表す 32 ビット整数。
以下のソースを見て下さい。 最初にコピー元配列「arr」を宣言し、コピー先の配列「arr2」をコピー数で領域確保しています。 今回の処理ではコピー元の位置を「6」として3個の要素をコピーしています。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー元の位置 Dim iPos As Integer = 6 'コピー先で3個の要素 Dim nLen As Integer = 3 Dim arr2(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arr2, 0, nLen) 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub
実行結果がコンソールには以下様に表示されます。確かにコピーされている様です。
arr2(0):6 arr2(1):7 arr2(2):8
この処理を関数に以下の様にまとめてみました。'''
''' 配列切出し ''' ''' <param name="arr">切出し元の配列</param> ''' <param name="iPos">切出し位置</param> ''' <param name="nLen">要素数</param> '''切出した配列を返す Function SliceArray(ByVal arr() As Integer, ByVal iPos As Integer, ByVal nLen As Integer) As Integer() '指定位置が配列の大きさを超えた? If iPos >= arr.Length Then Return {} End If 'コピー最終位置 Dim iLast As Integer = iPos + nLen - 1 '指定位置からの指定個数が配列の大きさを超えた? If iLast >= arr.Length Then '個数の調整 nLen = arr.Length - iPos End If '返す配列の生成 Dim arrRet(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arrRet, 0, nLen) '配列を返す Return arrRet End Function■配列を Enumerable.Skip、Enumerable.Take メソッドで処理
System.Linq.Enumerable の Skip メソッドを使うことで、配列内の一連の要素から指定位置から後ろのデータを全て取得します。 更に System.Linq.Enumerable の Take メソッドで先頭から指定個数の要素を切出します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー '「arr」の先頭から6個の要素をスキップしてそれ以降を配列生成 Dim arr2() As Integer = System.Linq.Enumerable.Skip(arr, 6).ToArray '「arr2」の先頭から3個の要素のみで配列生成 arr2 = System.Linq.Enumerable.Take(arr2, 3).ToArray '以下の様に連結しても同様!! 'Dim arr2() As Integer = arr.Skip(6).Take(3).ToArray 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub
関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
-
今回はクラスのコピーについて考えてみますが、以下の順で説明したいと思います。
- クラス変数のコピー
- クラスの実体コピー(Object.MemberwiseClone)
- クラスの実体コピー・参照型を含む(Object.MemberwiseClone)
- クラスの実体コピー・ディープコピー(Serializable)
■クラス変数のコピー
早速クラスのテストを以下のソースで行ってみます。
テスト用クラスとして Integer 型と String 型の2個のパブリック変数を持ち、 コンストラクタで値を設定するものとします。'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラス変数のコピー cTst2 = cTst1 '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=200, strData=BBB
クラス変数のコピーを行った後で、[cTst1]のデータを変更したつもりでも、[cTst2]のデータとして表示すると同じになってます。 これは、クラスの変数にはクラス生成されたデータ領域の参照が設定されるだけで、 クラス変数のコピーではその参照値(ポインタ)がコピーされるだけです。
結果、[cTst1]も[cTst2]も参照が指し示す実体は同じものとなり、 [cTst1]でデータにアクセスしても、[cTst2]でデータにアクセスしたことと同じになります。
どうしてもコピーしたい場合は、[cTst2]を新しいクラスで生成して、クラスの中身をそれぞれコピーすることはできます。 以下のソースでその例を示します。(しかし、これでは変数が多い場合は手間ですし、カッコよくありませんが...)
'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'デフォルトのコンストラクタ Sub New() End Sub 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラスのコピー cTst2 = New cTest cTst2.intData = cTst1.intData cTst2.strData = cTst1.strData '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。当然ですが、[cTst1]のデータと[cTst2]のデータは異なるものが表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
本当はクラス自体が配列のコピーの Clone メソッドの様なものを持てればすっきりするのですが。
■クラスの実体コピー(Object.MemberwiseClone)
クラスにコピーメソッドを実装する為にソースを変更します。 先ずクラスにクローン作成をサポートするClone メソッドを定義し Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを返してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone Implements ICloneable 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub 'コピーメソッド Public Function Clone() As cTestWithClone Return MemberwiseClone() '現在の System.Object の簡易コピー作成 End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA") Dim cTst2 As cTestWithClone 'クラスのクローンコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
これでほぼ目的は達成されるのですが、クラス内に参照型のデータが存在する場合、参照のみがコピーされるため注意が必要です。 (このあたりが、「簡易コピー」といわれる所以です。)
■クラスの実体コピー・参照型を含む場合(Object.MemberwiseClone)
クラスに参照型データとして Integer 型の配列を定義します。 例のコピーメソッドの実装では Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを作成し、 作成できない参照型を個別にクローン生成してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub 'コピーメソッド Public Function Clone() As cTestWithClone '先ず簡易コピー作成 Dim cTest As cTestWithClone = MemberwiseClone() '参照型データが設定済みならばクローン作製 If MyClass.arr IsNot Nothing Then cTest.arr = MyClass.arr.Clone End If 'コピーを返す Return cTest End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA", {100, 200, 300}) Dim cTst2 As cTestWithClone 'クラスのコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBBB, arr(1)=100, arr(2)=0, arr(0)=300 cTst2 : nData=100, strData=AAAA, arr(1)=100, arr(2)=200, arr(0)=300
これで参照型データのコピーが出来ることが分かりました。 尚、参照型データが複数ある場合は、全てにおいて処理を繰り返す必要があります。
■クラスの実体コピー・ディープコピー(Serializable)
以下の記事にもありますが、クラスも1個のオブジェクトなのでクラスの実体をシリアル化して、別の実体にシリアル化を戻せば、 クラスのコピーができることになります。
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
以下のソースに上記の記事内の関数を利用してクラスのコピーを行います。
クラスの宣言の先頭で <Serializable()> と記述し、シリアル化ができることを宣言しないと、関数でコピーができません。'テストクラス(シリアル化可能) <Serializable()> Class cTest 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA", {10, 20, 30}) Dim cTst2 As cTest 'クラスのコピー cTst2 = DeepCopy(cTst1) '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub
実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB, arr(1)=10, arr(2)=0, arr(0)=30 cTst2 : nData=100, strData=AAAA, arr(1)=10, arr(2)=20, arr(0)=30
関連する記事
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
-
前回の以下の記事は配列の使い方の基本について記しましたが、 今回は配列を使う場合の注意点をまとめてみました。
⇒配列の使い方について(Dim, Redim)
それでは以下の内容で説明したいと思います。■配列変数のコピー
以下のソースを見て下さい。 最初に配列「arr1」「arr2」を宣言していますが、「arr1」のみ初期化も行っています。
その後、「arr2」に「arr1」を代入しています。 (この代入で「arr2」に「arr1」の内容全てがコピーされた様に思いますが、実際にそうでしょうか。)
「arr1」の2番目の要素クリア後、「arr1」及び「arr2」の内容を全てコンソールに表示させてどうなったかを見てみます。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列変数のコピー arr2 = arr1 '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub
実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:0 arr2[2]:300
予想に反して「arr1」及び「arr2」共に同じ値が表示されました。
どうしてこうなるのかと言いますと、「配列変数のコピー」では配列の中身では無く参照をコピーすることになるからです。 参照とは配列の実体を指し示すポインタと言った方がいいかもしれません。
実は変数「arr1」も配列の実体の参照でしかないということです。 {100, 200, 300} と宣言された配列の実体はどこかにあり、その実体を指し示すポインタを変数「arr1」が保持しています。
実体をコピーしたい場合には配列の Clone メソッドを使います。以下にその様子を示します。
■配列変数の実体コピー(Clone)
上記のソースでは配列変数コピーで単に変数同士のコピーを行いましたが、 以下では配列の Clone メソッドの結果を代入しています。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列変数の実体コピー arr2 = arr1.Clone '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub
実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:200 arr2[2]:300
結果を見れば指標「1」の要素が「arr1」及び「arr2」が異なる値であることが分かります。
Clone メソッドは配列の実体を別に作成し、全ての要素をコピーしその参照を返します。 (Clone 直後は要素の内容は全て同じです。) そのため、「arr1」及び「arr2」は実体が別々の配列となります。
■配列の実体コピー・ディープコピー(Serializable)
以下の記事にもありますが、配列も1個のオブジェクトなので配列の実体をシリアル化して、別の実体にシリアル化を戻せば、 配列のコピーができることになります。
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
以下のソースに上記の記事内の関数を利用して配列のコピーを行います。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列の実体コピー・ディープコピー arr2 = DeepCopy(arr1) '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub
実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:200 arr2[2]:300
関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)