Help us understand the problem. What is going on with this article?

C++のvectorまとめ

はじめに

C++でプログラミングをしていて,配列の代わりとしてvectorを普段から使用しています.非常に便利なので,vectorの基本的な使い方や個人的に考える利点についてについてまとめます.他にも利点など思いつきましたら,教えていただけると嬉しいです!

vectorとは

cpprefによると,

1) std::vector は動的なサイズの配列をカプセル化するシーケンスコンテナです。
2) std::pmr::vector は多相アロケータを使用するエイリアステンプレートです。

となっている.

私自身,vectorらしい書き方をしているかはわからないが,配列の代わりとして非常に便利である.使っていて配列より便利と感じる点は,

  • var.size()で配列要素数を参照できるため,関数に渡しやすい
  • var.size()でインデックスの上限を指定するとセグエラ起こさない
  • var.resize(n)で要素数を変更できる
  • var.push_back(d)で1つづつ要素を追加できる
  • =でコピーできる
  • 値渡しのように関数の引数にできる(アドレス渡し・参照渡しも可)
  • 関数の戻り値にできる
  • algorithmでソートできる
  • eraseで指定要素のみ削除できる
  • 1行でstringにargvを格納できる

である.これ以上にも便利な点はあるのだろうが,これだけでもvectorを使う価値はありそうだ.

インクルード

vectorを使用するためには,

include
#include <vector>

としてインクルードするとよい.

宣言および初期化

1次元配列

1次元配列のvectorの宣言は以下.Type部にはintdoubleなどの基本的な型のみならず,自分で作成したclassなども使用できる.
ここで,nは要素数を表し,dは初期化する値を表すものとする.

declaration_1d
vector<Type> v;
vector<Type> v();
vector<Type> v(n);
vector<Type> v(n, d);

2次元配列

2次元配列のvectorの宣言は以下.
ここで,mnと同様,要素数を表すものとする.
g++のバージョンによっては>>> >と間に半角スペースがないといけないこともあるので,注意が必要である.

declaration_2d
vector<vector<Type>> vv;
vector<vector<Type>> vv();
vector<vector<Type>> vv(n);
vector<vector<Type>> vv(n, vector<Type>(m));
vector<vector<Type>> vv(n, vector<Type>(m, d));

3次元配列

ここまできたら必要ないかもしれないが,3次元配列のvectorの宣言は以下.
ここで,ln,mと同様,要素数を表すものとする.

declaration_3d
vector<vector<vector<Type>>> vvv;
vector<vector<vector<Type>>> vvv();
vector<vector<vector<Type>>> vvv(n, vector<Type>(m));
vector<vector<vector<Type>>> vvv(n, vector<Type>(m, d));

要素へアクセス

vectorでは,通常の配列と同じ書き方で要素へアクセスすることができる.

1次元配列

例として,v[]i番目にdを代入してそれを出力する場合は以下となる.

element_access
v[i] = d;
cout << v[i] << endl;

2次元配列

例として,v[][]i,j番目にdを代入してそれを出力する場合は以下となる.

element_access
v[i][j] = d;
cout << v[i][j] << endl;

3次元配列

例として,v[][][]i,j,k番目にdを代入してそれを出力する場合は以下となる.

element_access
v[i][j][k] = d;
cout << v[i][j][k] << endl;

要素数の変更

vectorでは,配列の要素数を変更することができる.

1次元配列

配列v[]の要素数をn個に変更する場合は以下.

vector_resize_1
v.resize(n);

2次元配列

配列vv[][]の要素数をn x nに変更する場合は以下.

vector_resize_2d_1
vv.resize(n);
for(size_t i=0; i<n; i++){
  vv[i].resize(n);
}
vector_resize_2d_2
vv.resize(n, vector<Type>(n));

末尾に要素を追加

1次元配列

v[]の末尾にdを追加する場合は以下.

vector_append_data_1d
v.push_back(d);

2次元配列

vv[][]i番目の末尾にdを追加する場合は以下.

vector_apped_data_2d
vv[i].push_back(d);

配列のコピー

同じ配列を作成

vector配列をコピーする方法はいくつかある.ここでは,v1[]v2[]にコピーする方法をいくつか紹介する.

vector_copy_1
v2 = v1
vector_copy_2
v2.resize(v1.size());
for(size_t i=0; i<v1.size(); i++){
  v2[i] = v1[i];
}
vector_copy_3
copy(v1.begin(), v1.end(), v2.begin());

末尾に追加

v1[]v2[]の末尾に追加する方法は以下.3つめの方が完結なので嬉しい.

vector_append_to_end_1
int sizeTemp = v2.size();
v2.resize(sizeTemp+v1.size());
for(size_t i=0; i<v1.size(); i++){
  v2[i+sizeTemp] = v1[i];
}
vector_append_to_end_2
for(size_t i=0; i<v1.size(); i++){
  v2.push_back(v1[i]);
}
vector_append_to_end_3
copy(v1.begin(), v1.end(), back_inserter(v2));

指定範囲をコピー

v1[]a~bv2[]にコピーする方法は以下.後者の方が完結なので嬉しい.

vector_specified_range_copy_1
v2.resize(b-a);
for(size_t i=0; i<v2.size(); i++){
  v2[i] = v1[a+i];
}
vector_specified_range_copy_2
copy(v1.begin()+a, v1.begin()+b, v2.begin());

関数の引数にする

vectorは簡単に関数の引数にすることができる.また,vectorのメンバ関数から要素数を参照することができるため,関数内で簡単にループ数を指定できる.ここでは,総和を求めるプログラム配列をターミナルで視覚化するプログラムの2つを例として挙げる.ただし,特に理由がない限りは,処理時間などの理由から参照渡しを用いた方が良い.加えて,参照渡しをする際には変数を変更するつもりがなければconstをつけた方が良い.

配列の総和を求めるプログラムの例

配列の総和を求める場合は以下のような関数を定義すると良い.
値渡しと参照渡しそれぞれについての関数を記述したのだが,これらについて,データ数N = 10000000でこれらの処理時間をtimeコマンドで計測したところ,0.105sの差で参照渡しの方が高速であった.

関数の呼び出しに関しては,どちらも

sumVector(v);

と,同じである.

値渡し

sum_function_example
Type sumVector(vector<Type> v){
  Type sum = 0;
  for(size_t i=0; i<v.size(); i++){
    sum += v[i];
  }
  return sum;
}

参照渡し

sum_function_example
Type sumVector(const vector<Type>& v){
  Type sum = 0;
  for(size_t i=0; i<v.size(); i++){
    sum += v[i];
  }
  return sum;
}

配列をターミナルで視覚化するプログラムの例

vectorでは.size()で要素数数を参照できるため,以下のように関数内で要素数を用いた処理が可能である.この時,桁が違う数も扱う際に揃えたい場合は,cout << setw(n) << v[i]のようにして桁数を指定してしまえば良い.

値渡し

view_function_example
void view(vector<Type> v){
  for(size_t i=0; i<v.size(); i++){
    cout << v[i] << " ";
  }
  cout << endl;
}
void view(vector<vector<Type>> vv){
  for(size_t i=0; i<vv.size(); i++){
    for(size_t j=0; j<vv[i].size(); j++){
      cout << vv[i][j] << " ";
    }
    cout << endl;
  }
}

参照渡し

view_function_example
void view(const vector<Type>& v){
  for(size_t i=0; i<v.size(); i++){
    cout << v[i] << " ";
  }
  cout << endl;
}
void view(const vector<vector<Type>>& vv){
  for(size_t i=0; i<vv.size(); i++){
    for(size_t j=0; j<vv[i].size(); j++){
      cout << vv[i][j] << " ";
    }
    cout << endl;
  }
}

関数の戻り値にする

vectorの便利な点としては,簡単に戻り値にできる点である.
int v[n];のように宣言した配列であれば,戻り値にできないのでアドレス渡しで余計に配列を引数にする必要があるのだが,vectorを使うと簡潔なプログラムを記述することができる.
しかし,便利であるのに対して,戻り値にする際にコピーする時間がかかってしまうため,どちらを使用するのかは使用要素数などで変えた方が良さそうである.vectorで余計に引数にすることもできる.

ここでは,ベクトルのスカラー倍をする関数を例としてあげる.

以下の2つの関数の処理時間の差は配列の要素数N=10000000の時に0.031sと後者の方が高速であった.

戻り値にvector

scalar_multiplication_returnVector
vector<int> scalar_multiplication(const vector<int>& v, int s){
  vector<int> mul(v.size());
  for(size_t i=0; i<mul.size(); i++){
    mul[i] = v[i]*s;
  }
  return mul;
}

計算結果を出力するvectorも引数にする

scalar_multiplication
void scalar_multiplication(const vector<int>& v, int s, vector<int>& res){
  res.resize(v.size());
  for(size_t i=0; i<res.size(); i++){
    res[i] = v[i]*s;
  }
}

ソート

昇順ソート

ascending_sort
sort(v.begin(), v.end());

降順ソート

descending_sort
sort(v.begin(), v.end(), greater<int>());

指定要素の削除

vectorでは.erase()を使用することで任意の要素を削除することができる.
先頭の要素を削除する場合は以下.

vector_erase_begin
v.erase(v.begin());

末尾を削除する場合は以下.

vector_erase_end
v.erase(v.end());

先頭からi番目の要素を削除する場合は以下.

vector_erase_begin5
v.erase(v.begin()+i);

argvをvectorに格納する方法

コマンドライン引数を取得するために,int argcchar **argvを使う.しかし,個人的にはcharは使いづらいので,これを一発でvector<string>に格納するのが好きである.書式は以下.

argv_to_args
#include <vector>
#include <string>

main(int argc, char **argv){
  vector<string> args(argv, argv+argc);
}

Range-Based-for-loopの例

コメントで,「ループはインデックスを用いたループよりもRange-based-loopを用いた方が良い」とあったので,上記のview()関数のRange-Basedのバージョンを記載する.

view_function_range_based
void view(const vector<Type>& v){
  for(const auto& e : v){
    cout << e << " ";
  }
  cout << endl;
}
void view(const vector<vector<int>>& vv){
  for(const auto& v : vv){
    for(const auto& e : v){
      cout << e << " ";
    }
    cout << endl;
  }
}

参考はcpprefjpのこのページである.eelementの頭文字と考えられる.

おわりに

まだまだプログラミングをしていくので,他にも便利なことがあったら追記していくつもりです.
何かオススメがあったらコメントで教えていただけると嬉しいです.

ysuzuki19
電気電子系の大学院生です. 詳しい人の意見を仰ぐ事とモチベ維持のためにQiitaに少しづつ上げていけたらなと思っています. Qiitaで自分が書いた記事をブラッシュアップしつつGithubPagesに移行していこうと試みているところです。
https://ysuzuki19.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした