VBAでセルを指定するとき、RangeとCellsのどちらで書けばいいか迷いますよね。結論から言うと、どちらも返すのは同じRangeオブジェクトで、違うのは指定の仕方だけです。場所が決まっていればRange、行や列を変数で動かすならCells。
範囲を数値で組み立てるときは、両方を合わせた Range(Cells, Cells) を使います。この3つを押さえておけば、ほとんどの場面で迷わなくなりますよ。
ただ、書き分けがあいまいなままだと、別シートを操作するときに困ります。シート名をつけたはずなのに、実行時エラー1004で止まってしまう。しかも表示しているシート次第で、同じコードが動いたり止まったりすることもあります。
この記事では、Excel VBAのRangeとCellsの違いを7項目の比較表で整理します。そのうえで、どちらで書くかを3つの質問で決める方法を紹介しましょう。Range(Cells, Cells) で範囲を作る書き方と、1004エラーの直し方もコード例つきで解説します。
対応バージョン: Windows版のデスクトップExcel(Microsoft 365 など)。Web版ExcelではVBAマクロを作成・実行できません。
NOTE
VBAを初めて触る方は、先に「VBEの画面の見方」でコードを書く画面の開き方を確認しておくとスムーズです。
VBAのRangeとCellsの違い:結論と比較表
RangeもCellsも、セルを指定して「Rangeオブジェクト」を返す点は同じです。Rangeオブジェクトとは、セルやセル範囲をVBAで扱うための入れ物のこと。取り出したあとは .Value や .ClearContents など、同じ命令がそのまま使えます。
違うのは「どう指定するか」だけ。Rangeは "A1" のような番地の文字列で、Cellsは行番号と列番号の数値で指定します。
Sub 同じセルを2通りで指定する()
Range("B3").Value = 100 ' 番地で指定
Cells(3, 2).Value = 100 ' 3行目・2列目で指定(同じB3)
Debug.Print Range("B3").Address ' → $B$3
Debug.Print Cells(3, 2).Address ' → $B$3
End Sub
Debug.Print の結果は、VBEのイミディエイトウィンドウに表示されます。どちらも $B$3 が返るので、指している場所はまったく同じです。
7項目の比較表
| 比較項目 | Range | Cells |
|---|---|---|
| 引数の形 | 番地の文字列 "A1" "A1:C10"/名前 "税率"/セル2つ Range(Cells(…), Cells(…)) | 行番号・列番号 Cells(1, 1)/列は文字も可 Cells(1, "A") |
| 戻り値 | Rangeオブジェクト | Rangeオブジェクト(同じ) |
| 一度に指定できる範囲 | 複数セルの範囲を直接書ける | 引数あり=1セル/引数なし=シートの全セル |
| 名前付き範囲 | 使える | 使えない |
| 変数との相性 | 文字列の結合が要る(列の変数化は手間) | 数値をそのまま渡せる |
| 読みやすさ | 番地が見えるので一目でわかる | 列番号を頭の中で列文字に置き換える |
| シートを書かないときの参照先 | 標準モジュールではアクティブシート | 同左 |
いちばん大事なのは「戻り値」の行です。結果は同じRangeオブジェクトなので、正解・不正解はありません。場面によって書きやすさが変わるから迷うんです。
「Cellsは1セルしか指定できない」という説明は、引数ありの場合の話。引数なしの Cells はシートの全セルを表します。Worksheets("作業用").Cells.ClearContents と書くとシート全体の値が消えるので、注意しましょう。
表の最終行「シートを書かないときの参照先」は、1004エラーの原因に直結します。詳しくは3章で取り上げますね。
迷ったら3つの質問で決める
どちらで書くか迷ったら、次の3つを上から順に確認してみてください。最初に「はい」になったものを選べばOKです。
- 名前付き範囲を使う? またはセルの場所が固定? → Range
- 行や列を変数で動かす? → Cells
- 範囲の始点・終点を数値で作る? → Range(Cells, Cells)
どれにも当てはまらないのが、「基準セルから2行下」「範囲を3行広げる」といった指定です。これはRange単体のOffset・Resizeの出番。表全体をつかむCurrentRegionや、1セルずつ回すFor Eachも含めて「VBA Rangeの使い方」で解説しています。
ループで書き比べる:Range(“A” & i) と Cells(i, 1)
違いがいちばんはっきり出るのがループです。同じ連番の入力を、RangeとCellsで書き比べてみましょう。
Sub 連番を入れる()
Dim i As Long
For i = 2 To 6
Range("A" & i).Value = i - 1 ' "A2" "A3"…と文字列を組み立てる
Cells(i, 2).Value = i - 1 ' 行番号 i をそのまま渡す
Next i
End Sub
' 実行後: A2:A6 と B2:B6 に 1〜5 が入る
行だけを動かすなら、手間はほとんど変わりません。Range("A" & i) も正しいコードで、番地の形が見える分だけ読みやすいくらいです。
差が出るのは、列も変数にしたいとき。Cellsなら Cells(r, c) と数値を2つ渡すだけ。Rangeでは、列番号の3を「C」に変換する処理が要ります。行も列も動くならCellsと覚えておきましょう。
For文そのものの書き方は「VBA For~Next文の使い方」を参考にしてみてください。
Range(Cells, Cells)で動的な範囲を作る
RangeとCellsは組み合わせて使えます。書き方は Range(Cells(開始行, 開始列), Cells(終了行, 終了列)) です。2つのセルを左上と右下にした範囲になります。
始点も終点も数値で決められるので、データの件数に合わせて範囲を伸び縮みさせられるのが強み。「範囲を数値で作る」場面は、この書き方が基本になります。
基本の書き方
Sub 範囲を数値で作る()
Debug.Print Range(Cells(1, 1), Cells(6, 3)).Address ' → $A$1:$C$6
Debug.Print Range("A1", "C6").Address ' → $A$1:$C$6(番地でも同じ)
End Sub
やっていることは、Rangeの引数に「1セルを表すRange」を2つ渡しているだけ。番地で書く Range("A1", "C6") を、数値で書き直した形です。
最終行・最終列と組み合わせる
実務でいちばん使うのは、最終行を求めてから範囲を作るパターンです。ここからは、次のような「売上」シートを例に進めます。
| A | B | C | |
|---|---|---|---|
| 1 | 日付 | 担当 | 金額 |
| 2 | 10/1 | 佐藤 | 12,000 |
| 3 | 10/1 | 鈴木 | 8,500 |
| 4 | 10/2 | 佐藤 | 15,000 |
| 5 | 10/3 | 田中 | 6,800 |
| 6 | 10/3 | 鈴木 | 9,700 |
理由は3章で説明しますが、コードには最初からシート名をつけています。
Sub データ部分に罫線を引く()
Dim ws As Worksheet
Dim lastRow As Long
Set ws = Worksheets("売上")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row ' → 6
With ws
.Range(.Cells(2, 1), .Cells(lastRow, 3)).Borders.LineStyle = xlContinuous
Debug.Print .Range(.Cells(2, 1), .Cells(lastRow, 3)).Address ' → $A$2:$C$6
End With
End Sub
7行目にデータを足して実行し直すと、範囲は自動で A2:C7 まで伸びます。終了行を変数 lastRow で渡しているからですね。
End(xlUp) で最終行を求める仕組みや、ほかの方法との違いは「VBA最終行の取得方法」で詳しく解説しています。
列も可変にしたいときは、終了列も変数にします。
Sub 列も可変にする()
Dim ws As Worksheet
Dim lastRow As Long, lastCol As Long
Set ws = Worksheets("売上")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row ' → 6
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column ' → 3
Debug.Print ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)).Address ' → $A$1:$C$6
End Sub
D列に「備考」の見出しを足すと、lastCol は4になり範囲は A1:D6 に広がります。最終列の求め方と結合セルの注意点は「VBAで最終列を取得する方法」を見てみてください。
Range.Cellsは「範囲の中での位置」になる
Cellsは、シートだけでなくRangeの後ろにもつけられます。このとき行・列の番号は、シートの左上ではなくその範囲の左上から数えるので注意しましょう。
Sub 範囲の中のCells()
Debug.Print Range("B2:D8").Cells(1, 1).Address ' → $B$2(A1ではない)
Debug.Print Range("B2:D8").Cells(2, 3).Address ' → $D$3
Debug.Print Range("B2:C3").Cells(3, 3).Address ' → $D$4(範囲の外でもエラーにならない)
End Sub
Range("B2:D8").Cells(2, 3) は、B2から数えて2行目・3列目なのでD3です。3行目のように範囲の外を指してもエラーにならないため、気づかないうちに外側のセルを書き換えることもあります。
もっと混同しやすいのが、With の中の .Cells です。
Sub Withの対象で変わるCells()
With Worksheets("売上")
Debug.Print .Cells(1, 1).Address ' → $A$1(シートの左上)
End With
With Worksheets("売上").Range("B2:D8")
Debug.Print .Cells(1, 1).Address ' → $B$2(範囲の左上)
End With
End Sub
見た目は同じ .Cells(1, 1) でも、Withの対象がシートか範囲かで指す場所が変わります。.Cells を見たら、Withの行まで戻って対象を確かめると安心ですよ。
Cells(i, “C”) のように列を文字で書く
Cellsの列は、数値だけでなく列の文字でも指定できます。Cells(1, "A") は Cells(1, 1) と同じA1で、"AA" のような2文字の列もOKです。
たとえば売上シートで ws.Cells(i, "C").Value と書けば、Cells(i, 3) より「C列の金額を読んでいる」と一目でわかります。行はループで動かし、列は見出しに合わせて固定する場面にぴったり。列そのものを変数で動かすなら、計算しやすい数値の方が向いています。
別シートで実行時エラー1004が出る原因と直し方
RangeとCellsを組み合わせたとき、いちばんつまずきやすいのが実行時エラー1004です。原因の多くは、RangeとCellsでシートの指定が食い違っていること。典型例から直し方、見落としやすい罠まで順に見ていきます。
Cellsだけアクティブシートを指している
まずは、よくある失敗例から。
Sub 別シートをクリア_NG()
' 「売上」シートを表示した状態で実行すると1004で止まる
Sheets("Sheet2").Range(Cells(1, 1), Cells(10, 3)).ClearContents
End Sub
Range の前には Sheets("Sheet2") があるので、RangeはSheet2を指します。ところが、カッコの中の Cells には何もついていません。
シートを書かないCellsは、アクティブシート(いま画面に表示しているシート)のセルを指します。Sheet2の範囲を「売上」シートのセルで作ろうとするので、エラーになるわけです。
やっかいなのは、Sheet2を表示した状態で実行すると動いてしまう点。CellsもたまたまSheet2を指すので、テストでは気づけません。別のシートから実行したときに初めて止まる、というパターンです。
直し方:Withとドット/Worksheet変数
直し方は2通りあります。1つ目は With でシートをまとめる書き方です。
Sub 別シートをクリア_With()
With Sheets("Sheet2")
.Range(.Cells(1, 1), .Cells(10, 3)).ClearContents
End With
End Sub
.Range と .Cells の先頭にあるドットが、「Withで指定したシートの」という意味になります。Range・Cells・Cellsの3か所すべてにドットをつけるのがポイント。
ドットが1つでも漏れると、そのCellsだけアクティブシートに戻ってしまいます。結果は同じ1004なので、見落としやすいんです。
2つ目は、シートを変数に入れておく書き方です。
Sub 別シートをクリア_変数()
Dim ws As Worksheet
Set ws = Worksheets("Sheet2")
ws.Range(ws.Cells(1, 1), ws.Cells(10, 3)).ClearContents
End Sub
複数のシートを行き来するマクロなら、こちらがおすすめ。src(転記元)と dst(転記先)のように名前をつけると、シートの取り違えを防げます。
Set を使うオブジェクト変数の基本は「VBA変数の使い方」で確認できますよ。
直したのにまだ1004:.Selectの落とし穴
シートの指定を揃えても、末尾が .Select だと1004が出ることがあります。記録マクロを手直ししたコードで、特に起きやすいパターンです。
Sub 別シートを選択_NG()
With Sheets("Sheet2")
.Range(.Cells(1, 1), .Cells(10, 3)).Select ' Sheet2が非アクティブだと1004
End With
End Sub
Selectは画面上でセルを選ぶ操作なので、表示されていないシートのセルは選べません。Sheet2がアクティブでなければSelectが失敗し、実行時エラー1004になります。
直し方は次の2つです。
- Selectせずに直接操作する(
.Value = ""・.ClearContents・.Copyなど) - どうしても選択したいときは、Selectの前に
.Activateでシートを表示する
記録マクロはSelectだらけになりがちですが、値の書き込みや書式設定にSelectは要りません。Selectを消すだけで、1004の原因が1つ減りますよ。
シートモジュールに書くと参照先が変わることがある
VBAのコードを書く場所には、「標準モジュール」と、シートごとに付いている「シートモジュール」があります。標準モジュールでは、シートを書かないRange・Cellsはアクティブシートを指すのが基本。
ところがシートモジュールに書くと、アクティブシートではなく、そのモジュールが属するシートを指す場合があるんです。標準モジュールからコピーしたコードが違う動きをしたら、これを疑ってみてください。
いちばん確実な対策は、置き場所に関係なく ws.Cells のようにシートを必ず書くこと。シートモジュールの中で自分のシートを指したいなら、Me.Range・Me.Cells と書く方法もあります。
1004が出る典型5パターン
ここまでの内容に、範囲外の指定を加えて一覧にしました。
| 書いたコード | 原因 | 直し方 |
|---|---|---|
Sheets("Sheet2").Range(Cells(1, 1), Cells(10, 3)) | Cellsだけアクティブシートを指す | With+.Cells、または ws.Cells |
.Range(.Cells(1, 1), Cells(10, 3)) | ドットが1つ漏れている | すべてにドットをつける |
ws.Range("A1:C10").Select(wsが非アクティブ) | 表示していないシートでSelectした | Selectせず直接操作する/先に ws.Activate |
Cells(0, 1)/Cells(1, 0) | 0行目・0列目は存在しない | ループの開始値や計算結果を確認する |
Cells(1048577, 1)/Cells(1, 16385) | シートの上限(1,048,576行・16,384列)を超えた | 行・列の計算式を見直す |
4つ目は、For i = 0 To … と書いたときや、lastRow - 5 のような計算が0以下になったときに起きがちです。Range("A" & i) で i が0になり、存在しない「A0」を指した場合もエラーになります。
エラーで止まったら、まずは止まった行のRange・Cellsにシートがついているかを確認してみてください。1004以外のエラーも含めた逆引きは「VBAマクロのエラー解決ガイド」にまとめていますよ。
実務シナリオ別の選び方
ここまでの判断基準を、よくある実務シナリオに当てはめてみましょう。まずは一覧です。
| シナリオ | おすすめ | 理由 |
|---|---|---|
| 固定の設定セル・名前付き範囲を読む | Range | 番地や名前がそのまま読める |
| 一覧表を上から1行ずつ処理する | Cells | 行番号を変数で動かせる |
| 可変範囲を別シートへ転記する | Range(Cells, Cells) | 終了行を変数で決めて範囲ごと扱える |
| 列の位置が変わる表を扱う | Cells | 列番号を変数で持てる |
| 集計結果を決まったセルに書く | ループはCells・書き込み先はRange | 役割で書き分けると読みやすい |
| 範囲内の全セルを一括処理する | Range+For Each | 行番号を管理しなくてよい |
以下のコードは、2章の「売上」シートを使っています。
固定の設定セル・名前付き範囲を読む
Sub 設定値を読む()
Dim ws As Worksheet
Set ws = Worksheets("設定")
Debug.Print ws.Range("B2").Value ' 固定セルは番地で書く
Debug.Print ws.Range("税率").Value ' 名前付き範囲もRangeで読める
End Sub
「税率」は、設定シートのセルにつけた名前です。名前はCellsでは指定できないので、ここはRange一択。表のレイアウトが変わってセルが移動しても、名前ならコードを直さずに済みますよ。
一覧表を上から1行ずつ処理する
Sub 一万円以上に印をつける()
Dim ws As Worksheet
Dim lastRow As Long, i As Long
Set ws = Worksheets("売上")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For i = 2 To lastRow ' 1行目は見出しなので2行目から
If ws.Cells(i, 3).Value >= 10000 Then
ws.Cells(i, 4).Value = "◎" ' D列に印をつける
End If
Next i
End Sub
' 実行後: D2(12,000円)と D4(15,000円)に ◎ が入る
C列を見てD列に書く、というように同じ行の別の列を扱っています。行番号 i を持ち回れるCellsが向いている場面です。
可変範囲を別シートへ転記する
Sub 売上を集計シートへ転記する()
Dim src As Worksheet, dst As Worksheet
Dim lastRow As Long
Set src = Worksheets("売上")
Set dst = Worksheets("集計")
lastRow = src.Cells(src.Rows.Count, 1).End(xlUp).Row
src.Range(src.Cells(1, 1), src.Cells(lastRow, 3)).Copy dst.Range("A1")
End Sub
' 実行後: 集計シートの A1:C6 に売上シートの表が貼り付く
転記元のRangeとCellsには、すべて src. をつけています。Selectを使わず、Copyに貼り付け先を直接渡しているのもポイント。集計シートを表示していなくても動きます。
転記元が複数シートになるケースは「VBAで複数シート間のデータを転記・集約する方法」で解説しています。
列の位置が変わる表を扱う
Sub 金額列を探す()
Dim ws As Worksheet
Dim lastCol As Long, col As Long, j As Long
Set ws = Worksheets("売上")
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
For j = 1 To lastCol ' 見出し行から「金額」の列を探す
If ws.Cells(1, j).Value = "金額" Then col = j
Next j
If col = 0 Then Exit Sub ' 見つからなければ終了
Debug.Print ws.Cells(2, col).Value ' → 12000(C2の金額)
End Sub
列の並びが変わって金額がE列に移っても、見出しの名前で探すのでコードはそのまま動きます。あとは ws.Cells(i, col) で行を動かせば、金額を1件ずつ読めます。列番号を変数 col に入れて使えるのは、数値で列を受け取るCellsならでは。
集計結果を決まったセルに書く
Sub 合計を書き出す()
Dim ws As Worksheet
Dim lastRow As Long, i As Long
Dim total As Long
Set ws = Worksheets("売上")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For i = 2 To lastRow ' ループの中は行が動くのでCells
total = total + ws.Cells(i, 3).Value
Next i
ws.Range("F1").Value = "合計" ' 書き込み先は固定なのでRange
ws.Range("G1").Value = total ' → 52000
End Sub
ループの中は行が動くのでCells、書き込み先は決まっているのでRange。1つのマクロの中で書き分けると、どこが可変でどこが固定かが一目でわかります。
範囲内の全セルを一括処理する
範囲内のセルを1つずつ処理するなら、Rangeで範囲を作ってFor Eachに渡すのが定番です。行番号を管理しなくて済むので、「そのセル自身だけを書き換える」処理ならこちらがシンプル。書き方は「VBA For Eachの使い方」で解説しています。
隣の列にも書き込むなら、前述の「一覧表を上から1行ずつ処理する」の形が扱いやすいですよ。
よくある質問(FAQ)
Q. RangeとCellsで処理速度は違いますか?
1セルずつ指定する場面での差は小さく、読みやすさで選んで問題ありません。何万行もの処理が遅いと感じたら、指定方法より「セルへの読み書きの回数」を見直す方が効きます。範囲の値を配列に一括で読み込み、計算後にまとめて書き戻す方法が代表的です。
Q. Cellsだけで範囲を指定できますか?
引数ありのCellsが指すのは1セルだけです。複数セルの範囲にしたいときは、Range(Cells(2, 1), Cells(6, 3)) のようにRangeと組み合わせましょう。基準セルから範囲を広げるResizeを使う方法もあります。
Q. Cells(1, “A”) のように列を文字で書いてもいいですか?
問題ありません。Cells(1, "A") は Cells(1, 1) と同じA1です。列が固定の処理なら、見出しとの対応がわかりやすくなるのでむしろおすすめ。
Q. Range(“A” & i) は間違いですか?
間違いではなく、正しく動きます。行だけを動かすループなら、番地の形が見える分、読みやすさではむしろ有利です。列も変数にしたくなった時点で、Cellsに書き換えるとよいでしょう。
Q. Range.CellsとCellsの違いは何ですか?
数える基準が違います。シートのCellsはシートの左上(A1)から、Range("B2:D8").Cells は範囲の左上(B2)から数えます。同じ Cells(1, 1) でも、前者はA1、後者はB2です。
Q. シートを書かないCellsはどのシートを指しますか?
標準モジュールではアクティブシートです。シートモジュールに書いた場合は、そのシートを指すことがあります。どちらにしても、ws.Cells のようにシートを明示しておけば迷いませんよ。
まとめ:RangeとCellsの迷ったときの早見表
最後に、どちらで書くかを一覧で確認しておきましょう。
| やりたいこと | 使うもの | 書き方の例 |
|---|---|---|
| 固定のセルに書く | Range | ws.Range("B2").Value = 100 |
| 名前付き範囲を読む | Range | ws.Range("税率").Value |
| 行を変数で動かす | Cells | ws.Cells(i, 3).Value |
| 行も列も変数で動かす | Cells | ws.Cells(r, c).Value |
| 列を文字で読みやすく書く | Cells(列は文字) | ws.Cells(i, "C").Value |
| 範囲を数値で作る | Range(Cells, Cells) | .Range(.Cells(2, 1), .Cells(lastRow, 3)) |
| 範囲の中で位置を指す | Range.Cells | ws.Range("B2:D8").Cells(1, 1) |
| 別シートを操作する | With+ドット/シート変数 | ws.Range(ws.Cells(1, 1), ws.Cells(10, 3)) |
この記事のポイントは次の3つです。
- RangeもCellsも、返すのは同じRangeオブジェクト。違いは指定の仕方だけ
- 場所が固定ならRange、行・列を変数で動かすならCells、範囲を数値で作るならRange(Cells, Cells)
- 1004を防ぐ近道は、RangeにもCellsにもシートを書き、Selectに頼らないこと
どちらか一方に統一する必要はありません。1つのマクロの中でも、場面に合わせて書き分けるのがいちばん読みやすいコードになります。まずは手元のマクロで、シートのついていないCellsがないか探すところから始めてみてください。
次に読むなら、こちらの記事がおすすめです。
- VBA Rangeの使い方:Offset・Resize・CurrentRegion・For Eachでの範囲操作
- VBA最終行の取得方法:Range(Cells, Cells)の終了行を求める
- VBA For Eachの使い方:範囲内のセルを1つずつ処理する
- Excel VBA学習ロードマップ:次に学ぶ順番を5ステップで確認