
セキュリティ的な理由で、職場にてLL言語の代わりにEXCEL VBAを使用する機会がふえています。
使用していてちょっと気になることができたので質問いたします。
必要に応じてサイズを増やすため、要素を動的に確保するプログラムにしたのですが、最初の要素を追加する場面でエラーになります。
予め最初の要素を作っておくとエラーにならないのですが、要素が無題になるので気に入りませんw
この場合、もっと適切がやりかたがあるのでしょうか?
Sub Sample()
Dim ary() As Variant, e As Variant
Dim size As Long
'ReDim ary(0) ' ←ここを有効にするとOK
size = UBound(ary) ' ←ここでエラー"インデックスが有効範囲にありません"
ReDim Preserve ary(size + 1)
ary(size + 1) = "foo"
size = UBound(ary)
ReDim Preserve ary(size + 1)
ary(size + 1) = "bar"
For Each e In ary
MsgBox TypeName(e) & ":" & e
Next
End Sub
No.1ベストアンサー
- 回答日時:
配列の組み込み方というものは、様々なスタイルがありますから、一概にどれがよいとは言えません。
ただし、
For Each e In ary
MsgBox TypeName(e) & ":" & e
Next
VBAをよく使う人たちで時々見かけますが、配列で、Indexの初期値に値があろうとなかろうと、こうしたFor Each スタイルを取るというのはレアケースだと思います。
配列は、
For Lbound(ary) To Ubound(ary)
Next
というスタイルが一般的です。
>予め最初の要素を作っておくとエラーにならないのですが、要素が無題になるので気に入りませんw
好む、好まないにも関わらず、配列変数のindex =0 の値を捨ててしまうコードは時々みかけます。というか、私などは、WebコントロールでDOMのオブジェクトから、データを取り出しますが、必ずしも、index=0 が生きたデータを持っているとは限らないからです。
そして、一回ずつ、UBoundで大きさを測る必要はないと思います。
そのindex は、インクリメントですから。
まとめますと、
・Index Counter の初期値 -1
この手法は、VB6の時に、林晴比古さんのコードによく出てきました。
・配列変数の宣言の時に、多めに、Index 設けておく。ary(1000)
収集が終わった時点で、不要なものを、Redim Preserve で空の部分を捨てる
・最初は、ary の配列宣言をしない。最初だけ、Redim で、後は、Redim Preserveにする。
・配列の代わりに、Collection にしてしまう。
また、dictionary オブジェクトやArrayList(.Net Framework)を使ってしまう。
など、思いつきます。
なお、私が、最近、多く書くスタイルというと以下のようなものです。
Sub Sample1()
Dim ary() As Variant, i As Long, j As Long
Dim LastRow As Long
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
ReDim ary(j) 'ここでは、LastRowは反映させないことにする。
For i = 1 To LastRow
ary(j) = Cells(i, 1).Value
j = j + 1
If j >= LastRow Then Exit For
ReDim Preserve ary(j)
Next i
For i = LBound(ary) To UBound(ary)
MsgBox ary(i)
Next i
End Sub
ご回答ありがとうございました。
いろいろなパターンをご提示いただきまして大変勉強になりました。
> 収集が終わった時点で、不要なものを、Redim Preserve で空の部分を捨てる
redim は要素を減らす方向にも使用できるのでね。
> 最初は、ary の配列宣言をしない。最初だけ、Redim で、後は、Redim Preserveにする。
個人的にはこれが好みですので、使用したいと思います。
> こうしたFor Each スタイルを取るというのはレアケースだと思います。
Rubyとか使用していると、For min To maxという書き方をまず使用しないので、影響されているのですね。今後は意識的にこちらの書き方をしていきたいと思います。
あと質問からは逸れますが、ご提示いただきましたプログラムで疑問点があります。
もしお手隙でしたら、ご回答いただけますと嬉しいです。
> ReDim ary(j) 'ここでは、LastRowは反映させないことにする。
変数jの初期設定を行っておりませんが、宣言時にゼロ値(Longなら0)に自動で初期化されることが保証されるのでしょうか?
> For~Next
で変数iとjを分けているのは、セルと配列のインデックスを分けて管理しやすく?しているのだと思いますが、他になにか理由はありますか。
No.2
- 回答日時:
#1の回答者です。
私は、Excel VBAでは、配列で処理することが多いのですが、もう何年もやっているはずなのに、まだ、新しいやり方をみて、それを使ったりします。
#1のお礼欄のご質問、さすがに鋭いですね。#1で省略した部分です。微妙な所もあって、書き渋ってしまいました。
人には人のスタイルがある、という所をわきまえていただければ十分なのですが、人によっては、白を黒と言いくるめられるぐらいの技量のある人から、私の書いた内容を、うやむやにするか、あえて、間違っているかのように、例をつけて説明する人がいます。
お急ぎなら、最後のまとめがありますので、それで読んだほうがはやいです。
それで、まず、ここから。
>セルと配列のインデックスを分けて管理しやすく?しているのだと思いますが、
>他になにか理由はありますか。
>> こうしたFor Each スタイルを取るというのはレアケースだと思います。
かつて、九天社というところで、『VBA デバッグ 実践のツボ』には、
以下のコードとともに、「For Each ~ Next では、検査はできても代入はできない」として、紹介していました。
Sub Sample12Kten()
Dim ary_num() As Variant '配列要素を格納
Dim i As Variant '個々の配列要素を格納
ary_num = Array(123, 234, 345, 456) '初期値を格納
'特定の配列要素の場合、配列の内容を変更
For i = LBound(ary_num) To UBound(ary_num)
If ary_num(i) = 234 Then
ary_num = Array(567, 678, 789, 890)
End If
Next i
'配列要素を表示
MsgBox "ary_num(0) = " & ary_num(0) & vbCrLf & _
"ary_num(1) = " & ary_num(1) & vbCrLf & _
"ary_num(2) = " & ary_num(2) & vbCrLf & _
"ary_num(3) = " & ary_num(3)
End Sub
また、例えば、2次元の時どうするか、ということも含まれています。For Each で取ると、縦横が逆転してしまったります。Index(添字)の管理のほうがずっと優れていると思うのです。
Sub maKingArray()
'こちらはどうでもよいです。2次元変数をつくるためのコード
Dim i, j
Dim a, ar
ReDim ar(10, 2) '元になる方を2次側に置くのが一般(変更可能)
a = Array("A", "B", "C")
For j = 0 To 2
For i = 0 To 10
If i = 0 Then
ar(i, j) = a(j)
Else
ar(i, j) = a(j) & i
End If
Next i
Next j
Call ArrayTest(ar)
End Sub
Sub Sample3(arg As Variant)
'大事なのはこちら。セルの構造と同じになります。
Dim x, y, i, j
y = UBound(arg) 'xは横
x = UBound(arg, 2) 'yは縦 (1次元か2次元か分からない時は、On Error トラップが必要ですょ
For j = 0 To x
For i = 0 To y
Cells(i + 1, j + 1).Value = arg(i, j)
Next i
Next j
End Sub
'//
配列というのは、要素にオブジェクトを取り込むことはめったにありません。だいたい文字列か数値か、そのどちらかです。配列の構造も、End of File でも、End of Data でもなく、計数で終わることが可能なのです。
さて本題です。
>> ReDim ary(j) 'ここでは、LastRowは反映させないことにする。
動的に作るためにはそこでは、配列を作らないようにしました。
Redim ary(LastRow -1 ) で可能だからです。
#1のコードを、
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
ReDim ary(LastRow -1) 'ReDim ary(0 To LastRow -1)
とすることができます。
>変数jの初期設定を行っておりませんが、宣言時にゼロ値(Longなら0)に自動で>初期化されることが保証されるのでしょうか?
私は、その話は、一度書きかけたのですが、#1では、やめたのでした。世の中、いろんな人がいますから、そういう中のひとりが「自分はこうだ」と主張して、こちらの話をうやむやにする人がいます。
「宣言時にゼロ値に自動で初期化される」
デフォルト状態なら、Index(添字)は、必ずゼロからです。
それがすべてなら、なんの問題ももないのですが、そうではないというのは、
配列のIndexの初期値を 1 に設定する、「Option Base 1 」があるという話に発展してきます。
Option Base 1 で、配列のIndexの初期値は、1になります。
個別のコーディングルールを持ち出すのはおこがましいのですが、少なくとも、私の中のルールでは、Option Base 1 や、Option Compare Text も使わないというルールを設けているからです。
http://officetanaka.net/excel/vba/variable/07.htm
田中氏は、相変わらず、すっきりとした説明ではありませんが、、
「配列を返すVBAの命令には、最小の要素が1から始まる配列を返すものもあります」これは、単にオブジェクトを配列として受ける側になれば、配列のように見えるものでも、1から始まるものは、それは配列とは考えない方がよいのではないか、と私は考えています。そして、それはCollectionだと考えます。
Excelで言えば、Sheets, Range は、1からですが、これらは配列ではなく、Collection です。
もうひとつは、私の中では、いつまでも、VBA/VB6の環境が続くのか、という根本的な懸念が含まれているからなのです。他の環境で、Option Base 1 が許されるのか、と考えます。つまり、それは移植性が高いか低いのではないかということです。Option Base 1とかで作られたものを0ベースに直すのは、意外に面倒なものなのです。
どこかのサイトでは、Indexは、0なら、0、1なら1と明示的に宣言してあげなくてはいけないと言っている人がいます。それは、ごもっともだと思います。
でも、それは、つまり、動的配列で一般的にそんなことができるのか、という疑問もあります。
動的配列に、以下のように書く人がいるのかな?
(なんとも不格好なコードだと思います)
Sub Sample2()
Dim ary() As Variant, i As Long, j As Long
Dim LastRow As Long
ReDim ary(1 To 1)
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
j = 1
For i = 1 To LastRow
ary(j) = Cells(i, 1).Value
j = j + 1
If j >= LastRow Then Exit For
ReDim Preserve ary(1 To j)
Next i
For i = LBound(ary) To UBound(ary) '1からですが。
MsgBox TypeName(ary(i)) & ": " & ary(i)
Next i
End Sub
私の話をまとめますと、
・配列の管理は、Index(添字)のほうがやりやすいです。
・動的配列では、最初は、基本的には必ずゼロ・スタートです。
・Option Base は、つかないほうがよい、
ということです。
なお、他には、パラメータ配列やコントロール配列という言葉がありますが、これらは別ものですから、今回には加えません。
再度の、しかも詳細なご回答ありがとうございました。
本回答だけでなく、回答者様のいろいろな質問に対してのご回答はいつもとても参考させていただいております。
質問文にも書きましたとおり、これからは業務においてVBAを扱うことが多くなってきます。
まだまだ理解の足りないところがありますが、精進して行きたいと思います。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- Excel(エクセル) Excelにて、フォルダ内のTextファイルをマクロで統合すると文字化けしてしまう時の解消コード 4 2023/01/01 07:32
- Visual Basic(VBA) マクロVBA 1シートをまとめる 閉じ方 初心者 SOS! 1 2022/06/17 14:54
- Visual Basic(VBA) ExcelVBAに関する質問 3 2023/02/17 10:47
- Visual Basic(VBA) EXCEL VBAにて動的にCheckBOXを複数作成し、同BOXにイベントを追加したい 1 2023/03/16 07:05
- Visual Basic(VBA) VBAでのMATCH関数 3 2022/10/17 19:06
- Visual Basic(VBA) 配列にしたセル範囲でのコメントがあるかどうかを取得するコードの書き方 2 2022/09/17 05:09
- Visual Basic(VBA) 別シートから年齢別の件数をカウントしたいの続き 5 2023/01/24 00:16
- C言語・C++・C# c言語の問題です 課題1 (二分探索木とセット) 大きさ size の配列 array を考える。す 2 2023/01/10 21:08
- Visual Basic(VBA) userformでSheetを選択して開くコード 1 2023/05/15 16:27
- Visual Basic(VBA) Excel-VBAでのファイルの開き方 4 2023/02/14 11:01
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
動的配列が存在(要素が有る)か...
-
C言語 重複しない4ケタの乱数...
-
IF関数でEmpty値を設定する方法。
-
VB.net 引数で配列変数を渡す際...
-
五目並べのプログラムを配列と...
-
EXCEL VBA で、0から?1から?
-
パソコンキーボードで時分秒を...
-
VBAで配列の計算
-
VBで作った乱数を一度も重複さ...
-
C#の質問
-
ジャグ配列とは
-
VBA 配列で重複した単語が格納...
-
配列の要素数を超えた参照のコ...
-
VBSでテキストファイル内の数字...
-
Visual C++ でコントロールを...
-
javaプログラムについて
-
このプログラミングの問題を教...
-
C++、クラスメンバの構造体配列...
-
System.err. printlnとSystem.o...
-
ループ処理の際、最後だけ","を...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
動的配列が存在(要素が有る)か...
-
IF関数でEmpty値を設定する方法。
-
パソコンキーボードで時分秒を...
-
EXCEL VBA で、0から?1から?
-
VBAで配列の計算
-
C言語 重複しない4ケタの乱数...
-
VB.net 引数で配列変数を渡す際...
-
変数を動的に作るには?
-
C#の質問
-
VBでbyte配列型のインスタンス...
-
VBで作った乱数を一度も重複さ...
-
javaプログラムについて
-
配列の要素数を超えた参照のコ...
-
For文と配列
-
複数のテキストボックスに同じ...
-
Excel VBAで配列の途中から(X)M...
-
ラジオボタンのチェックをEnter...
-
遅延バインディングを使用でき...
-
ジャグ配列とは
-
int型配列の一括初期化
おすすめ情報