はじめに
C++でプログラミングをしていて,配列の代わりとしてvector
を普段から使用しています.非常に便利なので,vector
の基本的な使い方や個人的に考える利点についてについてまとめます.他にも利点など思いつきましたら,教えていただけると嬉しいです!
vectorとは
cpprefによると,
- std::vector は動的なサイズの配列をカプセル化するシーケンスコンテナです。
- std::pmr::vector は多相アロケータを使用するエイリアステンプレートです。
となっている.
私自身,vector
らしい書き方をしているかはわからないが,配列の代わりとして非常に便利である.使っていて配列より便利と感じる点は,
- var.size()で配列要素数を参照できるため,関数に渡しやすい
- var.size()でインデックスの上限を指定するとセグエラ起こさない
- var.resize(n)で要素数を変更できる
- var.push_back(d)で1つづつ要素を追加できる
- =でコピーできる
- 値渡しのように関数の引数にできる(アドレス渡し・参照渡しも可)
- 関数の戻り値にできる
-
algorithm
でソートできる -
erase
で指定要素のみ削除できる - 1行で
string
にargvを格納できる
である.これ以上にも便利な点はあるのだろうが,これだけでもvector
を使う価値はありそうだ.
インクルード
vector
を使用するためには,
#include <vector>
としてインクルードするとよい.
宣言および初期化
1次元配列
1次元配列のvector
の宣言は以下.Type
部にはint
,double
などの基本的な型のみならず,自分で作成したclass
なども使用できる.
ここで,n
は要素数を表し,d
は初期化する値を表すものとする.
vector<Type> v;
vector<Type> v();
vector<Type> v(n);
vector<Type> v(n, d);
2次元配列
2次元配列のvector
の宣言は以下.
ここで,m
はn
と同様,要素数を表すものとする.
g++
のバージョンによっては>>
は> >
と間に半角スペースがないといけないこともあるので,注意が必要である.
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
の宣言は以下.
ここで,l
はn,m
と同様,要素数を表すものとする.
vector<vector<vector<Type>>> vvv;
vector<vector<vector<Type>>> vvv();
vector<vector<vector<Type>>> vvv(n, vector<vector<Type>>(m, vector<Type>(l)));
vector<vector<vector<Type>>> vvv(n, vector<vector<Type>>(m, vector<Type>(l, d)));
要素へアクセス
vector
では,通常の配列と同じ書き方で要素へアクセスすることができる.
1次元配列
例として,v[]
のi
番目にd
を代入してそれを出力する場合は以下となる.
v[i] = d;
cout << v[i] << endl;
2次元配列
例として,v[][]
のi,j
番目にd
を代入してそれを出力する場合は以下となる.
v[i][j] = d;
cout << v[i][j] << endl;
3次元配列
例として,v[][][]
のi,j,k
番目にd
を代入してそれを出力する場合は以下となる.
v[i][j][k] = d;
cout << v[i][j][k] << endl;
要素数の変更
vector
では,配列の要素数を変更することができる.
1次元配列
配列v[]
の要素数をn
個に変更する場合は以下.
v.resize(n);
2次元配列
配列vv[][]
の要素数をn x n
に変更する場合は以下.
vv.resize(n);
for(size_t i=0; i<n; i++){
vv[i].resize(n);
}
vv.resize(n, vector<Type>(n));
末尾に要素を追加
1次元配列
v[]
の末尾にd
を追加する場合は以下.
v.push_back(d);
2次元配列
vv[][]
のi
番目の末尾にd
を追加する場合は以下.
vv[i].push_back(d);
配列のコピー
同じ配列を作成
vector
配列をコピーする方法はいくつかある.ここでは,v1[]
をv2[]
にコピーする方法をいくつか紹介する.
v2 = v1
v2.resize(v1.size());
for(size_t i=0; i<v1.size(); i++){
v2[i] = v1[i];
}
copy(v1.begin(), v1.end(), v2.begin());
末尾に追加
v1[]
をv2[]
の末尾に追加する方法は以下.3つめの方が完結なので嬉しい.
int sizeTemp = v2.size();
v2.resize(sizeTemp+v1.size());
for(size_t i=0; i<v1.size(); i++){
v2[i+sizeTemp] = v1[i];
}
for(size_t i=0; i<v1.size(); i++){
v2.push_back(v1[i]);
}
copy(v1.begin(), v1.end(), back_inserter(v2));
指定範囲をコピー
v1[]
のa~b
をv2[]
にコピーする方法は以下.後者の方が完結なので嬉しい.
v2.resize(b-a);
for(size_t i=0; i<v2.size(); i++){
v2[i] = v1[a+i];
}
copy(v1.begin()+a, v1.begin()+b, v2.begin());
関数の引数にする
vector
は簡単に関数の引数にすることができる.また,vector
のメンバ関数から要素数を参照することができるため,関数内で簡単にループ数を指定できる.ここでは,総和を求めるプログラム
と配列をターミナルで視覚化するプログラム
の2つを例として挙げる.ただし,特に理由がない限りは,処理時間などの理由から参照渡しを用いた方が良い.加えて,参照渡しをする際には変数を変更するつもりがなければconst
をつけた方が良い.
配列の総和を求めるプログラムの例
配列の総和を求める場合は以下のような関数を定義すると良い.
値渡しと参照渡しそれぞれについての関数を記述したのだが,これらについて,データ数N = 10000000
でこれらの処理時間をtime
コマンドで計測したところ,0.105s
の差で参照渡しの方が高速であった.
関数の呼び出しに関しては,どちらも
sumVector(v);
と,同じである.
値渡し
Type sumVector(vector<Type> v){
Type sum = 0;
for(size_t i=0; i<v.size(); i++){
sum += v[i];
}
return sum;
}
参照渡し
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]
のようにして桁数を指定してしまえば良い.
値渡し
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;
}
}
参照渡し
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
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
も引数にする
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;
}
}
ソート
昇順ソート
sort(v.begin(), v.end());
降順ソート
sort(v.begin(), v.end(), greater<int>());
指定要素の削除
vector
では.erase()
を使用することで任意の要素を削除することができる.
先頭の要素を削除する場合は以下.
v.erase(v.begin());
末尾を削除する場合は以下.
v.erase(v.end() - 1);
先頭からi
番目の要素を削除する場合は以下.
v.erase(v.begin()+i);
argvをvectorに格納する方法
コマンドライン引数を取得するために,int argc
やchar **argv
を使う.しかし,個人的にはchar
は使いづらいので,これを一発でvector<string>
に格納するのが好きである.書式は以下.
#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のバージョンを記載する.
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のこのページである.e
はelement
の頭文字と考えられる.
おわりに
まだまだプログラミングをしていくので,他にも便利なことがあったら追記していくつもりです.
何かオススメがあったらコメントで教えていただけると嬉しいです.