[2019/08/30] クラスのコピーについて(Object.MemberwiseClone) (No.120)
[2019/08/30] 配列の使い方の注意点について(コピー, Clone) (No.119)
[2019/08/29] 配列の使い方について(Dim, Redim) (No.118)
[2019/08/20] バイト指定による文字列の切出しに付いて(MidB, LeftB, RightB) (No.115)
-
配列から範囲を指定して要素を抽出し、新しい配列を作成する方法について記します。
それでは以下の方法を説明したいと思います。■新規配列を生成し 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)
PR -
今回はクラスのコピーについて考えてみますが、以下の順で説明したいと思います。
- クラス変数のコピー
- クラスの実体コピー(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)
-
配列は似た性質のデータをまとめて取り扱えるようにするデータ形式です。 配列の機能は、その昔の言語である FORTRAN , C言語 (私も使った言語ですが)等でも持っています。 ここまで配列の機能が残っているということは、それなりに使い勝手が良いのではないかと思います。 そこで今回は、配列の使い方について順を追って説明したいと思います。
■簡単な配列の宣言と使い方
以下のソースを見て下さい。 処理の前半は、配列を利用しない場合の合計処理を示しています。 配列が無い場合は、値を別々の変数に宣言して、それぞれを参照し合計を求めています。 配列を利用しないと非常に煩雑になってしまします。
処理の後半で配列を利用する場合を示しています。 配列の各要素に値を設定する部分は、ベタに記述していますが、合計を求めるところで 配列の処理らしさが現れています。 (この処理などは初歩の初歩なので特に難しくないと思います。)Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '===配列を利用しない場合の合計処理=== '各変数の宣言 Dim nVal1 As Integer Dim nVal2 As Integer Dim nVal3 As Integer Dim nVal4 As Integer Dim nVal5 As Integer '値の設定 nVal1 = 1 nVal2 = 10 nVal3 = 100 nVal4 = 1000 nVal5 = 10000 '値の合計 Dim nSum As Integer nSum = nVal1 + nVal2 + nVal3 + nVal4 + nVal5 Console.WriteLine("配列を利用しない場合の合計処理:{0}", nSum) '===配列を利用=== '配列宣言 Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 '指標宣言 Dim i As Integer '合計計算 nSum = 0 For i = 1 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) End Sub実行結果がコンソールには以下様に表示されます。(当然合計値は同じ結果)
配列を利用しない場合の合計処理:11111 配列を利用した場合の合計処理:11111
■配列宣言の落とし穴
上記のソースでは配列宣言で Dim arr(5) As Integer と宣言しましたが、 領域としては指標が「1」~「5」まで確保されたかの様に感じますが、実は指標「0」の要素が存在します。
そこで上記のソースを以下の様に変更し実行してみます。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '===配列を利用=== '配列宣言 Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 arr(0) = 10000 '指標「0」の要素 '指標宣言 Dim i As Integer '合計計算 nSum = 0 For i = 0 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) End Sub配列を利用した場合の合計処理:21111結果を見れば指標「0」の要素が存在することが分かります。
Dim arr(5) で5個の領域を宣言したつもりが指標「0」の要素が存在するため、 全体では6個の領域を宣言したことになります。
配列の開始が指標「0」と考えるのか、それとも指標「1」からとするのかで、プログラムの動作や組み方が変わってきます。 複数の人で組む場合にはなおさらで、どちらにするのかを決めておく必要があります。
配列の開始が指標「0」を使うのであれば、必要個数の1個少な目で配列宣言するのか、 それとも指標「1」ならば必要個数で配列宣言し、指標「0」の要素は未使用とするか、と言ったところです。
■配列の初期化
配列の宣言で一緒に初期化ができます。 配列の要素数を記述せずに、宣言のデータ型の後に、=(イコール) で繋ぎ {}(中括弧) で要素を ,(カンマ) で区切って記述します。
上記のソースを配列の初期化を行う様に変更します。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer nSum = 0 For i = 0 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) End Sub
このソース合計計算の For ループで「5」を直値(リテラル)で記述しています。 この場合は「5」であることが明らかなので問題は無いのですが、 配列の最終の指標が何か分からない時には UBound 等を用います。 以下に項目を新しくして説明します。
■配列の最終指標はUBound
UBound 関数は配列内の指定された次元における最も大きいインデックスを返します。
Public Function UBound(Array As System.Array, Optional Rank As Integer = 1) As Integer 引数: ・Array:任意のデータ型の配列です。 ある次元で最も大きいインデックスを探す対象となる配列です。 ・Rank :省略可能です。 Integer. 最も大きいインデックスが返される次元です。 1番目の次元の場合は「1」、2番目の次元の場合は「2」という形で指定します。 Rank を省略した場合、「1」が使用されます。 戻り値:Integer型 ・指定した次元に設定できるインデックスの最大値です。 ・Array に要素が 1 つしかない場合、UBound は 0 を返します。 ・Array に要素が存在しない場合 (長さ 0 の文字列の場合など)、UBound は -1 を返します。
以下の例では For ループの最終指標にこの UBound 関数を使っています。
尚、配列のプロパティのには現在の配列の大きさ(長さ)を返してくれる Length がありますのでそれを使っても同様です。 但し、 Length は個数なので「-1」する必要があります。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) nSum = 0 '配列の大きさを返すプロパティ[Length]を使った最終指標 For i = 0 To arr.Length - 1 nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) End Sub■配列のサイズ変更(ReDim)
配列のサイズを変更するために ReDim 命令を使います。
以下のソースでは ReDim 命令で指標が「2」までのサイズに縮めています。 (合計の結果は「21000」を想定していますが...)Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化:要素6個) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '配列サイズ変更 ReDim arr(2) '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i As Integer = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列をReDimでサイズ変更した場合の合計処理:{0}", nSum) End Subしかし結果は「0」となってしまいます。
配列をReDimでサイズ変更した場合の合計処理:0これは ReDim 命令はサイズを変更した後で全ての要素をその配列のデータ型で初期化してしまうからです。 既に存在した要素を引き継ぐ場合には ReDim 命令で Preserve を指定します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化:要素6個) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '配列サイズ変更(Preserve指定) ReDim Preserve arr(2) '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i As Integer = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列をReDimでサイズ変更した場合の合計処理:{0}", nSum) End Sub結果は想定通り「21000」となりました。
配列をReDimでサイズ変更した場合の合計処理:21000
■配列のサイズ未指定での宣言
配列のサイズを指定せずに配列を宣言することができます。 この場合、配列は空の状態で存在し ReDim 命令でサイズを増やしてやらないと使用できません。 (空の状態の状態は Is Nothing で判定できます)
それでは、以下にInteger型配列をサイズ無しで宣言し、配列のサイズを順次拡張して中身が「0」~「9」となる 配列を作成する例を示します。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr() As Integer ' = Nothing はあった方がいいと思います '「0」~「9」の処理 For i As Integer = 0 To 9 If arr Is Nothing Then '最初は指標「0」 ReDim arr(0) arr(0) = i Else '次の指標 Dim idx As Integer = UBound(arr) + 1 'サイズ拡張 ReDim Preserve arr(idx) '要素設定 arr(idx) = i End If Next '配列の要素を文字列として連結 Dim str As String = "" For i As Integer = 0 To UBound(arr) If str <> "" Then str &= "," str &= arr(i) Next Console.WriteLine("配列:{0}", str) End Sub配列:0,1,2,3,4,5,6,7,8,9
■配列のループ処理は For Each
上記に配列のループ処理の最終指標として UBound Array.Length を使いましたが、 指標に関係なく配列の要素を順次取得する方法が For Each … Next 処理です。 以下の実際のソースで直感的に分かると思います。
For Each の後ろに配列の要素と同じデータ型の変数を記述し In の後ろに対象となる配列を記述します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '要素宣言 Dim nEnt As Integer '合計計算 Dim nSum As Integer = 0 For Each nEnt In arr nSum += nEnt Next Console.WriteLine("For Each ... Nexを利用した場合の合計処理:{0}", nSum) End SubFor Each ... Nexを利用した場合の合計処理:21111
尚、要素用の変数を別で宣言しなくても以下の様にしても同様な結果となります。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '合計計算 Dim nSum As Integer = 0 For Each nEnt As Integer In arr nSum += nEnt Next Console.WriteLine("For Each ... Nexを利用した場合の合計処理:{0}", nSum) End Sub関連する記事
⇒配列の使い方の注意点について(コピー, Clone)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)
-
あるデータ処理において文字列を Shift-JIS と考えて切出す必要があり、 文字列を Shift-JIS と見立てて切出す関数を作ってみましたので紹介します。
VB.NET において通常、文字列は Unicode(UTF-8) で処理されています。 ほとんどの場合は文字コードのことを特に考えなくても問題は無いのですが、まれにシリアル通信等で外部とのやり取りを行う場合に 文字列のコードが Shift-JIS を使っていたりします。こういった場合に今回の関数群は使えると思います。
では、最初に文字列の中から文字列のバイト位置(Shift-JISと考えて)と切出すバイト数を指定し、処理する関数である MidB(以前の VB では Mid$ とされていたもの)を記します。
開始位置、切出しバイト数の指定値により、全角文字の泣き別れが発生しますので、関数の処理手順は以下の様に行います。- 開始位置が「1」未満の場合は空文字を返す。
- 文字列をShift-JISエンコードでバイト配列に分解し、開始位置が配列の長さより大きい場合は空文字を返す。
- 先頭から見て指定位置の直前までを切出し最後の文字が漢字の左半分の場合、開始位置と切出しバイト数を調整。
- 調整された開始位置とバイト数で文字列を切出し最後の文字が漢字の左半分の場合、切出しバイト数を調整。
- 調整された開始位置とバイト数で文字列を生成する。
MidB 関数
'''
''' 開始位置、バイト指定による文字列切出・Mid関数 ''' ''' <param name="astrSrc">対象文字列</param> ''' <param name="aintStart">開始位置(先頭1から)</param> ''' <param name="aintLen">切出しバイト数</param> '''切出し文字列 Private Function MidB(ByVal astrSrc As String, ByVal aintStart As Integer, ByVal aintLen As Integer) As String Try '開始を「0」からの指標 Dim intStart As Integer = aintStart - 1 '開始が1より小さい場合は空文字を返す If intStart < 0 Then Return "" End If 'Shift-JISでエンコーディングしてバイト配列に分解 Dim objEnc As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS") Dim arrBytes As Byte() = objEnc.GetBytes(astrSrc) '開始がバイト配列より大きい場合は空文字を返す If intStart > arrBytes.Length - 1 Then Return "" End If '取り出しバイト数 Dim intLen As Integer = aintLen If (intStart + aintLen) > (arrBytes.Length - 1) Then intLen = arrBytes.Length - intStart End If '先頭から見て指定位置の直前までを切り出す Dim strTemp As String = objEnc.GetString(arrBytes, 0, intStart) If strTemp.EndsWith(ControlChars.NullChar) Or strTemp.EndsWith("・") Then '最後の文字が漢字の左半分の場合、切出の先頭は泣き別れになる intStart += 1 '開始位置を右へ intLen -= 1 '切出バイト数を1個少なくする End If '修正位置とバイト数で切り出す strTemp = objEnc.GetString(arrBytes, intStart, intLen) If strTemp.EndsWith(ControlChars.NullChar) Or strTemp.EndsWith("・") Then '最後の文字が漢字の左半分の場合、泣き別れになる intLen -= 1 '切出バイト数をさらに1個少なくする End If '文字列を返す Return objEnc.GetString(arrBytes, intStart, intLen) Catch ex As Exception Return "" End Try End Function以前の VB の Mid$ では第3引数が無く、開始位置から右側を全て返す関数がありましたので、 同じ関数名で引数の数が異なる関数を以下に記します。
MidB 関数(開始位置以降の切出し)
'''
''' 開始位置以降の文字列切出・Mid関数 ''' ''' <param name="astrSrc">対象文字列</param> ''' <param name="aintStart">開始位置(先頭1から)</param> '''切出し文字列 Private Function MidB(ByVal astrSrc As String, ByVal aintStart As Integer) As String Try '開始を「0」からの指標 Dim intStart As Integer = aintStart - 1 '開始が1より小さい場合は空文字を返す If intStart < 0 Then Return "" End If 'Shift-JISでエンコーディングしてバイト配列に分解 Dim objEnc As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS") Dim arrBytes As Byte() = objEnc.GetBytes(astrSrc) '開始がバイト配列より大きい場合は空文字を返す If intStart > arrBytes.Length - 1 Then Return "" End If '実際の処理は上記の関数をコール Return MidB(astrSrc, aintStart, arrBytes.Length - aintStart + 1) Catch ex As Exception Return "" End Try End Functionさらに、文字列の左側から指定バイト数を切り出す LeftB 関数、文字列の右側から指定バイト数を切り出す RightB 関数を記します。 この2個の関数は最初の MidB 関数を利用しています。
LeftB 関数 , RightB 関数
'''
''' バイト数指定による文字列の左側切出し・Left関数 ''' ''' <param name="astrSrc">対象文字列</param> ''' <param name="aintLen">左側切出しバイト数</param> '''切出し文字列 Private Function LeftB(ByVal astrSrc As String, ByVal aintLen As Integer) As String '先頭から指定バイト数を切り出す Return MidB(astrSrc, 1, aintLen) End Function '''''' バイト数指定による文字列の右側切出し・Right関数 ''' ''' <param name="astrSrc">対象文字列</param> ''' <param name="aintLen">右側切出しバイト数</param> '''切出し文字列 Private Function RightB(ByVal astrSrc As String, ByVal aintLen As Integer) As String 'Shift-JISでエンコーディングしてバイト配列に分解 Dim objEnc As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS") Dim arrBytes As Byte() = objEnc.GetBytes(astrSrc) 'バイト数がバイト配列の個数より多い場合は、対象文字列そのまま返す If aintLen >= arrBytes.Length Then Return astrSrc End If '開始位置 Dim intStart As Integer = arrBytes.Length - aintLen + 1 '先頭から指定バイト数を切り出す Return MidB(astrSrc, intStart, aintLen) End Functionこれらの関数を実行する例を以下に記します。 これはフォームにボタンを設置し、クリックイベント時に、各関数の処理を行い結果をデバッグ用の出力ウインドウに表示しています。
Private Sub btnStringCut_Click(sender As Object, e As EventArgs) Handles btnStringCut.Click '表示 Dim strSrc As String = "あいうえお" Dim i As Integer '引数が3個ある MidB 関数 Dim nCut As Integer = 6 For i = 0 To 10 Dim strDes As String = MidB(strSrc, i, nCut) Console.WriteLine("MidB(""{0}"", {1}, {2}) : /{3}/", strSrc, i, nCut, strDes) Next '引数が2個の MidB 関数 For i = 0 To 10 Dim strDes As String = MidB(strSrc, i) Console.WriteLine("MidB(""{0}"", {1}) : /{2}/", strSrc, i, strDes) Next 'LeftB 関数 For i = 0 To 10 Dim strDes As String = LeftB(strSrc, i) Console.WriteLine("LeftB(""{0}"", {1}) : /{2}/", strSrc, i, strDes) Next 'RightB 関数 For i = 1 To 11 Dim strDes As String = RightB(strSrc, i) Console.WriteLine("RightB(""{0}"", {1}) : /{2}/", strSrc, i, strDes) Next End Sub結果の表示は以下の通りです。
MidB("あいうえお", 0, 6) : // MidB("あいうえお", 1, 6) : /あいう/ MidB("あいうえお", 2, 6) : /いう/ MidB("あいうえお", 3, 6) : /いうえ/ MidB("あいうえお", 4, 6) : /うえ/ MidB("あいうえお", 5, 6) : /うえお/ MidB("あいうえお", 6, 6) : /えお/ MidB("あいうえお", 7, 6) : /えお/ MidB("あいうえお", 8, 6) : /お/ MidB("あいうえお", 9, 6) : /お/ MidB("あいうえお", 10, 6) : // MidB("あいうえお", 0) : // MidB("あいうえお", 1) : /あいうえお/ MidB("あいうえお", 2) : /いうえお/ MidB("あいうえお", 3) : /いうえお/ MidB("あいうえお", 4) : /うえお/ MidB("あいうえお", 5) : /うえお/ MidB("あいうえお", 6) : /えお/ MidB("あいうえお", 7) : /えお/ MidB("あいうえお", 8) : /お/ MidB("あいうえお", 9) : /お/ MidB("あいうえお", 10) : // LeftB("あいうえお", 0) : // LeftB("あいうえお", 1) : // LeftB("あいうえお", 2) : /あ/ LeftB("あいうえお", 3) : /あ/ LeftB("あいうえお", 4) : /あい/ LeftB("あいうえお", 5) : /あい/ LeftB("あいうえお", 6) : /あいう/ LeftB("あいうえお", 7) : /あいう/ LeftB("あいうえお", 8) : /あいうえ/ LeftB("あいうえお", 9) : /あいうえ/ LeftB("あいうえお", 10) : /あいうえお/ RightB("あいうえお", 1) : // RightB("あいうえお", 2) : /お/ RightB("あいうえお", 3) : /お/ RightB("あいうえお", 4) : /えお/ RightB("あいうえお", 5) : /えお/ RightB("あいうえお", 6) : /うえお/ RightB("あいうえお", 7) : /うえお/ RightB("あいうえお", 8) : /いうえお/ RightB("あいうえお", 9) : /いうえお/ RightB("あいうえお", 10) : /あいうえお/ RightB("あいうえお", 11) : /あいうえお/関連する記事
⇒文字列の連結をStringBuilderで高速に行う(StringBuilder)
⇒SerialPortコントロールの使い方その3(外部装置からの垂れ流しデータ受信)