連載

改訂版 プロフェッショナルVB.NETプログラミング

Visual Basic .NET 2003で拡張された機能

株式会社ピーデー 川俣 晶
2004/10/07
Page1 Page2


 本記事は、(株)技術評論社が発行する書籍『VB6プログラマーのための入門 Visual Basic .NET 独習講座』の一部分を許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。

 VB.NETは、2002年3月にリリースされた「Visual Studio .NET 2002」の機能の1つという位置づけであった。これは「Visual Basic .NET 2002」とも呼ばれる。これをバージョンアップした製品として、2003年4月に「Visual Studio .NET 2003」がリリースされた。VB.NETも、この一部として「Visual Basic .NET 2003」に名前が変わり、機能が強化されている。本質的に大きな変更はないが、ループ変数の宣言など、よく使われる機能に対する機能拡張があるので、それを把握しておく必要があるだろう。

ループ内でのループ変数の宣言

 C++/Java/C#などのプログラム言語では、forループ内で変数を宣言することができる。これは便利なものであるだけでなく、変数が宣言されてから使用されるまでの距離を短くするという観点からも好ましいことである。これに相当する機能は、これまでのVisual Basicにはなく、Option Explicit利用時にForステートメントを使用する場合には、常にループ変数を宣言するDimステートメントが必要とされていた。しかし、VB.NET 2003では、この機能が取り込まれた。ForステートメントとFor Eachステートメントで、ステートメント内で変数宣言を行うことができる。以下はそれを使用した例である。

1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
2:   Dim a(9) As Integer
3:   For i As Integer = 0 To 9
4:     a(i) = i
5:   Next
6:   For Each j As Integer In a
7:     Trace.WriteLine(j)
8:   Next
9: End Sub
リストA-1 For、For Eachステートメント内で変数宣言を行ったプログラム

 これを実行すると以下のようになる。

 1: 0
 2: 1
 3: 2
 4: 3
 5: 4
 6: 5
 7: 6
 8: 7
 9: 8
10: 9
リストA-2 リストA-1の実行結果

 ここでポイントになるのは、ソース3行目と6行目のAs Integerである。このように、For、For Eachステートメント内に記述するループ変数名に、Asキーワードに続けて型についての情報を記述すれば、そこで変数宣言も行われたことになる。

 このように宣言された変数のスコープは、For、For Eachステートメント内である。このサンプル・ソースの場合、変数iが有効なのは3〜5行目のみ。変数jが有効なのは、6〜8行目のみである。

 この機能は、プログラムを作成する上で必須の機能というわけではない。従来のように、Dimステートメントでループ変数を宣言してからFor、For Eachステートメントを使用しても、同じ結果は得られた。しかし、完全に役割が同じかというと、そうではない。これは、ただ単に打ち込む文字数を減らして横着をするための機能ではない。変数が宣言されてから使用されるまでの距離を短くしたり、ループ変数が使用できる範囲をループ内に限るといった特徴もある。これらの特徴を活かすために、ぜひこの機能を活用していきたいものである。

シフト演算子

 デジタル・データは、すべて、ビットという情報の組み合わせでできている。ビットは、0か1のいずれかの値を持つものである。しかし、通常のプログラム開発でビットを意識することはあまりない。大抵は、整数、文字列、オブジェクトなどを意識するだけで十分である。それにも関わらず、まれにビットを意識する処理が必要とされる。たとえば、イベントの付加情報を得るで使用した、VB 6のMouseMoveイベントがその例である。引数のButtonとShiftは、ビットを意識した処理を行わねば、適切な値を取り出すことができない。Visual Basicも、このような用途でのビット処理の機能(And演算子など)を備えており、必要があればそれを使うことができた。しかし、C言語などと比較したときに、1つだけVisual Basicに欠けていたものがあった。それがシフト演算子である(資料によっては、ビットシフト演算子と表記しているものもある。本連載では、Visual Basic .NET言語仕様の表記に従っている)。

 シフト演算子は、整数をビットの並びと見なしたとき、それを右または左方向にずらす、という効能を持つ。たとえば、00100というビットの並びがあったとき、右に1ビットシフトすると、00010という並びになる。逆に、左に1ビットシフトすると、01000という並びになる。このような、0と1の並びを意識した移動を確認するために、以下のサンプル・ソース(リストA-3)では、ToBinaryという関数を用意している。これは、渡されたShort型の整数を16個のビットの並びと見なし、0または1の文字が16個の並ぶ文字列として返す機能を持つ。この関数そのものを実現するために、シフト演算子が活用されているので、その点はあとで解説を行っている。なお、10進数による表記も、理解のために意味があるケースがあるので、ビットの並びのほかに、サンプル・ソースでは10進数による表示も行っている。

 1: Private Function ToBinary(ByVal value As Short) As String
 2:   Dim sb As New System.Text.StringBuilder
 3:   Dim mask As Integer = &H8000
 4:   For i As Integer = 0 To 15
 5:     If (value And mask) <> 0 Then
 6:       sb.Append("1"c)
 7:     Else
 8:       sb.Append("0"c)
 9:     End If
10:     mask >>= 1
11:   Next
12:   Return sb.ToString()
13: End Function
14:
15: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
16:   Trace.WriteLine("&H4444S >> 1")
17:   Dim a1 As Short = &H4444S
18:   Dim a2 As Short = a1 >> 1
19:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(a1), a1))
20:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(a2), a2))
21:
22:   Trace.WriteLine("&H4444S >> 2")
23:   Dim b1 As Short = &H4444S
24:   Dim b2 As Short = b1 >> 2
25:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(b1), b1))
26:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(b2), b2))
27:
28:   Trace.WriteLine("&H1S >> 3")
29:   Dim c1 As Short = &H4444S
30:   Dim c2 As Short = c1 >> 3
31:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(c1), c1))
32:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(c2), c2))
33:
34:   Trace.WriteLine("&H8888S >> 1")
35:   Dim d1 As Short = &H8888S
36:   Dim d2 As Short = d1 >> 1
37:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(d1), d1))
38:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(d2), d2))
39:
40:   Trace.WriteLine("&H8888S \ 2")
41:   Dim e1 As Short = &H8888S
42:   Dim e2 As Short = e1 \ 2
43:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(e1), e1))
44:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(e2), e2))
45:
46:   Trace.WriteLine("&H1111S << 1")
47:   Dim f1 As Short = &H1111S
48:   Dim f2 As Short = f1 << 1
49:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(f1), f1))
50:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(f2), f2))
51:
52:   Trace.WriteLine("&H1111S << 3")
53:   Dim g1 As Short = &H1111S
54:   Dim g2 As Short = g1 << 3
55:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(g1), g1))
56:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(g2), g2))
57:
58:   Trace.WriteLine("&H1111S << 17")
59:   Dim h1 As Short = &H1111S
60:   Dim h2 As Short = h1 << 17
61:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(h1), h1))
62:   Trace.WriteLine(String.Format("{0} {1}", ToBinary(h2), h2))
63: End Sub
リストA-3 シフト演算子を使用したプログラム

 これを実行すると以下のようになる。

 1: &H4444S >> 1
 2: 0100010001000100 17476
 3: 0010001000100010 8738
 4: &H4444S >> 2
 5: 0100010001000100 17476
 6: 0001000100010001 4369
 7: &H1S >> 3
 8: 0100010001000100 17476
 9: 0000100010001000 2184
10: &H8888S >> 1
11: 1000100010001000 -30584
12: 1100010001000100 -15292
13: &H8888S \ 2
14: 1000100010001000 -30584
15: 1100010001000100 -15292
16: &H1111S << 1
17: 0001000100010001 4369
18: 0010001000100010 8738
19: &H1111S << 3
20: 0001000100010001 4369
21: 1000100010001000 -30584
22: &H1111S << 17
23: 0001000100010001 4369
24: 0010001000100010 8738
リストA-4 リストA-3の実行結果

 さまざまなバリエーションを揃えたので長くなっているが、個々の処理は単純である。順番に見ていこう。

 まず、シフト演算子には、右シフトと左シフトの2種類がある。右シフトは「>>」と、不等号を2つ並べた名前を使う。左シフトは、逆向きの「<<」を使う。この演算子の左側は、シフトする対象となる値。右側には何ビットシフトするかを示す値を入れる。

 10行目の“mask >>= 1”という式でわかるとおり、シフト演算は、複合代入ステートメントとして記述することもできる。たとえば、以下のような値を持つ変数maskについて、この演算を行った場合、

&h8000(00000000000000001000000000000000)
&h4000(00000000000000000100000000000000)

のように変数内の値が変化する。

 さて、結果を順番に見ていこう。

 ソース18行目のa1 >> 1によって、ビット配列が右に1つずつずれている(リストA-4の2行目と3行目)。

 ソース23行目のb1 >> 2は、2ビットシフトである(同、5行目と6行目)。位置が2つ右方向にずれていることがわかるだろう。

 ソース30行目のc1 >> 3は、同様に3ビット右シフトである(同、8行目と9行目)。ここで注意して欲しいのは、右から3個目にあった1が、シフトの結果、この範囲から飛び出して消滅してしまったことである。シフトは、追い出されたビットが消えてなくなるという効能を持つ。

 ソース35行目のd1 >> 1の実行結果では、最も左側の2個のビットに注目していただきたい(同、11行目と12行目)。1ビット右にずれるとすれば、10が01になるべきだが、実行結果では11になっている。これは、整数の正負の情報を維持するような前提で決められている仕様である。このことは、実行結果の表示に併記した10進数を見るとわかる。右シフトの例は、すべて、正の数はいくらシフトしても正。負の数はいくらシフトしても負の状態が維持されていることがわかるだろう。このように、正負の状態が維持されるのは、最も左側のビットを特別扱いしているためである。右シフトを行う場合、最も右のビットの値は変化することはなく、その値が、順次右にずれていく。つまり「1000000000000000」を1回右シフトすると「1100000000000000」、2回右シフトすると「1110000000000000」、15回右シフトすると「1111111111111111」になるということである。

 さて、この動作は、2のべき乗で割ることに等しい。右に1つシフトすることは、2で割ることに、右に2つシフトすることは4で割ることに、右に3つシフトすることは8で割ることに等しい。そのことを確認するために、ソース42行目に、36行目と同じ結果が得られる整数の割り算を記述してみたので、参考にしていただきたい(実行結果は、リストA-4の14行目と15行目)。

 余談だが、かけ算や割り算をシフトに置き換えるのは、コンパイラが行う最適化の定石の1つである。これは、かけ算や割り算よりも、シフトのほうが高速に実行できるCPUが多いためである。かけ算や割り算をシフトで行うケースがあれば、逆に、シフトをかけ算や割り算で行うケースもある。シフト演算子のないプログラム言語を使う場合、シフトをかけ算や割り算で実行する場合もある。たとえば、シフト演算子のないVB.NET 2002以前のVisual Basicで、どうしてもシフト演算を行いたい場合に、かけ算や割り算で代用するテクニックもあった。しかし、VB.NET 2003では、そのようなアクロバット的テクニックは不要である。

 実行結果の14行目からは、左シフトの例である。ソース48行目のf1 << 1によって、左に1つずつビットがずれたわけである(同、17行目と18行目)。

 ソース54行目のg1 << 3によって、3ビット左シフトである(同、20行目と21行目)。ここで注意すべきことは、最も左側のビットが特別扱いされていないことである。素直に、もともと1であったビットがそこに入り込んでいる。右シフトと異なり、左シフトでは、最も左側のビットを特別扱いしていない。

 ソース60行目のh1 << 17は、はみ出すシフトを指定した例である(実行結果は、同23行目と24行目)。ここで使用しているのはビットが16個しかないShortであるから、17回シフトすれば、すべてのビットは範囲外に出て行ってしまい、残ったビットはすべて0になるはずである。しかし、結果を見ると、すべてゼロにはなっていない。1回左シフトした場合と結果は同じである。このようになる理由は、シフトする回数にある式を当てはめて計算しているためである。今回のプログラムの場合、実際には“h1 << (17 And 15)”として計算されている。ここで突然出現した15という数字は、Short型に対応するものである。Byte型なら7、Integer型なら31、Long型なら63になる。これらの数字は、こうして見ると意図がわかりにくいが、効能は明らかである。要するに、はみ出す分は切り落として無視する、ということである。たとえば、Short型を17回シフトさせようとした場合、これはShort型のビット数16個を超えているので、超えた分(ここでは16)を取り除く。すると、17引く16で残りは1である。あるいは、ビット数で割った余りと理解してもよい。17を16で割った余りは1になるので、実際には1回だけ左シフトが行われるわけである。

 なお、シフト演算ではオーバーフローの例外が発生していないことも確認していただきたい。たとえ、どれだけのビットが溢れても、それはオーバーフローとしては扱われない。この点で、かけ算、割り算とシフトは、決定的に違う挙動を示している。


 INDEX
  [連載] 改訂版 プロフェッショナルVB.NETプログラミング
  Visual Basic .NET 2003で拡張された機能
  1.ループ内でのループ変数の宣言/シフト演算子
    2.Newキーワードで引数を指定しない場合の括弧
 
「改訂版 プロフェッショナルVB.NETプログラミング 」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH