C
C++

引数のポインタ周り(配列,ベクタ,ベクタのコピー)

※この記事では,Ubuntu16.04にインストールしたg++コンパイラで実行しています.

C++で関数にポインタを渡して計算するときに,いつも試行錯誤しながら(エラー出しながら)やっているので,整理して,メモ.

基本

まず,はじめに基本編.int型の受け渡し.

#include <iostream>
#include <vector>
using namespace std;   /// この三行は以下共通

void sum(int n){    /// 値を引数とする
    n = n + 3;
}
void sump(int* n){  // アドレスを引数とする
    *n = *n + 3;    // 値で足し算
}

int main(int argc, char** argv) { // 以降は省略
    int n = 5;
    cout << "n= " << n << endl; // n=5
    sum(n);     // 値の足し算
    cout << "n= " << n << endl; // 反映されない(n=5のまま)

    sump(&n);   // アドレスで渡す
    cout << "n= " << n << endl;  // n=8

    return 0; // 以降は省略
}

関数sumは値で受け渡しているのでmain関数では反映なし,sumpのようにアドレスで渡すと反映できます.

配列の要素

つづいて,同じく,sum, sumpを使って,配列の要素について計算.

main.cpp
    int ar[2] = {20, 18};
    cout << "ar[0]= " << ar[0] << endl;
    sum(ar[0]);     // 値の足し算
    cout << "ar[0]= " << ar[0] << endl; // 反映されない

    sump(&ar[0]);   // アドレスで渡す
    cout << "ar[0]= " << ar[0] << endl;
    sump(ar);   // 先頭アドレスを渡す
    cout << "ar[0]= " << ar[0] << endl;

    cout << "ar[1]= " << ar[1] << endl;
    sump(ar+1);   // アドレスで渡す
    cout << "ar[1]= " << ar[1] << endl;

配列要素のアドレスの渡し方は,ここでは,"&ar[x]"と"ar+x"の二つを使っています.
"&ar[x]"と関数に渡して,関数の引数としては"(int* n)"と受ける.関数内では,"(int)*n"と使う.この辺でよく混乱します.
ちなみに,アドレス,リスクはあたいと覚えるそうです.あとは,独自ですが,"int* n"とみてnはintのポインタ,"int *n"とみて*nはintとイメージしています.

ベクタの要素

つづいて,sum, sumpを使って,ベクタの要素について計算.

main.cpp
    std::vector<int> vec(2);
    vec[0] = 111;
    vec[1] = 121;
    cout << "vec[0]= " << vec[0] << endl;
    sum(vec[0]);
    cout << "vec[0]= " << vec[0] << endl; // 反映されない
    sump(&vec[0]);   // アドレスで渡す
    cout << "vec[0]= " << vec[0] << endl;
    sump(vec.data());   // 先頭アドレスを渡す
    cout << "vec[0]= " << vec[0] << endl;
    cout << "vec[1]= " << vec[1] << endl;
    sump(vec.data()+1);   // アドレスを渡す
    cout << "vec[1]= " << vec[1] << endl;

ベクタ要素のアドレスの渡し方は,ここでは,"&vec[x]"と"vec.data()+x"の二つを使っています.

配列・ベクタ

次に,配列,ベクタごとの計算.新たに関数を定義して,

void sumarray(int ar[], int length){ // 配列の長さは自分で指定
    for(int i = 0; i < length; ++i) ar[i] += 3;
}
void sumvec(vector<int> &vec){
    for(int i = 0; i < vec.size(); ++i) vec[i] += 3;
}
int main(int argc, char** argv) {
    // 初期化はそのまま

    cout << "ar[0]= " << ar[0] << " ar[1]= " << ar[1] << endl;
    sumarray(ar, 2); // 先頭のアドレスを渡す
    cout << "ar[0]= " << ar[0] << " ar[1]= " << ar[1] << endl;
    cout << "vec[0]= " << vec[0] << " vec[1]= " << vec[1] << endl;
    sumvec(vec); // 先頭のアドレスを渡す
    cout << "vec[0]= " << vec[0] << " vec[1]= " << vec[1] << endl;

配列ではsump(ar)もsumarray(ar, 2)もOKですが,ベクタではsump(vec)はダメで,sumvec(vec.data())もダメです.引数の指定が違うので.

ベクタの場合は,イテレータを使う方法もあります.

void sumvecitr(vector<int> &vec){
    vector<int>::iterator itrvec = vec.begin(); // イテレータ
    for(; itrvec != vec.end(); ++itrvec){
        *itrvec += 3;
    }
}
//
sumvecitr(vec);
//

で計算できます.

ベクタ(関数内コピーあり)

やっと本題.関数内でベクタをコピーして計算する場合.

void sum_ncpvec(vector<int> &vec){
    vector<int> cpvec = vec; // コピー
    for(int i = 0; i < cpvec.size(); ++i){
        cpvec[i] += 3;
    }
}
void sum_cpvec(vector<int> &vec){
    vector<int>* cpvec = &vec; // アドレスコピー
    for(int i = 0; i < (*cpvec).size(); ++i){
        (*cpvec)[i] += 3;
    }
}

int main(int argc, char** argv) {
    // 初期化はそのまま
    sum_ncpvec(vec);
    cout << "vec[0]= " << vec[0] << " vec[1]= " << vec[1] << endl;  // 反映されない
    sum_cpvec(vec);
    cout << "vec[0]= " << vec[0] << " vec[1]= " << vec[1] << endl;

当然ですが,sum_ncpvecは関数内でのコピー・計算なので,mainでは反映されません.反映したい場合は,アドレス(&vec)をvector<int>* cpvecにコピーする必要があります.関数内の計算では,(*cpvec)をvector<int>として扱います.

コピーしなくてもよい場合は多いと思いますが,vector<クラス>を扱っていて,自作クラス内のメンバ変数を読んだりすると,どんどん文が長くなるのでコピーしたら嵌ったので,整理しました.

参考

直接的な引用はないですが,参照はこちら.
C言語の引数に多次元配列を渡す
C++ 動的配列クラス std::vector 入門 - イテレータ
新C言語入門 ビギナー編