【C++】関数の引数として配列・vectorを渡す方法(値渡し、ポインター渡し、参照渡し)とconst修飾子の有無

タイトル C++
スポンサーリンク

C++を勉強し始めの多くの人が躓くポイントの1つが,値渡し,ポインター渡し,参照渡しの使い方と使い分けです.私も例外ではなく,初めは混乱してしまいました.下の記事など,分かりやすい解説をしているサイトはいくつもあります.

C++ 値渡し、ポインタ渡し、参照渡しを使い分けよう – Qiita

しかし,「配列」を関数の引数に渡す場合となると,もう一段階深い理解が問われます.

そこで,本記事では

  • 配列を関数の引数に渡す方法と注意点
  • std::vector型を関数の引数に渡す方法と注意点

に特化して,値渡し,ポインター渡し,参照渡しについて解説します.そして,初学者の悩むもう1つのポイントである「const修飾子」の使いどころについても触れたいと思います.

スポンサーリンク

1. 配列を関数に渡す場合

1.1 関数の引数への「配列の値渡し」は不可能

C++では,配列のコピーを作ることはできません.配列は,暗黙的にポインターに変換されます.厳密には,配列の先頭の要素のアドレスを指すポインターに変換されます.

したがって,次のコードはエラーとなります.

int array[3] = {1, 2, 3};
int array_copy = array;
// error: invalid conversion from ‘int*’ to ‘int’

これは,配列を関数の引数として渡す場合も同様です.すなわち,関数に配列を値渡しすることは不可能です.

#include <iostream>
using namespace std;

void show(int array, int size)
{
    for (int i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    cout << "array: " << array << endl;
}

int main()
{
    int arr[3] = {1, 2, 3};
    show(arr, 3);
}
// error: invalid conversion from ‘int*’ to ‘int’

1.2 ポインター渡し(1)

関数の引数として,配列の(先頭の)ポインターを渡すことはできます.次のコード例では,引数を

int array[]

と指定しており,一見,int型の配列の値渡しに見えます.しかし、実際はポインターへの暗黙変換が行われており,arrayはポインター変数として渡されます.配列を変数に渡すとき,先頭のアドレスを指すポインターが渡されると述べました.しかし,配列の先頭以外の要素にアクセスするにはどうしたらよいでしょうか?

int* ptr = array;

このように,配列をポインター変数ptrに渡す場合を考えます.ポインターなので,型名に*を付けていることに注意しましょう.配列の先頭以外の要素は,ポインター変数ptrに添え字演算子を作用させることで,アクセスすることができます(先頭は[0]でアクセス可能).

#include <iostream>
using namespace std;

// ポインター渡し
void show(int* array, int size)
{
    for (int i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    cout << "array: " << array << endl;
}

int main()
{
    int arr[3] = {1, 2, 3};
    show(arr, 3);
}

出力

arr: 0x7ffedddf018c
1 2 3 
array: 0x7ffedddf018c

1.3 ポインター渡し(2)

void show(int* array, int size); // (1)
void show(int array[], int size); // (2)

上の2つの書き方は,どちらも同様で,配列のポインターが渡されます.

#include <iostream>
using namespace std;

// ポインター渡し
void show(int array[], int size)
{
    for (int i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    cout << "array: " << array << endl;
}

int main()
{
    int arr[3] = {1, 2, 3};
    show(arr, 3);
}

出力

arr: 0x7ffeb21d667c
1 2 3 
array: 0x7ffeb21d667c

1.4 参照渡し

参照を渡す際には,配列のサイズ指定が必要であることに注意します.これは面倒なことにも思えます.しかし,逆に言えば,ポインター渡しとは異なり,配列の範囲を超えてアクセスする危険性はありません

#include <iostream>
using namespace std;

// 参照渡し
void show(int (&array)[3], int size)
{
    for (int i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    cout << "array: " << array << endl;
}

int main()
{
    int arr[3] = {1, 2, 3};
    cout << "arr: " << arr << endl;
    show(arr, 3);
}

出力

arr: 0x7ffcc1af61cc
1 2 3 
array: 0x7ffcc1af61cc
スポンサーリンク

2. std::vector型を関数に渡す場合

2.1 値渡し

vector型については,値渡しが可能です.下のコード例では,arrというvector型のオブジェクトを生成し,関数show()にarrを値渡ししています.関数の内部で,arrのコピーが作られるため,出力例の2つのvectorオブジェクトのアドレス(関数外->arr,関数内->array)は異なります

#include <iostream>
#include <vector>
using namespace std;

// 値渡し
void show(vector<int> array)
{
    // 範囲for文
    for (int elem : array)
    {
        cout << elem << " ";
    }
    cout << endl;
    cout << "array: " << &array << endl;
}

int main()
{
    vector<int> arr = {1, 2, 3};
    cout << "arr: " << &arr << endl;
    show(arr);
}

出力

arr: 0x7fff6678f570
1 2 3 
array: 0x7fff6678f590

2.2 参照渡し

#include <iostream>
#include <vector>
using namespace std;

// 参照渡し
void show(vector<int>& array, int size)
{
    // 範囲for文
    for (int i = 0; i < size; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;
    cout << "array: " << &array << endl;
}

int main()
{
    vector<int> arr = {1, 2, 3};
    cout << "arr: " << &arr << endl;
    show(arr, arr.size());
}

出力

arr: 0x7ffc3b7794d0
1 2 3 
array: 0x7ffc3b7794d0

2.3 iterator(イテレータ)を使う

関数への配列の渡し方とは少し話題が異なりますが,vector型では,配列におけるポインターの代わりに,イテレータを使うことで,各要素に簡単にアクセスすることができます

詳細はこちら
【C++入門】vector型の宣言と関数の使い方総まとめ(algorithm)

#include <iostream>
#include <vector>
using namespace std;

// 値渡し
void show(vector<int> array)
{
    for (auto iter = array.begin(); iter != array.end(); ++iter)
    {
        cout << *iter << " ";
    }
    cout << endl;
    cout << "array: " << &array <<
    endl;
}

int main()
{
    vector<int> arr = {1, 2, 3};
    cout << "arr: " << &arr << endl;
    show(arr);
}

出力

arr: 0x7ffd935d52b0
1 2 3 
array: 0x7ffd935d52d0
スポンサーリンク

3. 値渡し,ポインター渡し,参照渡しのどれが適切か?

関数の引数として配列を渡したい場合,値渡しはできません.基本的には,参照渡し又はポインター渡し(後述のconstポインター/const参照でもOK)のどちらでも,問題はないと思われます.しかし,ポインター渡しの場合は,配列の要素として定義していない範囲にアクセスしてしまう可能性があるので,注意が必要です.

関数の引数としてvector型を渡したい場合,値渡しが可能です.しかし,値渡しでは,引数として渡したオブジェクトのコピーが行われます.そのため,例えば10000個の要素を持ったvector型オブジェクトを値渡しすると,10000個の要素のコピー作成処理と,メモリ領域の確保が発生します.したがって,値渡しは無駄なコピーが発生していまうことから,あまりメリットがありません.

vectot型については,基本的には参照渡し(又は,後述のconst参照渡し)が適切であると考えられます.

4. const修飾子を付ける

最後に,ポインター渡しや参照渡しにおいて,参照先を変更したくない場合は,const修飾子を付けると安心です.これは,値渡し,ポインター渡しどちらでも可能です.もちろん,参照先の配列(vector)の要素を書き換えたいときは,constを付ける必要はありません(書き換えようとするとエラーになります).

constポインター(配列)

function(const type* array)

const参照(配列)

function(const type& array)

const参照(vector)

function(const vector<type>& vec)

コメント