VBAの勉強を始めてみた

色々試しています。

CPUよもやま話(トランジスタと論理回路)

どんな高水準プログラミング言語で書かれたプログラムであっても最終的には機械語に変換され、電気信号となってCPUで処理されます。CPUの中ではレジスタやALUなどが電気信号を処理しますが、じゃあレジスタやALUはそもそも何で構成されているんでしょうか?

自分の作ったプログラムが電気信号となって回路をどのように駆け巡っているのか想像できれば、プログラムに対する愛着もさらに深まるというものです。

今回は、CPUを構成する最小単位といえる「トランジスタ」について話してみようと思います。

トランジスタはP型半導体とN型半導体を組み合わせて作られた半導体素子です。組み合わせ方によって、PNP型とNPN型がありますが現在はNPN型が主流になっているそうです。

導体や絶縁体なら何となく知ってるけど、半導体って何?美味しいの?(゜ρ゜)っていう人がいるかも知れませんね。

いや、いませんね。

トランジスタ(NPN型)ってこんなのです。

f:id:kouten0430:20190119235319p:plain

通常はコレクターエミッタ間には電流が流れませんが、ベースーエミッタ間に微小の電流を流す(ベースーエミッタ間に電圧を加える)と、コレクターエミッタ間が導通になります。図記号で表現すると下のようになります。

f:id:kouten0430:20190119175333p:plain

上の図で分かりにくい場合は、下のように簡易的に考えても良いと思います。ベースがスイッチをON・OFFしているようなイメージです。

f:id:kouten0430:20190119175356p:plain

こんな感じです。

このように電流を流したり、流さなかったりすることができるものが半導体なのです。

(電子の動きについて説明するとややこしくなるので割愛)

他にも、半導体素子にはダイオードサイリスタ・ツェナーダイオード発光ダイオード・フォトカプラ等がありますが、それぞれが適材適所で活用されています。

このうちトランジスタのスイッチング作用は電子計算機にとって、かなり重要な役割を果たすことになります。

 

専門外の人でも一度ぐらいは見たことがあるかもしれませんが、トランジスタってこんなヤツで、CPUの中にはこれのさらにさらにさらに小さいバージョンが数億個入っているそうです。

 

で、 このトランジスタを使って何ができるのかというと、上手く組み合わせることで論理回路を作ることができるのです。

ANDとかORとかNOTのあれです。

実際にどうやって作るのかやってみましょう。

※デジタル信号に置き換えてイメージしやすいように、電源は1Vで供給されているものとします。1Vが「1」、0Vが「0」です。

 

AND回路の作り方

これはANDの図記号

f:id:kouten0430:20190119171149p:plain

これをトランジスタで作るとこうなります(※少し簡略化しています)。

f:id:kouten0430:20190119175442p:plain

AとBが直列に繋がっているので、AとBに電圧を印加した時だけCに1Vの電圧が現れます。

f:id:kouten0430:20190119181225p:plain

真理値表

0 0 0
1 0 0
0 1 0
1 1 1

 

OR回路の作り方

これはORの図記号

f:id:kouten0430:20190119181911p:plain

これをトランジスタで作るとこうなります(※少し簡略化しています)。

f:id:kouten0430:20190119181947p:plain

AとBが並列に繋がっているので、AまたはBのいずれか(もしくは両方)に電圧を印加した時にCに1Vの電圧が現れます。

f:id:kouten0430:20190119182015p:plain

真理値表

0 0 0
1 0 1
0 1 1
1 1 1

 

NOT回路の作り方

これはNOTの図記号

f:id:kouten0430:20190119182108p:plain

これをトランジスタで作るとこうなります(※少し簡略化しています)。

f:id:kouten0430:20190119182127p:plain

AがOFFの時は抵抗の電圧降下がほぼ無視できるので、Bに1Vが現れます。AがONの時は抵抗の電圧降下によって、Bに0Vが現れます。 

f:id:kouten0430:20190119182351p:plain

真理値表

0 1
1 0

 

電圧降下について

電圧降下という言葉に馴染みがない人のために少し補足します。電圧降下とは回路中に存在する抵抗の両端に電位差が生じる現象のことです。今回登場した回路は、抵抗がひとつに見えますがトランジスタも抵抗のひとつとして計算してみましょう(導通の時は0Ω、非道通の時は∞Ωとして考える)。NOT回路を例にすると、コレクタ側にある抵抗をR1、トランジスタの抵抗をR2とした場合、R1の電圧降下とR2の電圧降下は次のように求めることができます。

f:id:kouten0430:20190120004732p:plain

E=1V、R1=1Ω(適当)とします。

トランジスタが導通(ON)状態であれば、

  • V1=1/(1+0)×1=1V
  • V2=0/(1+0)×1=0V

トランジスタが非導通(OFF)状態であれば、

  • V1=1/(1+∞)×1≒0V
  • V2=∞/(1+∞)×1≒1V

です。

 

 

ブレッドボードなどを使って自分で電子工作してみるのも楽しいかもしれません。

仕組みが何とな~くイメージできれば、もうトランジスタのことは忘れても大丈夫です。これで論理回路の機能を抽象化!(気が向いたらたまに思い出してあげて下さい)

次回以降、CPUの他の構成部品などについても書いていきたいなと思っています。

 

作図に以下のフリー素材を使用させていただきました。

電気用図記号パーツ(EXCELフリー素材) | フリー素材のDigipot

Option Compare Text でマッチする文字列を全部調べてみた(Tips-15)

今回は以下の記事の続きです。 

kouten0430.hatenablog.com

Option Compare Textを宣言して文字列比較を行った場合、文字コードが同じもの以外にどんな文字列が類縁の文字としてマッチするのか・・・・・・。前回は比較サンプルが少なく不十分だったので、今回は文字コード0000~FFFFの全組み合わせを漏れなく調べてみました。
方法は、エクセルシートの1行目と1列目に文字列を配置し、行と列の全組み合わせを比較します(文字コードが制御文字や不使用領域であっても無関係に比較します)。比較の結果がTrueであれば、行と列が交差する位置に○をつけるという寸法です。

コードは後述しますが、文字列の生成~文字列の比較~シートへの結果出力まで、すべてVBAで行います。

f:id:kouten0430:20190112154144p:plain

基本的には、このように右下がりに○が付きますが、表示を縮小していくと○の重複箇所が見えてきます。

f:id:kouten0430:20190112154243p:plain

何がマッチして、何がマッチしないのか・・・・・・。○をフィルタリングすることで確認することが可能になります。4294967296通りの組み合わせを掲載することはできないので、記事内ではいくつか抜粋して紹介することにします。

 

半角の 1

f:id:kouten0430:20190112154654p:plain

半角、上付き、下付き、丸数字、全角にマッチします。

丸数字の違いがわかりづらいのでちょっと拡大してみます。

f:id:kouten0430:20190112154722p:plain

よく見ると二重丸だったり、文字の太さが違ったりしていますね。

 

半角の A

f:id:kouten0430:20190112154826p:plain

半角大文字、半角小文字、下付きの大文字、上付きの大文字、上付きの小文字、下付きの小文字、全角大文字、全角小文字にマッチします。

 

平仮名の あ

f:id:kouten0430:20190112154914p:plain

平仮名、全角片仮名、半角片仮名にマッチします。

 

平仮名の ゑ

f:id:kouten0430:20190112155031p:plain

平仮名、全角片仮名にマッチします。同じ読みの「え」や「エ」にはマッチしません。

 

ローマ数字の Ⅳ

f:id:kouten0430:20190112155110p:plain

ローマ数字の大文字と小文字にマッチします。

 

ギリシャ文字の α

f:id:kouten0430:20190112155215p:plain

ギリシャ文字の大文字と小文字にマッチします。見た目は似ているけどアルファベットのAにはマッチしません(半角の Aで、915行目がマッチしていないことからも分かります)。

 

特殊文字

f:id:kouten0430:20190112155423p:plain

f:id:kouten0430:20190112155437p:plain

同じ文字コードの文字のみマッチします。(電話は白と黒で別物扱いだし、ゆきだるまは他にも数種類あるけれどマッチしていません)

 

漢字

f:id:kouten0430:20190112155546p:plain

f:id:kouten0430:20190112155558p:plain

同じ文字コードと同じ意味(?)の韓国文字がマッチする。これからは덟렗と名乗ることにします(嘘)

 

さて・・・・・・。

こんなことをしても何の得にもならないので、試してみようという酔狂な人はいないと思いますが、コードを掲載しておきます(使い捨てなので雑ですけど)。エクセルシートの列数が足りないので、全部調べる場合は5シートに分けて5つのプロシージャを実行する必要があります。まっさらなシートをアクティブにした状態でプロシージャを実行すると、文字列の生成→文字列の比較→シートへの結果出力までを自動で行います。(結果を見やすくするための罫線の描画やオートフィルターの適用はプログラムに含まれていません)

 

文字コード0000~3FFEまでと0000~FFFFの比較

Option Compare Text
Sub 文字コード3FFEまで()
    Dim 行配列(2 To 65537) As String
    Dim 列配列(2 To 16384) As String
    
    行 = 2
    列 = 2
    
    Application.ScreenUpdating = False
    
    For Unicode = &H0 To &H7FFF
        If Unicode < &H3FFF Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    For Unicode = &H8000 To &HFFFF  'ここからマイナス符号付き
        行配列(行) = ChrW(Unicode)
        行 = 行 + 1
    Next Unicode
    
    Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
    Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
    
    For 行 = 2 To 65537
        For 列 = 2 To 16384
            If 行配列(行) <> "" And 列配列(列) <> "" Then
                If 行配列(行) = 列配列(列) Then
                    Cells(行, 列).Value = "○"
                End If
            End If
        Next 列

        Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
        DoEvents
    Next 行

    Application.StatusBar = False

End Sub

 

 文字コード3FFF~7FFDまでと0000~FFFFの比較

Option Compare Text
Sub 文字コード7FFDまで()
    Dim 行配列(2 To 65537) As String
    Dim 列配列(2 To 16384) As String
    
    行 = 2
    列 = 2
    
    Application.ScreenUpdating = False
    
    For Unicode = &H0 To &H7FFF
        If Unicode >= &H3FFF And Unicode < &H7FFE Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    For Unicode = &H8000 To &HFFFF  'ここからマイナス符号付き
        行配列(行) = ChrW(Unicode)
        行 = 行 + 1
    Next Unicode
    
    Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
    Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
    
    For 行 = 2 To 65537
        For 列 = 2 To 16384
            If 行配列(行) <> "" And 列配列(列) <> "" Then
                If 行配列(行) = 列配列(列) Then
                    Cells(行, 列).Value = "○"
                End If
            End If
        Next 列

        Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
        DoEvents
    Next 行

    Application.StatusBar = False

End Sub

 

 文字コード7FFE~BFFCまでと0000~FFFFの比較

Option Compare Text
Sub 文字コードBFFCまで()
    Dim 行配列(2 To 65537) As String
    Dim 列配列(2 To 16384) As String
    
    行 = 2
    列 = 2
    
    Application.ScreenUpdating = False
    
    For Unicode = &H0 To &H7FFF
        If Unicode >= &H7FFE And Unicode <= &H7FFF Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    For Unicode = &H8000 To &HFFFF  'ここからマイナス符号付き
        If Unicode >= &H8000 And Unicode < &HBFFD Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
    Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
    
    For 行 = 2 To 65537
        For 列 = 2 To 16384
            If 行配列(行) <> "" And 列配列(列) <> "" Then
                If 行配列(行) = 列配列(列) Then
                    Cells(行, 列).Value = "○"
                End If
            End If
        Next 列

        Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
        DoEvents
    Next 行

    Application.StatusBar = False

End Sub

 

 文字コードBFFD~FFFBまでと0000~FFFFの比較

Option Compare Text
Sub 文字コードFFFBまで()
    Dim 行配列(2 To 65537) As String
    Dim 列配列(2 To 16384) As String
    
    行 = 2
    列 = 2
    
    Application.ScreenUpdating = False
    
    For Unicode = &H0 To &H7FFF
        行配列(行) = ChrW(Unicode)
        行 = 行 + 1
    Next Unicode
    
    For Unicode = &H8000 To &HFFFF  'ここからマイナス符号付き
        If Unicode >= &HBFFD And Unicode < &HFFFC Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
    Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
    
    For 行 = 2 To 65537
        For 列 = 2 To 16384
            If 行配列(行) <> "" And 列配列(列) <> "" Then
                If 行配列(行) = 列配列(列) Then
                    Cells(行, 列).Value = "○"
                End If
            End If
        Next 列

        Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
        DoEvents
    Next 行

    Application.StatusBar = False

End Sub

 

文字コードFFFB~FFFFまでと0000~FFFFの比較

Option Compare Text
Sub 文字コードFFFFまで()
    Dim 行配列(2 To 65537) As String
    Dim 列配列(2 To 16384) As String
    
    行 = 2
    列 = 2
    
    Application.ScreenUpdating = False
    
    For Unicode = &H0 To &H7FFF
        行配列(行) = ChrW(Unicode)
        行 = 行 + 1
    Next Unicode
    
    For Unicode = &H8000 To &HFFFF  'ここからマイナス符号付き
        If Unicode >= &HFFFC And Unicode <= &HFFFF Then
            行配列(行) = ChrW(Unicode)
            列配列(列) = ChrW(Unicode)
            行 = 行 + 1
            列 = 列 + 1
        Else
            行配列(行) = ChrW(Unicode)
            行 = 行 + 1
        End If
    Next Unicode
    
    Range(Cells(1, 2), Cells(1, 16384)).Value = 列配列
    Range(Cells(2, 1), Cells(65537, 1)).Value = WorksheetFunction.Transpose(行配列)
    
    For 行 = 2 To 65537
        For 列 = 2 To 16384
            If 行配列(行) <> "" And 列配列(列) <> "" Then
                If 行配列(行) = 列配列(列) Then
                    Cells(行, 列).Value = "○"
                End If
            End If
        Next 列

        Application.StatusBar = "比較中..." & 行 & "/" & "65537行"
        DoEvents
    Next 行

    Application.StatusBar = False

End Sub

 

 注:私のPCでは1プロシージャ20分程度かかります。

オブジェクトはメモリの中でどうなってるの?(Tips-14)

注:今回のお話は VBA にそっくりそのまま適用できるかわかりません。一般的なお話として読んでいただければ嬉しいです。

 

前回、メモリ内部を「オブジェクト」とだけ表現しました。
せっかくなのでメモリ内部をもう少し拡大して観察したいと思います。

f:id:kouten0430:20190103193736p:plain

 

メモリ内は主にスタック領域、メソッドエリア(静的領域)、ヒープ領域に分けて管理されます。

f:id:kouten0430:20190103193534p:plain


クラスをNew する

まず、New するところから始めます。
今回はクラスの実体化という表現はやめて、クラスのインスタンス化という表現にします。(意味は同じです)
New でメモリのヒープ領域に、クラスで定義されたインスタンス変数の容量を確保します。ここで確保された容量をインスタンスと呼びます。
New した数だけ確保(生成)できます。

f:id:kouten0430:20190103194037p:plain

 

この、インスタンスオブジェクトとも呼びます。

場合によっては別のニュアンスで使い分けされることもありますが、本記事の趣旨ではないので割愛します。

(@_@;)「あれ?メソッドとプロパティはどこ行った?」

安心して下さい。
ちゃんとメモリ内のメソッドエリアと呼ばれる場所にロードされます。

f:id:kouten0430:20190103194210p:plain

 

コード情報はインスタンスが複数あっても、1クラスにつき1つしかロードされません。同じクラスの各インスタンスから呼び出されて使いまわされるイメージです。
別のクラスBなどから生成したインスタンスがあっても、そこからはクラスAのコード情報は呼び出せません。逆もまた然りです。

 

変数でインスタンスを参照する

Set 変数 で、インスタンスを代入します。
といってもデータをまるまる代入するのではなく、インスタンスが存在する場所の先頭アドレスを参照します。

f:id:kouten0430:20190103194446p:plain

 

ちなみに図の例では変数をクラスA型で宣言しているので、クラスAから生成したインスタンス以外を参照させようとするとエラーになります。

 

メソッドを呼び出す

変数1から、メソッドを呼び出してみましょう。

今さら言う必要もないかもしれませんが、メソッドを呼び出すときは

とします。

これによって先頭アドレス:XXXXXXXX に格納されたインスタンスが処理対象であることが特定できます。
あわせて、そのインスタンスに紐付けられたメソッドが呼び出されます。(別のクラスBなどに同じ名前のメソッド名があったとしても呼び出されない)

f:id:kouten0430:20190103195010p:plain

 

メソッドが実行され、必要に応じてインスタンスやローカル変数への読み書きも行われます。

f:id:kouten0430:20190103195144p:plain

 

各変数について整理しときましょう。

インスタンス

  • 同じクラス内に限りどこからでもアクセスできる。
  • 参照されている限り存在する。
  • 容量の大きいデータを取り扱うことができる。

ローカル変数

  • メソッド内からのみアクセスできる。
  • メソッドのルーチンが終了したら破棄される。

 

 

メソッドの処理によって何らかの結果が返されます。

f:id:kouten0430:20190103195412p:plain

 

メインコードからはインスタンスに対して直接読み書きができないので、メソッドやプロパティが窓口となってやり取りが行われます。

処理の結果は数値や文字列だったり、別のクラスのインスタンス化だったり、画面上の表示変化だったり、様々なものがあるだろうと思います。
その辺はメソッドがどのように作られているかによって変わります。

 

メソッドが終了したら

スタック領域にあった、引数やローカル変数は破棄されます。
インスタンスへの参照は続いているため、アドレス情報は残されます。(と思われる)

f:id:kouten0430:20190103200135p:plain

 

プロシージャが終了したら

インスタンスへの参照はなくなり、アドレス情報・インスタンス・コード情報は破棄されます。

f:id:kouten0430:20190103200230p:plain

 

最後に

VBAでは特にメインコード以外は Black box に近く、内部を意識しなくてもプログラミングができるようになっています。逆にいうと、それだけ各オブジェクトの機能の抽象度が高く、部品としての独立性が高くなっているんだともいえます。(Workbookオブジェクト、Worksheetオブジェクト、Rangeオブジェクト・・・・・・等々は New すらしなくても使える)

f:id:kouten0430:20190104102853p:plain


 今回はこの Black box 側を強調して描きたかったので、メインコードは標準モジュール内で動いているかのような書き方をしましたが、実際はメインコードもコンパイルソースコード→中間コードに一括変換→機械語に順次変換)されて、静的領域にロードされています。

 

 

と、ここまで偉そうに書きましたが実際に自分の目で内部動作を見たわけではありません。メモリダンプを解析する技術があって、実際に自分の目で見たのならもっと自信を持って書けるんですが。

 

以下の本が参考になります。 

オブジェクト指向でなぜつくるのか 第2版

オブジェクト指向でなぜつくるのか 第2版

 

 

クラスを実体化するってどういうこと?(Tips-13)

クラスを実体化することの比喩として、「たい焼きの型」と「たい焼き」の話がしばしば用いられます。
しかしこの比喩で「なるほど!そうだったのか」と一発で理解できる人は何人いるのでしょうか?
回り道をし、色々な角度から考えを巡らせて理解に至った人が、再び「たい焼きの型」と「たい焼き」の話に戻ってきた時に初めて「なるほど!そうだったのか」と思うのではないでしょうか?
比喩だけで理解できないのは、具体的なことを何も説明していないからです。当然といえば当然です。

 

なので、今回はクラスを実体化することについて、たい焼きの話よりも少しだけ実機寄りの例え話をします。
その後に実機の動作を説明してみたいと思います。

 

~~~ここから例え話~~~
VBA初心者のKさんは汎用的に使用頻度の高いルーチンをメモ帳にたくさん保存していました。
必要に応じて標準モジュールなどにコピペして使っています。
クラスライブラリならぬ、汎用ルーチンライブラリ(メモ帳Ver)といったところです。
メモ帳の中ではプログラムは動作しません。何故なら汎用ルーチンが書かれているだけの単なるテンプレートだからです。

f:id:kouten0430:20181228143250j:plain

 

これを、標準モジュールなどにコピペして初めて動作するプログラムとして実体化されます。

f:id:kouten0430:20181228143317j:plain


メモ帳のテンプレートからコピペしていくつでも実体を作ることができます。

f:id:kouten0430:20181228143417j:plain


あれ・・・・・・?これって「たい焼きの型」から「たい焼き」を作る話に似ているような気がする。
~~~ここまで例え話~~~

 

次は実際にクラスを実体化することの説明です。例え話とは次の点が異なります。

  • クラスは標準モジュールにではなく、メモリの中に実体化される(実体化されたものをオブジェクトと呼ぶ)
  • 汎用ルーチン群を種類別に整理してクラスにまとめることができる(〔汎用ルーチン1・汎用ルーチン2・汎用ルーチン3〕→クラスA、〔汎用ルーチン4・汎用ルーチン5〕→クラスB ・・・・・・のように)。なお、クラスにまとめられた汎用ルーチンはメソッドと呼ぶ。
  • 汎用コードのコピペと違い、オブジェクトの内部は見えない。(内部を見えなくすることで機能を抽象化することができる)
  • オブジェクトの内部は外部から直に操作できない。操作はメソッドやプロパティを窓口にして行う。(不整合なデータを渡してプログラムの挙動がおかしくなるようなことを防ぐ)

 

クラスモジュールで作られたクラスAを例に流れを見てみましょう。

クラスモジュール内でプログラムが動作しないのはメモ帳の場合と同じです。(あくまでテンプレートと考える)

f:id:kouten0430:20181228143529j:plain

 

①標準モジュール側で New クラス名 とすることでクラスからメモリ内にオブジェクトが実体化されます。

f:id:kouten0430:20181228143656j:plain

 

②Set 変数 で、変数がオブジェクトを参照します。

f:id:kouten0430:20181228143746j:plain

 

③変数.メソッド 又は 変数.プロパティのように記述するとオブジェクトの機能を使用することができます。

f:id:kouten0430:20181228143837j:plain

 

④Set 変数 = Nothing でオブジェクトへの参照を解除します。オブジェクトは何処からも参照されなくなったら、メモリ上から破棄されます。

f:id:kouten0430:20181228143911j:plain

 

クラスからは New でいくつでもオブジェクトを作ることができます。 

f:id:kouten0430:20181228150151j:plain

 

外部ライブラリ(クラスライブラリ)などから、クラスを実体化する場合も流れは同じです。

 

さて、たい焼きよりも実機寄りの例え話から始めて、次に実機の流れを説明してみました。イメージは掴みやすかったでしょうか?

今回は大まかな流れだけを説明するために、メモリの中身は「オブジェクト」とだけ表現しました。次回以降、メモリの中でオブジェクトがどうなっているのかちょっとだけ説明してみようと思います。

 

※クラスモジュールでクラスを自作する方法については、thom さんの「クラスモジュール超入門」の記事でとても分かりやすく解説されています。

thom.hateblo.jp

閉じたままのブックからデータを転記するには?(Tips-12)

VBAで、閉じたままのブックからデータを転記する方法は無いかにゃー」
と思っている人のために、今回は ExecuteExcel4Macro メソッドを紹介します。

ExecuteExcel4Macro?

それは何かの最先端技術ですか?

いえ、違います。

新しいバージョンの Excel で、 Excel 4.0 時代のマクロ関数を使うことができるメソッドなのです。
PlayStation3 で PlayStation1 のゲームが遊べるようなものです。(←ぇ)

構文は次のとおりです。

f:id:kouten0430:20181226174201j:plain

 

引数は Excel 4.0 のマクロ関数を等号 (=) なしで指定します。
ゆえに、Excel 4.0 マクロ関数を知らないと使いこなすことができません。

今更、過去のものを覚えてもしょーがないですが、閉じたままのブックからデータを転記できる機能だけでも覚えておいて損はないと思います。

引数は次のとおりです。

  • ExecuteExcel4Macro("'パス[ファイル名]シート名'!R1C1")

 

データを取得するセルはR1C1形式で書く必要があります。

んで、指定できるのは1セルのみです。複数範囲からデータを取得するにはループを回すなどの工夫が必要です。

 

実際にやってみましょう。

まず、1セルのみから。

 

1データ×500ブック分を転記してみる

下のようにA1セルにデータが入ったものを500ブック用意しました。(ファイル名は 1.xlsx ~ 500.xlsx)

f:id:kouten0430:20181226174524j:plain

ExecuteExcel4Macroメソッド を使って、これ(×500ブック)を転記先ブックの A1~A500 に転記させてみましょう。(処理時間の計測もしておきます) 

Sub test1()
    StartTime = Timer

    For i = 1 To 500
        Cells(i, 1) = ExecuteExcel4Macro("'" & ThisWorkbook.Path & "\[" & i & ".xlsx]Sheet1'!R1C1")
    Next i
    
    Debug.Print Timer - StartTime; "秒"
End Sub

 A500までずらーっと転記されました。

f:id:kouten0430:20181226174825j:plain

かかった時間は 

f:id:kouten0430:20181226175119j:plain

1.82秒でした。

 

同じことを、ExecuteExcel4Macro メソッドを使わずにやってみましょう。(1ブックずつ開いて転記する)

Sub test2()
    Application.ScreenUpdating = False

    StartTime = Timer
    
    For i = 1 To 500
        Workbooks.Open Filename:=ThisWorkbook.Path & "\" & i & ".xlsx"
        Cells(i, 1) = Workbooks(i & ".xlsx").Worksheets(1).Cells(1, 1)
        Workbooks(i & ".xlsx").Close savechanges:=False
        DoEvents
    Next i
    
    Debug.Print Timer - StartTime; "秒"
End Sub

かかった時間は

f:id:kouten0430:20181226175348j:plain

174.55秒でした。
さっきの100倍近くかかってしまいました。主にファイルを開いて閉じるのにかかった時間だと推測されます。

 

5データ×500ブック分を転記してみる

次に、下のように A1~E1 にデータが入ったものを500ブック用意しました。(データがさっきの5倍)

f:id:kouten0430:20181226175527j:plain

ExecuteExcel4Macroメソッドを使って、これ(×500ブック)を転記先ブックの A1~E500 に転記させてみましょう。 

Sub test3()
    StartTime = Timer

    For i = 1 To 500
        For j = 1 To 5
            Cells(i, j) = ExecuteExcel4Macro("'" & ThisWorkbook.Path & "\[" & i & ".xlsx]Sheet1'!R1C" & j)
        Next j
    Next i
    
    Debug.Print Timer - StartTime; "秒"
End Sub

E500までずらーっと転記されました。

f:id:kouten0430:20181226175648j:plain

かかった時間は

f:id:kouten0430:20181226175717j:plain
9.59秒でした。

単純に test1 の5倍くらいかかった計算になります。

 

同じことを、ExecuteExcel4Macro メソッドを使わずにやってみましょう。(1ブックずつ開いて転記する)

Sub test4()
    Application.ScreenUpdating = False

    StartTime = Timer
    
    For i = 1 To 500
        Workbooks.Open Filename:=ThisWorkbook.Path & "\" & i & ".xlsx"
        配列 = Workbooks(i & ".xlsx").Worksheets(1).Range("A1:E1")
        Range(Cells(i, 1), Cells(i, 5)) = 配列
        Workbooks(i & ".xlsx").Close savechanges:=False
        DoEvents
    Next i
    
    Debug.Print Timer - StartTime; "秒"
End Sub

かかった時間は

f:id:kouten0430:20181226175908j:plain
168.21秒でした。

test2 とほぼ変わらずです。

ブックを開いた後は、新しいVBA マクロのほうが配列などで効率よく転記できるからですね。(1~5データ程度では違いが出ないことから、いかにファイルのオープン・クローズ処理のみに時間がかかっているか分かります)

 

まとめ

データ100個も追加計測し、処理方法・データ数・時間(秒)をまとめました。

  データ1個 データ5個 データ100個
ブックを閉じたまま 1.82 9.59 179.79
ブックを開く 174.55 168.21 160.18

(すべて500ブックで実施)

 

1ファイル内での転記対象のデータが多くなると、あるポイントでブックを閉じたままの方法とブックを開く方法とで時間が逆転してしまいます。

なので、転記対象のデータ数とブック数を見比べ、状況に応じて速いほうを選択するのが良いかなと思います。

Excel 4.0 マクロがいつまでサポートされるのか分かりませんけど。

語彙的意味に基づく文字列比較とは?(Tips-11)

VBAでは文字列データの比較方法を以下の Option Compare ステートメントで宣言することができます。

 

Option Compare Binary(又は省略)

Option Compare Text

  • システムのロケールによって決定され、語彙的意味に基づいて比較されます。

Option Compare Database

  • Access でのみ使用するので今回は割愛します。

 

ふむふむ。よくわからん。

もう少し具体的に書いてみよう。 

「Binary」では文字コードの数値が同じか否かを比較します。"A" = "A"であれば、41 = 41 でありTrue。"A" = "a"であれば、41 = 61 であり False。といった具合に。


「Text」では文字コードに加えて語彙的意味での比較が行われます。つまり、文字コードが違っても類縁の文字は等しいとみなされるということです。(半角の「1」と全角の「1」、ひらがなの「あ」とカタカナの「ア」など)

じゃあ、「1」は「1」の他にも「一」「壱」「①」「Ⅰ」「ⅰ」などにもマッチするのでしょうか?

?(・_・;?

するような気もするし、しないような気もする・・・・・・。

何がマッチし何がマッチしないのか・・・・・・。ちょっと試してみましょう。

 

下の表に文字列のサンプルをいくつかピックアップしました。縦軸の文字と横軸の文字を VBA 内(Option Compare Text)で比較し、結果(True 又は False)を軸が交差する箇所に出力してあります。

f:id:kouten0430:20181222172632j:plain

f:id:kouten0430:20181222172541j:plain

f:id:kouten0430:20181222172647j:plain

 

サンプルが少ないため若干心もとないですが、比較のルールは大筋で下記のようになっていると思われます。

 

清音

  • ひらがな・カタカナ・全角・半角を区別せずマッチする。濁音・半濁音・捨てかな(カナ)・漢字にはマッチしない。

濁音

  • ひらがな・カタカナ・全角・半角を区別せずマッチする。清音・半濁音・捨てかな(カナ)・漢字にはマッチしない。

半濁音

  • ひらがな・カタカナ・全角・半角を区別せずマッチする。清音・濁音・捨てかな(カナ)・漢字にはマッチしない。

捨てかな(カナ)

  • 捨てかな・捨てカナ・全角・半角を区別せずマッチする。清音・濁音・半濁音・漢字にはマッチしない。

アルファベット

  • 大文字・小文字・全角・半角を区別せずマッチする。機種依存文字の似た文字(分音記号付きなど)にはマッチしない。

機種依存文字

  • 同じ文字の大文字・小文字を区別せずマッチする。同じ意味の文字でも、ギリシャ文字と環境依存文字などはマッチしない。(ギリシャ文字のδ〔デルタ)と環境依存文字の⊿(デルタ〕など)

アラビア数字

  • 全角・半角・丸付き数字(環境依存文字)を区別せずマッチする。漢数字・ローマ数字にはマッチしない。

ローマ数字

  • 大文字・小文字を区別せずマッチする。アラビア数字・漢数字・丸付き数字にはマッチしない。

 

全ての文字の比較を試してみたいところですが、組み合わせが膨大なので今日のところは勘弁してやろう(←ぇ)

 

ちなみに「語彙」とは語の集まりのことなので一文字を示す表現として用いることはできないのですが Microsoft Docs そのような表現が使われていたので使わせてもらいました。

Option Compare ステートメント | Microsoft Docs

Option Compare ステートメント (VBA) | Microsoft Docs

ロケール | Microsoft Docs

Like演算子でのパターンマッチングについて(Tips-10)

VBA ではRegExpクラスを利用すれば正規表現を使用することができます。
が、そこまでしなくとも Like演算子を使って正規表現のようなパターンマッチングをすることもできます。 

覚えることが少ないので初心者にはオススメですし、組み合わせ次第ではそれなりに複雑なパターンマッチングをすることができます。 

  • 文字列 Like 文字パターン

 

文字列が文字パターンと一致すれば True、一致しなければ False が返ります。

以下は文字パターンに使用する記号の一覧です。たったこれだけなので簡単ですね。

  説明
* 0個以上の文字にマッチ あ* → あ あああ あいうえお などにマッチ
? 1文字にマッチ あ? → あい あお あか など あ で始まる2文字にマッチ
# 1数字にマッチ #個 → 1個 2個 など 数字 で始まり 個 で終わる2文字にマッチ
[ ] [ ]内の1文字にマッチ あいう → あ い う の1文字にマッチ
[!] [ ]内の1文字以外にマッチ !あいう → あ い う 以外の1文字にマッチ
[-] [ ]内の指定した範囲の1文字にマッチ [1-9] → 1 2 3 4 5 6 7 8 9 の1文字にマッチ

 

今回は、このうち [ ]  [ ! ] [ - ] について少し補足しようと思います。

勘違いしがちですが、[ ]は2文字以上の単語を指定することはできません。あくまでも[ ]内に列挙された1文字にマッチします。
どういうことかというと、下のコードでは 東京 という単語にではなく、のいずれか1文字から始まるパターンにマッチします。 なので、東京にもマッチするし京都にもマッチします

Sub test1()
    Debug.Print "東京 "; "東京" Like "[東京]*"
    Debug.Print "京都 "; "京都" Like "[東京]*"
End Sub

f:id:kouten0430:20181215172634j:plain

 

,(カンマ)で区切っても2文字以上の単語を指定することはできません。,(カンマ)も1文字として比較対象になるからです。
下のコードでは、東京大阪という2つの単語にではなく、東 京 ,   のいずれか1文字から始まるパターンにマッチします。

Sub test2()
    Debug.Print "東海 "; "東海" Like "[東京,大阪]*"
    Debug.Print "京浜 "; "京浜" Like "[東京,大阪]*"
    Debug.Print ", "; "," Like "[東京,大阪]*"
    Debug.Print "大仏 "; "大仏" Like "[東京,大阪]*"
    Debug.Print "阪神 "; "阪神" Like "[東京,大阪]*"
End Sub

f:id:kouten0430:20181215172730j:plain

 

文字と文字の間に -(ハイフン)を入れると文字の範囲内の1文字にマッチします。
例えば、[1-9]であれば、1~9 のいずれかにマッチし、[A-Z]であれば、A~Zのいずれかにマッチするということです。[9-1]のように降順だとエラーになります。

でも、文字の範囲って何なのさ?と、思うかもしれません。

確かに、分からないまま使用するのは気持ちの悪いものなので、少し具体的にいうと

文字コードの指定範囲(昇順)

ということになります。
下は文字コード表の一部ですが、[1-9]であれば文字コード 31 ~ 39 の範囲内、[A-Z]であれば文字コード 41 ~ 5A の範囲内の1文字と比較します。

f:id:kouten0430:20181215172826j:plain

 

ちょっと実験してみましょう。
[0-o]の範囲であれば、文字コード表の 30 ~ 6F の範囲が True その前後が False になるはずです。 

Chr関数で文字コード 21 ~ 7E を [0-o] と比較して結果を見てみましょう。

Sub test3()
    For i = &H21 To &H7E
        Debug.Print Chr(i); " "; Chr(i) Like "[0-o]"
    Next i
End Sub

f:id:kouten0430:20181215172947j:plain

 

ふむふむ。確かに、文字コードの昇順のようですね。
当然、小文字と大文字は文字コードが違うので区別されています。

これを [!0-o] とすれば、True と False が逆になる訳です。

 

今回、モジュール内の文字列比較方法を省略したので比較方法が Binary でしたが、 比較方法が Text の場合についても書いておこうと思います。その辺は次回以降に。