元資料 4 枚目に、こうあります ── 「これまで習ったプログラムを機能毎に分割し、わかり易く記述する」。 これが構造化プログラミングです。
なぜ分けるのか。元資料は目的を「わかり易く」と書いていますが、 具体的な利点は 4 つあります。
kaijo(5) と書いてあれば、中を読まなくても意味が分かる逆に言えば、1 つの Sub が長くなってきたら、分ける合図です。
元資料 4 枚目は
「連続、順次(sequence)、分岐(branch)、繰り返し(iteration、loop)の構造を明確にし」と
4 つ並べていますが、「連続」と「順次」は同じもの(sequence)の別訳です。
構造化プログラミングの基本構造は3 つです
(E. W. ダイクストラらが 1960 年代に整理したもので、
この 3 つだけであらゆるプログラムが書けることが知られています ──
だから GoTo が要らないのです。第 5 章 §2)。
上から順に実行する。第 1〜4 章
条件で選ぶ。第 5・6 章
条件が続く間くり返す。第 7・8 章
3 つの構造には共通の性質があります ── 入口が 1 つ、出口が 1 つ。
だから箱の中に箱を入れても、外から見れば「1 つの処理」として扱えます。
この性質があるおかげで、プログラムを部品として組み合わせられるのです。
GoTo は、この「出口が 1 つ」を壊してしまいます。
Sub 手続き名(引数,…,引数)
手続きの本体
End Sub
ここまで書いてきた Sub Macro1() と同じものです。
違いは引数(ひきすう)を受け取れることと、
他の手続きから呼び出せることです。
Sub Macro1()
' 見出しを作る仕事を、別の Sub に任せる
Call midashi(1, "科目", "点数")
Call midashi(5, "氏名", "学年")
Cells(2, 1) = "情報学入門": Cells(2, 2) = 88
Cells(6, 1) = "山田": Cells(6, 2) = 2
End Sub
Sub midashi(ByVal gyou As Integer, ByVal a As String, ByVal b As String)
Cells(gyou, 1) = a
Cells(gyou, 2) = b
Cells(gyou, 1).Interior.Color = RGB(0, 63, 140)
Cells(gyou, 1).Font.Color = RGB(255, 255, 255)
Cells(gyou, 2).Interior.Color = RGB(0, 63, 140)
Cells(gyou, 2).Font.Color = RGB(255, 255, 255)
End Sub
6 行の色づけを 2 回書かずに済みました。
見出しの見た目を変えたくなったら、midashi の中を 1 か所直すだけです。
第 1 章の「時間割の表」で同じことを 4 回書いた面倒さを思い出してください ──
これがその解決です。
Function 関数名(引数,…,引数)
関数の本体
End Function
返り値は、関数名に代入する。
元資料 6 枚目の例です。
Function f(ByVal i)
f = 10
End Function
f = 10 の f は変数ではなく関数の名前です。
ここに代入した値が、呼び出した側に返ります。
これは VBA 独特の書き方なので、最初は戸惑うところです。
Sub | Function | |
|---|---|---|
| 値を返すか | 返さない | 返す(関数名に代入) |
| 呼び方 | Call midashi(1, "a", "b") |
x = kaijo(5) と式の中で使う |
| 向いている仕事 | 何かをする(書く・色をつける) | 何かを求める(計算する) |
| マクロとして実行 | できる | できない(値を返す先がない) |
迷ったときの決め方。
「結果を受け取りたいか」で決めます。
x = 何か(…) と書きたいなら Function、
何かをして(…) だけなら Sub。
Excel の関数(SUM、AVERAGE)と同じ感覚です ──
実は Function で書いた関数は、
シートのセルから =kaijo(5) のように呼び出せます(ユーザ定義関数)。
これが VBA の便利なところの 1 つです。
値 n を引数として、階乗 n!(=1×2×…×n)を計算する関数を作り、 n = 1 から 10 までの値を求めよ。
Sub Macro2()
Dim n As Integer
Cells(1, 1) = "n": Cells(1, 2) = "n!"
For n = 1 To 10
Cells(n + 1, 1) = n
Cells(n + 1, 2) = kaijo(n) ' 関数を式の中で使う
Next n
End Sub
Function kaijo(ByVal n As Integer) As Double
Dim i As Integer
Dim k As Double
k = 1
For i = 1 To n
k = k * i
Next i
kaijo = k ' 関数名に代入 = 返り値
End Function
戻り値の型に注意。
上の関数は As Double にしています。
As Integer にすると 8! = 40320 で範囲外エラーになり、
As Long でも 13! = 6,227,020,800 であふれます。
階乗は爆発的に増えるので(第 8 章の倍々貯金より速い)、
Double にしておくのが無難です。
上のプログラムの As Double を As Integer に変えて、
何番目で止まるか確かめてみてください。
Sub Macro3()
Cells(1, 1) = "1〜10 の和": Cells(1, 2) = souwa(10)
Cells(2, 1) = "1〜100 の和": Cells(2, 2) = souwa(100)
Cells(3, 1) = "1〜1000 の和": Cells(3, 2) = souwa(1000)
' 公式 n(n+1)/2 と比べてみる
Cells(5, 1) = "公式 1000×1001/2"
Cells(5, 2) = 1000 * 1001 / 2
Cells(6, 1) = "一致したか"
Cells(6, 2) = IIf(souwa(1000) = 1000 * 1001 / 2, "一致", "不一致")
End Sub
Function souwa(ByVal n As Long) As Long
Dim i As Long
Dim s As Long
s = 0
For i = 1 To n
s = s + i
Next i
souwa = s
End Function
関数にすると「何度でも呼べる」のがはっきりします。
第 7 章では 1〜N の和を求めるたびにプログラムを書き直していました。
関数にしておけば souwa(10)、souwa(1000) と
引数を変えて呼ぶだけです。
なお、この計算には n(n+1)/2 という公式があるので、
本当はループを回す必要がありません ──
プログラムを書く前に、数学で解けないか考えるのも大事な習慣です。
元資料 8〜10 枚目のところです。ここが第 9 章でいちばん重要で、 同時にいちばん間違いやすい部分です。
ByVal ── 値を渡す値の写しを渡します。
呼ばれた側で x を書きかえても、
呼んだ側の a は変わりません。
ByRef ── 参照を渡す箱そのものを渡します。
呼ばれた側で x を書きかえると、
呼んだ側の a も変わります。
元資料 9 枚目の図は、ByRef を「参照を渡す」、
ByVal を「変数を渡す」と説明しています。
しかし ByVal で渡されるのは変数ではなく「値のコピー」です。
「変数を渡す」という言い方は、むしろ ByRef(箱そのものを渡す)の説明に聞こえるので、
取り違えのもとになります。
この教材では ByVal = 値を渡す/ByRef = 参照(箱)を渡すと書き分けました。
VBA では、何も書かないと ByRef になります。
これは他の多くの言語(値渡しが既定)と逆なので、覚えておいてください。
書きかえるつもりがない引数には、必ず ByVal と書くのが安全です ──
そうしておけば、うっかり呼び出し元の値を壊すことがありません。
2 つの変数の値を入れ替える副プログラムを作る。 3 つの整数を入力し、この副プログラムを利用して小さい順に並べ替えて表示する。
値を入れ替えるには、この副プログラムが呼び出し元の変数を書きかえられなければなりません。
つまり ByRef が必須です。
ByVal で書くとどうなるか、実際に見てみましょう。
Sub Macro4()
Dim a As Integer, b As Integer
' ByRef で交換する
a = 3: b = 7
Cells(1, 1) = "ByRef で交換": Cells(1, 2) = "前 a=" & a & " b=" & b
Call swapRef(a, b)
Cells(1, 3) = "後 a=" & a & " b=" & b
Cells(1, 3).Interior.Color = RGB(198, 239, 206)
' ByVal で交換する(つもり)
a = 3: b = 7
Cells(3, 1) = "ByVal で交換": Cells(3, 2) = "前 a=" & a & " b=" & b
Call swapVal(a, b)
Cells(3, 3) = "後 a=" & a & " b=" & b
Cells(3, 3).Interior.Color = RGB(255, 199, 206)
Cells(5, 1) = "ByVal だと入れ替わっていない ── コピーを交換しただけ"
End Sub
Sub swapRef(ByRef x As Integer, ByRef y As Integer)
Dim t As Integer
t = x: x = y: y = t
End Sub
Sub swapVal(ByVal x As Integer, ByVal y As Integer)
Dim t As Integer
t = x: x = y: y = t
End Sub
swapVal の中身は swapRef と 1 文字も違いません。
それでも結果が違うのは、引数の渡し方だけが違うからです。
ByVal の側では、コピーの x と y を
きちんと交換しています ── ただしそれはコピーの中での出来事で、
手続きが終わると消えてしまうのです。
交換の副プログラムができたので、それを部品として使います。 3 つの数を並べるには、隣どうしを比べて、順が逆なら交換するを繰り返せばよい。
Sub Macro5()
Dim a As Integer, b As Integer, c As Integer
a = Cells(1, 2).Value
b = Cells(1, 3).Value
c = Cells(1, 4).Value
' 隣どうしを比べて、逆なら交換する
If a > b Then Call swap2(a, b) ' a が最小になる(a,b の間で)
If a > c Then Call swap2(a, c) ' a が 3 つの最小になる
If b > c Then Call swap2(b, c) ' 残り 2 つを整える
Cells(3, 1) = "小さい順"
Cells(3, 2) = a
Cells(3, 3) = b
Cells(3, 4) = c
Cells(5, 1) = "比較は 3 回。交換は必要なときだけ"
End Sub
Sub swap2(ByRef x As Integer, ByRef y As Integer)
Dim t As Integer
t = x
x = y
y = t
End Sub
3 回の比較で、なぜ必ず並ぶのか。
1 回目と 2 回目で a が 3 つのうち最小になります
(a より小さいものが来たら必ず交換するので)。
残るのは b と c の 2 つだけなので、3 回目で決まります。
この「隣どうしを比べて交換する」やり方をバブルソートといい、
数が増えても同じ考え方で並べられます(第 12 章)。
部品(交換)を作ってから、それを組み合わせて大きな仕事をする ──
これが構造化プログラミングの実際の姿です。
交換に t(一時変数)が必要な理由。
x = y と書いた瞬間に、もとの x の値は失われます。
だから先に t = x で退避しておくのです。
x = y : y = x と書くと、両方とも同じ値になってしまいます ──
上のプログラムで t の行を消して試してみてください。
Sub は何かをする、Function は何かを求めるByVal は値のコピー、ByRef は箱そのもの。
VBA は省略すると ByRefByRef が必須。一時変数を忘れると両方同じ値になるもとにしたのは「情報学入門(VBA)150-2019」全 19 枚です。
| 元資料 | この教材 | 種別 |
|---|---|---|
| 「連続、順次(sequence)、分岐(branch)、繰り返し(iteration、loop)」と 4 つ並べている | 「連続」と「順次」は同じもの(sequence)の別訳。基本構造は 3 つ。 この 3 つだけであらゆるプログラムが書けること(=GoTo が要らない理由)を追記 | 不正確 |
9 枚目の図で ByVal を「変数を渡す」と説明 |
渡すのは値のコピー。「変数を渡す」は ByRef の説明に聞こえて取り違えのもと。 ByVal = 値/ByRef = 参照(箱)と書き分けた | 不正確 |
| VBA は省略すると ByRef になることに触れていない | 他の言語と逆なので明記。書きかえない引数には ByVal を書く習慣を推奨 | 不足 |
Function f(Byval i) の綴り(Byval) |
VBA は大文字小文字を区別しないので動くが、正しくは ByVal | 表記 |
| ByVal で交換しようとするとどうなるかが示されていない | 中身が 1 文字も違わない 2 つの Sub を並べ、結果が違うことを実行して確かめられるようにした | 不足 |
| 階乗の関数の戻り値の型に触れていない | Integer では 8! で、Long でも 13! であふれる。Double にする | 不足 |
| 交換に一時変数が必要な理由が書かれていない | x = y の時点でもとの x が失われる。消して試せるようにした | 不足 |
| Sub と Function の使い分けの指針がない | 比較表を追加(「結果を受け取りたいか」で決める)。
Function はセルから =kaijo(5) と呼べること(ユーザ定義関数)も追記 | 不足 |
| なぜ分割するのかが「わかり易く」の一言 | 4 つの利点として具体化した | 不足 |
元資料で正しかったこと。
Sub 手続き名(引数,…,引数)〜 End Sub と
Function 関数名(引数,…,引数)〜 End Function の書式、
「返り値は、関数名に代入する」という説明(VBA 独特で、ここを外すと動きません)、
ByRef が「参照を渡す」という説明、
そして「2 つの変数を入れ替える副プログラムを作り、それを使って 3 つの整数を並べ替える」という
課題の設計(部品を作ってから組み合わせる)は、いずれも的確です。