TL;DR —
CellsはRangeを数値でアドレス指定したものです。Range("A1")は、 読む順(列の文字、次に行番号)どおりの文字列でセルを指します。Cells(row, column)は、 Excel が格納する順 — まず行、次に列 — の 2 つの整数で 1 つのセルを指すので、Cells(1, 2)は A2 ではなく B1 になります。どちらの座標も計算できる数値なので、Cellsはループのために作られた参照です。名前で打ち込む範囲にはRangeを、計算で求める 位置にはCellsを使い、計算した角からブロックを組むにはRange(Cells(1, 1), Cells(n, k))を 使いましょう。
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Data")
' Range は固定の範囲を名指しし、Cells は位置を行優先で計算する。
ws.Range("A1").Value = "Header" ' 人間向けの書き方 - 打ち込んだアドレス
ws.Cells(2, 1).Value = "First row" ' 行 2、列 1 = A2(B2 ではない)
セルを参照することは、どんなマクロも最初に行う作業であり、Cells と Range は誰もが最初に
つまずく場所でもあります。混乱の原因は、どちらが優れているかではありません — 両者はセルの
アドレス指定の仕方が違い、その順番を取り違えると、無言で間違った場所に書き込んでしまうの
です。本記事は、残りを予測可能にする 1 つの考えを軸にしています。Cells は Range の数値版
であり、その引数は行が先に来る。 これを押さえれば、ループのパターンも、向きが入れ替わるバグ
も、どちらも腑に落ちます。
この記事で学べること
- 考え方の軸 —
CellsはRangeを文字列ではなく 2 つの数値でアドレス指定したもの - 最も大事なルール —
Cells(row, column)なので、Cells(1, 2)は A2 ではなく B1 Range(Cells(...), Cells(...))で計算した角からブロックを組む- 添字のない素の
Cellsがシート上のすべてのセルを意味する理由 - 既存の範囲に対する
rng.Cells(i)での添字づけと、その行優先の順序 RangeとCellsのどちらに手を伸ばすか — そしてなぜループで使ってはいけないのか
考え方の軸:Cells は Range を数値でアドレス指定する
セル参照は 1 つの問い — どのセル? — に答えなければならず、VBA はその答え方を 2 つ用意して
います。Range("A1") は、スプレッドシート記法の文字列で答えます。
まず列の文字、次に行番号 — ちょうど目でグリッドをたどる順です。Cells(1, 1) は2 つの整数
で答えます。まず行、次に列です。
ws.Range("B3").Value = 10 ' 文字列アドレス: 列 B、行 3
ws.Cells(3, 2).Value = 10 ' 同じセルを数値で: 行 3、列 2
どちらも同じセルを指します。効いてくる違いは、そのアドレスで何ができるかです。"B3" のような
文字列は固定のテキストですが、Cells(3, 2) は計算できる 2 つの数値です。それこそが Cells
の存在理由のすべてです — カウンター、UBound、あるいは実行時に見つけた行から動かせる参照な
のです。Range は名前で分かっている範囲のため、Cells はコードの中で割り出す位置のための
ものです。
最も大事なルール:Cells は (row, column) なので Cells(1, 2) は B1
"A2" と打ち込むことに慣れた人が、誰もつまずくのがこの 1 行です。引数はまず行、次に列 —
横より先に縦 — で、シート上で読む「文字、次に数字」の順とは逆です。
ws.Cells(1, 2).Value = "here" ' 行 1、列 2 => B1(A2 ではない)
ws.Cells(2, 1).Value = "there" ' 行 2、列 1 => A2
「A が 1 列目で 2 が行だから」Cells(1, 2) は A2 のはず、と思ったなら、それはグリッドの順で
読んでいます。VBA は格納の順で読みます。確実な覚え方は R が先、次に C — Row(行)、そして
Column(列)の順で、R1C1 記法や行列の添字と同じです。ループの中でこれを逆にすると、書き込みは
すべて 1 つの軸だけずれ、シート上で行と列が入れ替わり、警告するエラーも出ません。迷ったら
毎回、「行 r、列 c」と声に出して読みましょう。
列の引数は、文字列としての列文字も受け取ります。列は分かっているけれど行を計算する場合に 便利です。
ws.Cells(lastRow + 1, "C").Value = total ' 行は計算、列 C は既知
計算した角からブロックを組む:Range(Cells, Cells)
Cells は 1 つのセルをアドレス指定するので、それだけではブロックを表せません。動的な範囲を
可能にする定番イディオムは、2 つの Cells 呼び出しを Range の角として渡す ことです。
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row ' A 列の一番下
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' 計算した 2 つの角からデータブロック全体を組む:
Dim data As Range
Set data = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)) ' A1 から最後のセルまで
これが Cells を学ぶ見返りです。Range("A1") 単体では、文字列を継ぎ合わせる("A" & lastRow)
ことなしに計算した右下の角を扱えず、しかもそれは列が Z を越えた瞬間に壊れます。
Range(Cells(1, 1), Cells(lastRow, lastCol)) は数値のままで、データの幅がどれだけあっても
気にしません。ここから先は、Resize で参照の形を変えるか、
CurrentRegion で Excel にブロックを見つけさせることができます。
素の Cells はシート全体を意味する
添字のない Cells は、エラーでも 1 つのセルでもありません — ワークシート全体のセル
コレクションです。これはシート全体への操作に実際に役立ちます。
ws.Cells.ClearContents ' シート上のすべての値を消去する
ws.Cells.Font.Name = "Calibri" ' フォントを一度に全体へ設定する
しかしこれは罠でもあります。特定の 1 つのセルを意図した場所で素の Cells を使うと、
1,048,576 × 16,384 のすべてのセルに静かに作用します。「1 つのセルを書式設定する」はずの
マクロがシート全体を再書式設定するなら、添字を失った Cells を探しましょう。安全な習慣は、
本当に全部を意味するのでない限り、Cells には必ず (row, column) を与えることです。
範囲の中の Cells と、線形の添字
Cells はワークシートのプロパティだけではありません — 範囲に対して相対的にも働き、そこ
では (1, 1) は A1 ではなく、その範囲自身の左上の角になります。
Dim blk As Range: Set blk = ws.Range("C5:F20")
blk.Cells(1, 1).Value = "top-left of the block" ' A1 ではなく C5
単一添字の形 Cells(i) もあり、これは**行優先(row-major)**の順でセルをたどります —
まず行を左から右へ、それから次の行へ下ります。
blk.Cells(1).Value ' C5 (最初のセル)
blk.Cells(5).Value ' C6 (幅 4 のブロックを 4 つ進み、次の行へ折り返す)
ここで意外なのは方向です。Cells(i) は下より先に横へ数えるので、4 列のブロックでは
5 番目のセルは 5 行目ではなく 2 行目の先頭です。線形にたどっていて変な場所へ飛ぶように
見えるなら、それはほぼ必ず、列優先を期待しているのに実際は行優先だから、です。
範囲には Range、位置には Cells — そしてループで回さない
すっきりしたルールは「どちらか一方を選べ」ではありません。名前で指す範囲には Range、
計算で求める位置には Cells です。固定のレポート領域、名前付きのブロック、一度だけ打ち込む
見出し行 — それは Range("A1:D1") です。行や列を実行時に計算するセル — それは Cells(r, c)
です。両者は組み合わさります。Range(Cells(...), Cells(...)) は、計算した角から名前付きのような
形のブロックを組みます。
避けたい唯一のことは、範囲全体への操作があるのに、1 セルずつのループの中で Cells に
手を伸ばすことです。For r = 2 To lastRow: ws.Cells(r, 3).Value = ... と書くと、行ごとに 1 回
ワークシートに触れることになり、マクロが遅くなる第 1 位の原因になります。成熟したやり方は、
Cells で角を見つけ、Range を組み、ブロック全体に一度で作用させることです — 配列を
Range(...).Value に代入するか、ネイティブのメソッドを呼びます(
WorksheetFunction を参照)。Cells は作業を見つけるための
道具であって、1 単位ずつ実行するための道具ではありません。
ExcelMaster の活用
Cells と Range は、見た目以上に多くの判断を隠しています。どちらの引数が行なのか、素の
Cells がたった今シート全体に触れてしまったのか、線形の添字は横に進むのか下に進むのか、
そしてループは範囲全体への 1 回の書き込みであるべきだったのか。どの選択を間違えても静かです
— 1 つの軸だけずれた値、シート全体の再書式設定、遅くて仕方のないマクロ。
ExcelMaster なら、代わりに結果を
説明するだけで済みます。「C 列に、データの最終行まで累計を書き込んで」と言えば、最終行を
見つけ、Range(Cells(2, 3), Cells(lastRow, 3)) を組み、値を 1 回のパスで書き込みます — 行が先の
引数は正しく、うっかりシート全体を指す Cells もなく、1 セルずつのループもありません。ブックも
コードもあなたの手元に残り、書き込みが行と列が入れ替わって着地する、あの一幕を飛ばせます。
よくある質問
Excel VBA で Cells と Range の違いは何ですか?
どちらもセルを指しますが、アドレス指定の仕方が違います。Range("A1") はスプレッドシート記法の
文字列 — 列の文字、次に行 — を使います。Cells(1, 1) は2 つの数値 — 行、次に列 — を
使います。Cells の引数は計算できる数値なので、Cells はループの中や実行時に割り出す位置に
使い、Range は名前で打ち込む固定の範囲や名前付きの範囲に使います。両者は
Range(Cells(r1, c1), Cells(r2, c2)) として組み合わさり、計算した角からブロックを作ります。
なぜ Cells(1, 2) は A2 ではなく B1 なのですか?
Cells は引数をまず行、次に列の順で取るからです — グリッドで読む「文字、次に数字」の順とは
逆です。Cells(1, 2) は行 1、列 2 を意味し、これは B1 です。Cells(2, 1) は行 2、列 1 を
意味し、これは A2 です。「R が先、次に C」(行、次に列)と覚えましょう。R1C1 記法と同じ順です。
VBA で Cells を使ってセル範囲を選択するには?
2 つの Cells 呼び出しを Range の角として渡します。Range(Cells(1, 1), Cells(10, 4)) は
A1:D10 です。これは角を計算で求めるときにブロックを組む標準的な方法で、たとえば
Range(Cells(2, 1), Cells(lastRow, lastCol)) のように書きます。参照がたまたまアクティブな
シートを指さないよう、Cells と Range の両方を同じワークシートで修飾しましょう。
VBA で引数なしの Cells は何をしますか?
素の Cells(添字なし)は、ワークシート上のすべてのセルを指します。Cells.ClearContents
はシート全体を消去し、Cells.Font.Size = 11 はすべてのセルに設定します。これはシート全体の
変更に役立ちますが、同時に、(row, column) の添字を失った Cells はシート全体に静かに作用する
ということでもあります — なので、特定のセルを意味するときは必ず添字を付けましょう。
Cells で数値の代わりに列文字を使えますか?
はい。列の引数は文字列を受け取るので、Cells(2, "B") は Cells(2, 2)(セル B2)と同じです。
行の引数は数値でなければなりません。列は分かっているけれど行を計算する場合、たとえば
Cells(lastRow + 1, "C") のように、文字を使うと便利です。
検証環境
検証環境: Excel 365(Windows 11)、VBA 7.1 — 最終確認 2026-08-15。
関連ガイド: VBA Range · VBA Resize · VBA CurrentRegion · VBA Offset · VBA For Loop
