Excel VBAでセルを操作するとき、RangeとCellsのどちらを使うか迷いませんか。
書き方がまったく違うので、「結局どっちが正解なの?」と手が止まりがちですよね。使い分けを間違えると、ループ処理でセル番地の文字列結合が必要になったり、別シート操作で1004エラーに悩まされたりします。
この記事では、RangeとCellsの違いを比較表で整理し、実務シナリオごとの選び方をお伝えします。Offset・Resizeとの組み合わせやFor Eachでのループ処理も取り上げるので、読み終わるころには「この場面ならこっち」と迷わず選べるようになりますよ。
NOTE
VBAを初めて触る方は、先に「VBE画面の見方」をチェックしておくとスムーズです。
RangeとCellsの違いを一覧でおさらい
まず結論から整理しましょう。RangeとCellsはどちらも「セルを指定する方法」ですが、得意な場面が違います。
| 比較項目 | Range | Cells |
|---|---|---|
| 書き方 | Range("A1") | Cells(1, 1) |
| 指定方法 | アドレス文字列 | 行番号・列番号(数値) |
| 複数セル範囲 | Range("A1:C10") で直接指定 | 単体では1セルのみ |
| 名前付き範囲 | Range("売上合計") で参照可 | 非対応 |
| 変数との相性 | 文字列結合が必要 | 数値をそのまま渡せる |
| ループ処理 | For Eachと好相性 | For文カウンターと好相性 |
| Offset/Resize | 両方で使える | 両方で使える |
ひとことで言えば、「文字列で指定するか、数値で指定するか」の違いです。この違いが使い分けの根拠になっています。
Range:アドレス直書きでわかりやすい
RangeはExcelシート上の「A1」形式をそのまま使えます。普段のExcel操作と同じ感覚なので、直感的に読み書きできるのが強みです。
' 1つのセルに値を入力
Range("A1").Value = "こんにちは"
' 範囲をまとめて太字にする
Range("A1:C10").Font.Bold = True
' 離れたセルを一括指定
Range("A1,C3,E5").Font.Color = vbRed
Rangeの詳しい使い方は「VBA Rangeの使い方」でも解説しています。
Cells:行番号・列番号で指定できる
Cellsは行と列を数値で指定します。「Cells(行, 列)」の形で、列もアルファベットではなく数字で書くのがポイントです。
' A1セル(1行目・1列目)に値を入力
Cells(1, 1).Value = "こんにちは"
' B3セル(3行目・2列目)に値を入力
Cells(3, 2).Value = 100
数値だけで指定するので、変数と組み合わせやすいのが最大の強みです。
VBA Cellsを使うべき場面
Cellsが本領を発揮するのは、セルの位置を動的に変えたいときです。
For文でループするとき
Cellsの最大の強みは、変数をそのまま行番号に渡せることです。
' A列の1行目〜10行目に連番を入力
Dim i As Long
For i = 1 To 10
Cells(i, 1).Value = i
Next i
同じ処理をRangeで書くとこうなります。
' Rangeだと文字列結合が必要
Dim i As Long
For i = 1 To 10
Range("A" & i).Value = i
Next i
動きは同じですが、"A" & i と文字列結合が入ります。列も変数にしたい場合はさらに複雑になるので、ループ処理ではCellsがおすすめです。
For文の書き方をもっと知りたい方は「VBA For文の使い方」もチェックしてみてください。
行列を動的に計算したいとき
行も列も変数で動かしたい場合、Cellsの数値指定が活きます。
' 5×5の掛け算表を作る
Dim r As Long ' 行カウンター
Dim c As Long ' 列カウンター
For r = 1 To 5
For c = 1 To 5
Cells(r, c).Value = r * c
Next c
Next r
二重ループとの相性は抜群です。Rangeでこれをやると、列番号をアルファベットに変換する処理が必要になり、かなり面倒になります。
変数(Dim)の宣言ルールが気になる方は「変数の使い方とルール」を参照してください。宣言を強制するOption Explicitもあわせて覚えておくと安心ですよ。
VBA Rangeを使うべき場面
一方、Rangeが適しているのは「セルの位置が決まっている」場面です。
セルアドレスが固定のとき
操作対象が固定セルなら、Rangeが読みやすいです。
' 決まったセル範囲をクリアする
Range("A1:D1").ClearContents
' ヘッダー行を太字にする
Range("A1:F1").Font.Bold = True
「A1:D1」とそのまま書けるので、コードを読んだ人がすぐに対象範囲を把握できます。可読性の高さがRangeの大きなメリットです。
名前付き範囲を指定するとき
Excelで名前を定義している場合、Rangeから直接参照できます。
' 名前定義「売上合計」の値を取得
Dim total As Long
total = Range("売上合計").Value
名前付き範囲はCellsでは参照できません。名前定義を使う場面ではRangeの一択になります。
セルの位置が変わってもコードを修正しなくて済むので、実務ではとても便利ですよ。なお、RangeやCellsで取得した値をExcel関数で処理したい場合は「WorksheetFunctionの使い方」もあわせてチェックしてみてください。
For Eachで範囲内のセルを一括処理する
For Each文を使うと、Range内の各セルを1つずつ取り出して処理できます。「何行目か」を自分で管理しなくていいので、コードがシンプルになるのが魅力です。
For EachとRangeの基本パターン
Sub 空欄セルに初期値を入れる()
Dim cell As Range
For Each cell In Range("B2:B20")
If cell.Value = "" Then
cell.Value = 0
End If
Next cell
End Sub
For Each cell In Range(...) と書くだけで、範囲内のセルを上から順に処理してくれます。行番号を管理する i 変数が不要なので、コードの見通しがよくなります。
For Each文の詳しい使い方は「For Eachの使い方」で解説しています。
For EachとCellsの使い分け
For EachとFor文(Cells)のどちらを選ぶかは、「処理中に行番号が必要かどうか」で決まります。
' ケース1: 行番号が不要 → For Each がシンプル
Sub 負の値を赤くする()
Dim cell As Range
For Each cell In Range("C2:C100")
If cell.Value < 0 Then
cell.Font.Color = vbRed
End If
Next cell
End Sub
' ケース2: 行番号を使って別列に書き込む → For + Cells が便利
Sub 判定結果を隣の列に出力する()
Dim lastRow As Long
lastRow = Cells(Rows.Count, 3).End(xlUp).Row
Dim i As Long
For i = 2 To lastRow
If Cells(i, 3).Value >= 80 Then
Cells(i, 4).Value = "合格"
Else
Cells(i, 4).Value = "不合格"
End If
Next i
End Sub
「同じセルだけ触る処理」ならFor Each、「隣の列や別のセルにも書き込む処理」ならFor + Cellsと覚えておくと迷いません。
For Eachで実務に使えるパターン:条件に合うセルを集計
実務でよくあるのが、範囲内のセルを条件でフィルタしながら集計するパターンです。
Sub 部門別の売上を集計する()
Dim cell As Range
Dim totalSales As Long
' A列が「営業部」の行だけ、B列の金額を合計
For Each cell In Range("A2:A50")
If cell.Value = "営業部" Then
totalSales = totalSales + cell.Offset(0, 1).Value
End If
Next cell
Range("D1").Value = "営業部合計"
Range("E1").Value = totalSales
End Sub
ここで使っている Offset については、次のセクションで詳しく説明します。
Offset・Resizeで基準セルからの相対指定
RangeやCellsで取得したセルを「起点」として、そこから相対的に位置をずらしたり範囲を広げたりできます。これがOffsetとResizeです。
Offset:基準セルから行・列をずらす
Offset(行方向, 列方向) で、基準セルからの相対位置を指定します。
' A1セルから2行下・1列右 → B3セル
Range("A1").Offset(2, 1).Value = "ここはB3"
' 現在のセルの1つ右に値を入力
Cells(5, 1).Offset(0, 1).Value = "B5に入力"
行方向の正の値は下、負の値は上。列方向の正の値は右、負の値は左です。
Offsetが特に活躍するのは、For Eachループの中で隣のセルにアクセスするときです。
Sub 単価と数量から金額を計算する()
Dim cell As Range
' B列(単価)をループして、C列(数量)と掛けた結果をD列に出力
For Each cell In Range("B2:B20")
If cell.Value <> "" Then
cell.Offset(0, 2).Value = cell.Value * cell.Offset(0, 1).Value
End If
Next cell
End Sub
For Eachでは行番号を直接持たないので、「隣のセル」「2列右のセル」にアクセスするにはOffsetが必須です。この組み合わせは実務で非常によく使います。
Resize:範囲のサイズを変更する
Resize(行数, 列数) で、基準セルから指定した大きさの範囲を作ります。
' A1セルを起点に、5行3列の範囲を選択
Range("A1").Resize(5, 3).Select
' ヘッダー行を除いたデータ範囲を取得
Dim dataRange As Range
Set dataRange = Range("A1").Resize(10, 4) ' A1:D10
Resizeは「データの先頭セルはわかっているが、範囲の大きさを動的に決めたい」ときに重宝します。
Offset + Resize の組み合わせ:ヘッダーを除外する実例
OffsetとResizeを組み合わせると、「ヘッダー行をスキップしてデータ範囲だけ取得する」といった実務的な操作が1行で書けます。
Sub ヘッダーを除いてデータ範囲に色をつける()
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
' A1から始まるテーブルのヘッダーを除外して色付け
' Offset(1, 0) で1行下にずらし、Resize(lastRow - 1, 4) でデータ行分の範囲を確保
Range("A1").Offset(1, 0).Resize(lastRow - 1, 4).Interior.Color = RGB(230, 240, 255)
End Sub
ちょっとむずかしく見えますが、やっていることはシンプルです。Offset(1, 0) でA2に移動し、Resize(lastRow – 1, 4) でA2からデータ最終行までの4列分を取得しています。
Offset・Resizeの詳しい使い方は「VBA Rangeの使い方」でも解説しています。
Range(Cells, Cells)の組み合わせ技
RangeとCellsは組み合わせて使えます。これを覚えると、動的な範囲指定がぐっと楽になります。
基本構文
Range(Cells(開始行, 開始列), Cells(終了行, 終了列)) の形で書きます。
' A1からE10までを選択
Range(Cells(1, 1), Cells(10, 5)).Select
開始位置も終了位置も数値で制御できるのがポイントです。固定範囲にはRange単体、動的範囲にはこの組み合わせと覚えておきましょう。
開始・終了行を動的に変える実例
実務で最もよく使うのが、最終行を取得して動的に範囲を決めるパターンです。
Sub 動的範囲に罫線を引く()
' A列の最終行を取得
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
' A1から最終行のC列まで罫線を引く
Range(Cells(1, 1), Cells(lastRow, 3)).Borders.LineStyle = xlContinuous
End Sub
Cells(Rows.Count, 1).End(xlUp).Row でA列の一番下から上に向かって最終行を探しています。データの件数が増減しても自動で対応できるので、実務では非常に重宝します。
最終行の取得方法をもっと詳しく知りたい方は「最終行を取得する3つの方法」もどうぞ。
もうひとつ、可変範囲をコピーする実例も見てみましょう。
Sub データ範囲をコピーして貼り付ける()
Dim lastRow As Long
Dim lastCol As Long
' データの最終行・最終列を取得
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
lastCol = Cells(1, Columns.Count).End(xlToLeft).Column
' データ範囲をまるごとコピー
Range(Cells(1, 1), Cells(lastRow, lastCol)).Copy
' Sheet2のA1に貼り付け
Sheets("Sheet2").Range("A1").PasteSpecial xlPasteValues
Application.CutCopyMode = False
End Sub
最終行だけでなく最終列も動的に取得すれば、列が増えても自動で対応できます。
実務シナリオ別の選択ガイド
ここまでの内容を、よくある実務シナリオで整理しましょう。
一覧表を上から順に処理する
請求書データや社員名簿を1行ずつ処理するパターンです。
Sub 一覧表を順に処理する()
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
Dim i As Long
For i = 2 To lastRow ' 2行目から(1行目はヘッダー)
' B列が空欄ならスキップ
If Cells(i, 2).Value <> "" Then
' D列に計算結果を入力
Cells(i, 4).Value = Cells(i, 2).Value * Cells(i, 3).Value
End If
Next i
End Sub
行を変数 i で動かすのでCells一択です。「For文」と「For Each」のどちらでもCellsが活躍します。条件分岐の詳しい書き方は「VBA If文の使い方」も参考にしてみてください。
For Eachで条件付き書式を動的に適用する
For Eachを使えば、セルの値に応じて書式を変えるといった処理もシンプルに書けます。
Sub 売上達成状況に色をつける()
Dim cell As Range
For Each cell In Range("D2:D50")
Select Case True
Case cell.Value >= 1000000
cell.Interior.Color = RGB(198, 239, 206) ' 緑(達成)
Case cell.Value >= 500000
cell.Interior.Color = RGB(255, 235, 156) ' 黄(もう少し)
Case Else
cell.Interior.Color = RGB(255, 199, 206) ' 赤(未達)
End Select
Next cell
End Sub
行番号を使わず「セルそのもの」を操作するだけなので、For Eachが最適です。Select Caseの詳しい書き方は「Select Caseの使い方」もチェックしてみてください。
可変範囲をまるごとコピーする
データの最終行・最終列が変わる範囲を一括操作するパターンです。
Sub 可変範囲を書式設定する()
Dim lastRow As Long
Dim lastCol As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
lastCol = Cells(1, Columns.Count).End(xlToLeft).Column
' データ範囲全体に背景色をつける
Range(Cells(2, 1), Cells(lastRow, lastCol)).Interior.Color = RGB(230, 240, 255)
End Sub
Range(Cells, Cells)の組み合わせが最適です。開始・終了を変数で制御しつつ、範囲全体をまとめて操作できます。
集計行をループの外に固定する
ヘッダーや集計行など、位置が固定のセルを操作するパターンです。
Sub 集計処理()
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
' ループ部分はCells
Dim total As Long
Dim i As Long
For i = 2 To lastRow
total = total + Cells(i, 3).Value
Next i
' 固定セルへの書き込みはRange
Range("A1").Value = "集計結果"
Range("B1").Value = total
End Sub
ループ内はCells、固定セルはRangeと使い分けるのがスマートです。コードの意図が明確になり、後から読んでも何をしているか一目でわかります。
シナリオ別まとめ表
| シナリオ | おすすめ | 理由 |
|---|---|---|
| 一覧を上から順に処理 | Cells + For文 | 行番号を変数で動かす |
| 範囲内のセルを一括処理 | Range + For Each | 行番号管理が不要でシンプル |
| 可変範囲を一括操作 | Range(Cells, Cells) | 動的な開始・終了を範囲指定 |
| 固定セルへの読み書き | Range | アドレス直書きで可読性が高い |
| 名前付き範囲の参照 | Range | Cellsでは名前定義を参照できない |
| 二重ループ(行×列) | Cells | 行列ともに数値で制御できる |
| 隣のセルにアクセス | Offset | For Each内で行番号なしに隣接セルを操作 |
| 動的な範囲サイズ変更 | Resize | 基準セルから任意サイズの範囲を作成 |
1004エラーの原因と対処法
RangeとCellsの組み合わせで最もハマりやすいのが、実行時エラー1004(「アプリケーション定義またはオブジェクト定義のエラー」)です。
Worksheetオブジェクトの明示
このエラーは、別シートのCellsをRange引数に渡すときに起きます。
' エラーになるコード
Sheets("Sheet2").Range(Cells(1, 1), Cells(10, 3)).Select
一見すると正しく見えますが、問題があります。Range は Sheet2 を指していますが、Cells はアクティブシートを指しています。シートの参照先がバラバラなのでエラーになるのです。
正しくはこう書きます。
' 正しいコード:Withでシートを統一する
With Sheets("Sheet2")
.Range(.Cells(1, 1), .Cells(10, 3)).Select
End With
ポイントは .Range と .Cells の先頭にドット(.)をつけることです。ドットをつけると、Withで指定したSheet2のプロパティとして認識されます。
ドットを1つでも忘れるとエラーになるので注意してください。
もうひとつ、シートを変数に入れて書く方法もあります。
' 変数でシートを指定する方法
Dim ws As Worksheet
Set ws = Sheets("Sheet2")
ws.Range(ws.Cells(1, 1), ws.Cells(10, 3)).Select
どちらの書き方でもOKです。大事なのは「Range・Cells・すべてのシート参照を揃える」ことです。
NOTE
1004エラーは実行時に発生するため、コードを書いた時点では気づけません。別シートを操作するコードを書いたら、必ずテスト実行して確認しましょう。
OffsetやResizeでも同じ原則
Offset・Resizeを使う場合もシート参照を揃える必要があります。
' NGパターン:シート参照が混在
Sheets("Sheet2").Range("A1").Offset(1, 0).Resize(10, 3).Value = ""
' OKパターン:Withで統一
With Sheets("Sheet2")
.Range("A1").Offset(1, 0).Resize(10, 3).Value = ""
End With
Offset・Resizeは基準セルのシートを引き継ぐので、基準となるRange/Cellsのシート指定さえ正しければ問題ありません。Withブロックを使う習慣をつけておくと安全です。
まとめ:迷ったときの使い分け早見表
RangeとCellsの使い分けを最終確認しましょう。
| やりたいこと | 使うもの | 書き方の例 |
|---|---|---|
| 固定セルに値を入れる | Range | Range("A1").Value = 100 |
| 名前付き範囲を参照 | Range | Range("売上合計").Value |
| ループで行を動かす | Cells | Cells(i, 1).Value |
| 行列ともに変数で動かす | Cells | Cells(r, c).Value |
| 範囲内のセルを一括処理 | For Each + Range | For Each cell In Range("A1:A10") |
| 隣のセルにアクセス | Offset | cell.Offset(0, 1).Value |
| 動的なサイズの範囲を作る | Resize | Range("A2").Resize(lastRow, 4) |
| 動的範囲を一括操作 | Range + Cells | Range(Cells(1,1), Cells(lastRow,3)) |
| 別シートのセルを操作 | With + ドット付き | .Range(.Cells(1,1), .Cells(10,3)) |
判断に迷ったら、次の順番で考えてみてください。
- 名前付き範囲を使う? → Range
- セルの位置が固定? → Range
- 範囲内を順に処理(行番号不要)? → For Each + Range
- 変数で位置を動かす? → Cells
- 動的な範囲指定? → Range(Cells, Cells)
- 基準セルから相対的にずらす? → Offset / Resize
どちらが正解ということではなく、場面で使い分けるのがベストです。VBAに慣れてくると、これらを自然に組み合わせて使えるようになりますよ。
ぜひVBEを開いて、両方のコードを試してみてくださいね。VBA全体の学習順序に迷ったら「VBA学習の順番ガイド」も参考にしてください。
