VBA RangeとCellsの違いと使い分け|1004エラー対策つき

スポンサーリンク

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項目の比較表

比較項目RangeCells
引数の形番地の文字列 "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です。

  1. 名前付き範囲を使う? またはセルの場所が固定? → Range
  2. 行や列を変数で動かす? → Cells
  3. 範囲の始点・終点を数値で作る? → 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") を、数値で書き直した形です。

最終行・最終列と組み合わせる

実務でいちばん使うのは、最終行を求めてから範囲を作るパターンです。ここからは、次のような「売上」シートを例に進めます。

 ABC
1日付担当金額
210/1佐藤12,000
310/1鈴木8,500
410/2佐藤15,000
510/3田中6,800
610/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の迷ったときの早見表

最後に、どちらで書くかを一覧で確認しておきましょう。

やりたいこと使うもの書き方の例
固定のセルに書くRangews.Range("B2").Value = 100
名前付き範囲を読むRangews.Range("税率").Value
行を変数で動かすCellsws.Cells(i, 3).Value
行も列も変数で動かすCellsws.Cells(r, c).Value
列を文字で読みやすく書くCells(列は文字)ws.Cells(i, "C").Value
範囲を数値で作るRange(Cells, Cells).Range(.Cells(2, 1), .Cells(lastRow, 3))
範囲の中で位置を指すRange.Cellsws.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がないか探すところから始めてみてください。

次に読むなら、こちらの記事がおすすめです。

タイトルとURLをコピーしました