※この記事では,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 << "0) n= " << n << endl; // n=5
sum(n); // 値の足し算
cout << "1) n= " << n << endl; // 反映されない(n=5のまま)
sump(&n); // アドレスで渡す
cout << "2) n= " << n << endl;// 反映される(n=8)
int* m; // アドレスで宣言
m = &n; // nのアドレスをコピー
cout << "3) m= " << *m << endl;
sump(m); // "m"がアドレスを示す
cout << "4) m= " << *m << endl;
cout << "5) n= " << n << endl;// nにも反映されている(同じアドレスなので)
return 0; // 以降は省略
}
関数sumは値で受け渡しているのでmain関数では反映なし,sumpのようにアドレスで渡すと反映できます.
配列の要素
つづいて,同じく,sum, sumpを使って,配列の要素について計算.
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を使って,ベクタの要素について計算.
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言語入門 ビギナー編