N - 1.13.配列 Editorial /

Time Limit: 0 msec / Memory Limit: 0 KB

前のページ | 次のページ

キーポイント

  • 配列は様々なデータの列を扱うことができる機能
  • vector<型> 配列変数名;で配列変数を宣言できる
  • 配列変数名 = { 要素1, 要素2, ... };で配列変数に値を代入できる
  • 配列変数.at(i)i番目の要素にアクセスできる
  • 配列変数.size()で配列の要素数を取得できる
  • vector<型> 配列変数名(要素数)と書くと指定した要素数で配列を初期化できる
  • 配列でN個の入力を受け取るときは、N要素で初期化した後、for文の中でatを使って1ずつ受け取る
vector<int> vec(N);
for (int i = 0; i < N; i++) {
  cin >> vec.at(i);
}
  • 配列とfor文を組み合わせると、大量のデータを扱うプログラムを簡潔に書ける
  • 配列の添字のルールは文字列と同じ

配列と文字列

文字列は「文字の列」を扱うための機能でした。
配列は文字だけでなく、様々なデータの列を扱うことができる非常に重要な機能です。

文字列と配列は使い方もある程度同じです。
次のプログラムは、「'a', 'b', 'c', 'd'」という文字の列と、「25, 100, 64」という整数の列を扱っています。

#include <bits/stdc++.h>
using namespace std;

int main() {

  // 文字列
  string str; // 文字列変数を宣言

  str = "abcd"; // 'a', 'b', 'c', 'd' という文字(char)の列を代入

  cout << str.at(0) << endl; // 1つ目である'a'を出力

  cout << str.size() << endl; // 文字列の長さである4を出力


  // 配列
  vector<int> vec; // int型の配列変数vecを宣言

  vec = { 25, 100, 64 }; // 25, 100, 64 という整数(int)の列を代入

  cout << vec.at(0) << endl; // 1つめである25を出力

  cout << vec.size() << endl; // 配列の長さである3を出力
}
実行結果
a
4
25
3

配列変数の宣言

配列変数は次の形式で宣言します。

vector<型> 配列変数名;

vector<int> vec;と書いた場合、int型のデータ列を扱う配列変数vecが宣言されます。

配列変数への代入

配列変数に値を代入する方法はいくつかありますが、その一つが次の形式です。

配列変数 = { 要素1, 要素2, ... };

配列が持つデータの1つ1つのことを要素と呼びます。

vec = { 25, 100, 64 };と書いた場合、「25, 100, 64」というデータ列が配列変数vecに代入されています。

i番目の要素

文字列と同様に、配列も.at(i)を使ってi番目の要素へアクセスできます。

配列変数.at(i)

配列でも添字は0から始まります。

vector<int> vec = {25, 100, 64};という配列変数vecの場合、添字の値と文字の対応は次の表のとおりです。

添字 0 1 2
要素 25 100 64

配列の要素数

文字列と同様に、配列も.size()を使って要素数(長さ)を取得できます。

配列変数.size()

vector<int> vec = {25, 100, 64};という配列変数vecの場合、vec.size()の値は3になります。


配列と変数

配列は「複数の変数を一度に宣言する方法」のように使うことができます。

次のプログラムは、3つの整数を入力から受け取り、それらの合計を出力します。

#include <bits/stdc++.h>
using namespace std;

int main() {
  int a1, a2, a3;
  cin >> a1 >> a2 >> a3;

  cout << a1 + a2 + a3 << endl;
}

次のプログラムは上のプログラムとほとんど同じ意味です。

#include <bits/stdc++.h>
using namespace std;

int main() {
  // 3個の入力を受け取れるように 3要素の配列 {0, 0, 0} で初期化
  vector<int> vec(3);

  // atを使って1つずつ入力
  cin >> vec.at(0) >> vec.at(1) >> vec.at(2);

  // 和を出力
  cout << vec.at(0) + vec.at(1) + vec.at(2) << endl;
}

配列の初期化

次のように書くと、指定した要素数で配列を初期化できます。

vector<型> 配列名(要素数);

vector<int> vec(3);vector<int> vec = {0, 0, 0}とほとんど同じ意味です。

vector<int>の場合は全ての要素が0で初期化されます。
どんな値で初期化されるかは型によって変わります。例えばvector<string> vec(3)と書いた場合、空の文字列の配列{"", "", ""}で初期化されます。

配列変数への入力

配列変数で入力を受け取るには、十分な大きさで配列を初期化した後、.at(i)を使って一つずつ受け取っていく必要があります。

// 3個の入力を受け取れるように 3要素の配列 {0, 0, 0} で初期化
vector<int> vec(3);

// atを使って1つずつ入力
cin >> vec.at(0) >> vec.at(1) >> vec.at(2);

for文を使った入力

入力を配列変数で受け取る場合、for文を使って入力処理を書くのが一般的です。

#include <bits/stdc++.h>
using namespace std;

int main() {
  // 100要素の配列で初期化
  vector<int> vec(100);

  // 100個の入力を受け取る
  for (int i = 0; i < 100; i++) {
    cin >> vec.at(i);
  }

}

入力の個数が多いときでも、for文を使えば簡潔に入力処理を書くことができます。


配列の使い所

配列とfor文を組み合わせると、大量のデータを扱うプログラムを書くことができます。
次の例題を見て下さい。

例題

N人の数学と英語のテストの点数が与えられます。
それぞれの人について、数学と英語の点数の合計点を計算してください。

制約

0≦N≦1000

入力

N
1人目の数学の点数 2人目の数学の点数 ... N人目の数学の点数
1人目の英語の点数 2人目の英語の点数 ... N人目の英語の点数

出力

1人目の数学と英語の合計点
2人目の数学と英語の合計点
\vdots
N人目の数学と英語の合計点

入力例

3
20 100 30
100 5 40

出力例

120
105
70

解答例

Nが小さい場合、配列を使わずにint型の変数だけでこの問題を解くことも可能です。
しかし、Nが大きい場合は配列を使わずに書くのは非常に大変になります。
例えばN=1000だったとき、変数を1000個宣言しなければなりません。

配列とfor文を使えば、Nの大きさに関わらず簡潔に処理を書くことができます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  int N;
  cin >> N;

  vector<int> math(N); // N個の数学の点数データ
  vector<int> english(N); // N個の英語の点数データ

  // 数学の点数データを受け取る
  for (int i = 0; i < N; i++) {
    cin >> math.at(i);
  }

  // 英語の点数データを受け取る
  for (int i = 0; i < N; i++) {
    cin >> english.at(i);
  }

  // 合計点を出力
  for (int i = 0; i < N; i++) {
    cout << math.at(i) + english.at(i) << endl;
  }
}

その他の機能

初期値の指定

次のように書くと、配列の各要素の初期値を指定できます。

vector<型> 配列名(要素数, 初期値);

例えばvector<int> vec(3, 5);と書いた場合、配列変数vec{5, 5, 5}で初期化されます。

要素の追加

配列は文字列のように+=で要素を追加することはできません。
代わりに配列変数.push_backを使えば、配列の末尾に要素を追加することができます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec = { 1, 2, 3 };

  vec.push_back(10); // 末尾に10を追加

  // vecの全要素を出力
  for (int i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
  }
}
実行結果
1
2
3
10

push_backの後の配列変数vecは次の表の通りになっています。

添字 0 1 2 3
要素 1 2 3 10

注意点

範囲外エラー

文字列と同様に、配列も添字の値が正しい範囲内に無いと実行時エラーになります。

次のプログラムは{ 1, 2, 3 }という3要素の配列(有効な添字の値は02)に対し、vec.at(10)で存在しない要素にアクセスしようとしているため、実行時エラーが発生します。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec = { 1, 2, 3 };
  cout << vec.at(10) << endl;
}
実行時エラー
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 10) >= this->size() (which is 3)
終了コード
134

文字列と同様に、1行目の最後で'std::out_of_range'(範囲外)というエラーメッセージが表示されます。
2行目の__n (which is 10) >= this->size() (which is 3)では、「添字の値(10) 配列のサイズ(3)」であるためエラーが発生したということを伝えています。


細かい話

細かい話なので、飛ばして問題を解いても良いです。

vector以外の配列

C++にはvector以外にも配列の書き方があります。次の3つの配列の書き方はほとんど同じ意味です。

vector<int> data(3); // vectorによる配列
int data[3]; // Cの配列
array<int, 3> data; // arrayによる配列

他の人のソースコードを見ると、2つ目の書き方をよく見かけるかもしれません。
しかし、この書き方で宣言した配列は多くの落とし穴があります。基本的にはvectorによる配列を使った方が良いです。

APG4bではvectorによる配列の使用を推奨していますが、AtCoderのコンテストの模範解答ではCの配列が使われることもあります。コンテストに参加する人はどちらも読めるようにしておくと良いでしょう。

atを使わないi番目の要素へのアクセス

今まで配列の要素を指定するときは配列変数.at(添字)と書いてきましたが、配列変数[添字]でも同じことができます。
ただし、この書き方は範囲外の添字を指定してしまったときにエラーメッセージを表示してくれず、何が原因でプログラムが正しく動いていないのかがわかりにくいので、配列変数.at(添字)の書き方を使うようにしましょう

次のプログラムは、配列{ 1, 2, 3 }の最初の要素に2つの方法でアクセスしています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec = { 1, 2, 3 };

  cout << vec.at(0) << endl;
  cout << vec[0] << endl; // .at(0)と同じ
}
実行結果
1
1

安全に[]でi番目の要素へのアクセスする方法

ABC/ARCで他の人の提出を見ると[]を使っている人が多いです。これは[]のほうが短くて読み書きしやすい、または最初に[]を学んで慣れているという理由からです。
先に述べたとおり[]はエラーが発見しにくいですが、#define _GLIBCXX_DEBUGとコードの最初に書くことで.atと同程度に安全に使うことができるようになります。

2021/11/10追記 注意:_GLIBCXX_DEBUGを使用することで、後に登場するpriority_queueなど、一部の処理の実行速度が遅くなってしまうことが確認されています。使用の際はご注意ください。

次のプログラムは{ 1, 2, 3 }という3要素の配列(有効な添字の値は02)に対し、vec[10]で存在しない要素にアクセスしようとしているため、実行時エラーが発生します。

#define _GLIBCXX_DEBUG
#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec = { 1, 2, 3 }; 
  cout << vec[10] << endl; // 実行時エラーが起きる
}
実行時エラー
/usr/include/c++/5/debug/vector:409:
Error: attempt to subscript container with out-of-bounds index 10, but 
container only holds 3 elements.

Objects involved in the operation:
    sequence "this" @ 0x0x401350 {
      type = 

Error: attempt to subscript container with out-of-bounds index 10, but container only holds 3 elements.で3要素の配列に対して境界外の添字10でアクセスしようとしたことを表しています。

.at()を使うか#define _GLIBCXX_DEBUG[]を使うかは好みですが、APG4bでは.at()を統一して使用します。

#define _GLIBCXX_DEBUGはGCC特有の機能です。Clang環境では代わりに#define _LIBCPP_DEBUG 0と書く必要があります。

要素の削除

配列変数.pop_backを使えば配列の末尾の要素を削除することもできます。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec = { 1, 2, 3 };

  vec.pop_back(); // 末尾の要素を削除

  // vecの全要素を出力
  for (int i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
  }
}
実行結果
1
2

pop_backの後の配列変数vecは次の表の通りになっています。

添字 0 1
要素 1 2

配列同士の比較

配列変数同士は==で比較することができます。
==では2つの配列の全要素が一致していたとき、条件式は真になります。

ただし、比較する際はどちらも「配列変数」である必要があり、vec == { 1, 2, 3 }のようには書けないことに注意しましょう。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec1 = { 1, 2, 3 };
  vector<int> vec2 = { 1, 2, 3 };

  if (vec1 == vec2) {
    cout << "OK" << endl;
  }

  /*
  ↓これはコンパイルエラーになる
  if (vec1 == { 1, 2, 3 }) {
    cout << "NG" << endl;
  }
  */
}

配列の初期化その2

配列の初期化は次のように書くこともできます。

vector<型> 配列変数 = vector<型>(要素数, 初期値);

この書き方はすでに宣言してある配列変数を上書きするときにも使えます。 次の例では{10, 10, 10}で初期化してある変数を100要素の配列{2, 2, ... , 2, 2}で上書きしています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> vec(3, 10); // {10, 10, 10} で初期化
  vec = vector<int>(100, 2); // 100要素の配列 {2, 2, ... , 2, 2} で上書き
  cout << vec.at(99) << endl;
}
実行結果
2

大きすぎる配列

配列の要素数が多すぎると、実行時エラーになることがあります。
次の例は要素数が10億の配列を確保しようとして実行時エラーが発生しています。

#include <bits/stdc++.h>
using namespace std;

int main() {
  vector<int> v(1000000000);
  cout << v[0] << endl;
}
終了コード
134

どの程度までの大きさの配列が確保できるかは実行環境によります。

AtCoderの問題の回答として提出してこのエラーが出た場合、またはと表示されます。


問題

リンク先の問題を解いてください。

ABCの問題

ここまでの知識だけで解ける問題をAtCoder Beginner Contestから再び紹介します。練習問題だけでは物足りない人は解いてみてください。
「易しめ」と書いてあるものも今までのABCの問題よりも難易度が高いことに注意してください。
解けない場合、2.01.ループの書き方2.02.多重ループを読んでから再挑戦してみてください。

易しめ

難しめ

配列とループ構文がわかっていれば、AtCoderのコンテストに参加するのに最低限の知識を持っていると言えます。
ぜひリアルタイムでコンテストに挑戦してみてください。

前のページ | 次のページ