改めて前回の内容をおさらいをしておくと、
ということをお伝えしました。
今回は 参照渡し についてお話ししたいと思います。
参照渡しとは?
参照渡しも、値渡しと同様に、引数(パラメーター)の記述方法の1つです。
値渡しが、関数の引数(パラメーター)に、値のコピーを渡すのに対して、
参照渡しは、関数の引数(パラメーター)に、データが格納されている場所を渡しています。
関数に
「このデータは、メモリのこの場所にあるから、そこに行ってそのデータ使ってねー ^^) _旦~~」
と言っているわけですね。
これが何を意味するかというと、
値渡し→関数に渡すのは元のデータのコピー→元のデータには変化がない。
参照渡し→関数に渡すのは元のデータのある場所→元のデータに変化がある!
ということなんです。
コピー機に、コピーしたい情報が書かれた紙をセットして、書かれている情報をコピーしたとします。
仮にコピーした紙をシュレッダーにかけたとしても、セットした元々の紙には何の変化もないですよね?
でも、もしセットした元々の紙をシュレッダーに突っ込んだら、元々の紙は言うまでもなく細かく裁断されてしまいます。
参照渡し→オリジナルのデータの位置を関数に渡す処理(→オリジナルのデータに変更が生じる可能性がある)
と言う違いがあります。
関数の目的に合わせて引数の記述を値渡しで行うのか、参照渡しで行うのかが設計されています。(自分で関数を作る場合は設計します)
値渡しは、元のデータを使いたいけれども、元のデータが加工・変化することは望まない(=データのコピーだけあれば事足りる)場合に使われるのに対し、
参照渡しは関数に渡したデータを加工・変化させたい場合に使います。
参照渡しの記述方法
戻り値のデータ型 関数名(引数のデータ型 &変数名)//←参照渡しは&がついている!
{
}
--------------------------------------------------------------------------
戻り値のデータ型 関数名(引数のデータ型 変数名)//←&がついていないこれは値渡し!
{
}
前回の値渡しの記事で使ったサンプルコードを参照渡しに修正すると・・・↓
void OnStart()
{
string varString="こんにちわー";
//exampleFuncton関数を呼び出し、引数にvarStringを設定
exampleFuncton(varString);
Print(varString);
}
//関数「exampleFuncton」を宣言し、仮引数を文字型「passingByValue」に設定
void exampleFuncton(string &passingByValue)//&をつけて参照渡しにする
{
//仮引数「passingByValue」に"こんばんわー"を代入
passingByValue="こんばんわー";
//ログ出力
Print(passingByValue);
}
↑こうなります。
関数「exampleFuncton」の宣言時、仮引数を文字列型のデータ型の後ろ、変数名「passingByValue」の前 の部分に & をつける事によって、参照渡しに設定します。
値渡しの時から変更したのはここだけです。
void exampleFuncton(string &passingByValue)//&をつけて参照渡しにする
exampleFuncton関数の挙動を値渡しの時と、参照渡しの時で比べたのが下の動画になります。
最初値渡しで、MQL5 Program Fileを実行し、その後参照渡しに変更して再コンパイル→実行してみると・・・
OnStart関数内で宣言している変数「varString」をログ出力してみると、
値渡しの時は初期値の ”こんにちわー”のままで変わらなかったのが、
参照渡しにすることによって、”こんばんわー” に変わっていますね。
参照渡しで関数に値を渡すと、元のデータが変更する、ということの意味がお分かりいただけたのではないでしょうか?
配列を参照渡しで渡す
で、基本的に戻り値には、どのデータ型も指定することができるが、return演算子を使って配列全体を返すことはできない
ということを書きました。
じゃあ、関数の処理結果として配列全体を返す手段がないか?というとそんなことはなくて、参照渡しを使うことによって実現できます。
そもそも、配列を引数に指定する場合は参照渡しでしか渡すことができない、という文法上の決まりがあるんです。
void OnStart()
{
//setArray関数のための配列を宣言
double getArray[];
//setArray関数を呼び出し、引数として記述
setArray(getArray);
//ログ出力→7.2が出力される
Print(getArray[2]);
}
//setArray関数を宣言し、引数に参照渡しで配列を指定
void setArray(double &array[])
{
//配列のサイズを3にリサイズする
ArrayResize(array,3);
//配列の各インデックスに値を代入する
array[0]=1.4;
array[1]=4.5;
array[2]=7.2;
}
順を追ってサンプルコードを見ていきましょう。
関数名は 「setArray」
参照渡しにするため & をつけて配列名を記述します。(配列名はarray。配列であることを明示する為に最後に[]をつけます)
続いて、{}内の処理実装部分。
ArrayResize関数について
冒頭のArrayResize関数というのは組み込み関数(MQL5側で事前に用意されている関数)です。
ArrayResize関数の引数について
ArrayResize関数は、第1引数に記述した配列を、第2引数で指定したサイズに修正する処理を行います。
ArrayResize関数の戻り値について
ArrayResize関数の戻り値は、配列の変更後の新しいサイズです。
もし関数が正常に実行された場合、指定した新しいサイズが返されます。
ただし、何らかの理由でサイズ変更が失敗した場合は、-1が返されます。
ArrayResize関数を使う上での注意点
※サイズ変更ができる配列は動的配列(配列サイズが指定されていない配列)のみです。静的配列では、配列のサイズを宣言時に指定し、その後はサイズを変更することができませんのでご注意ください。
※プログラミング文脈における「静的」と「動的」という言葉についての詳細は↓の記事をご参照ください
ArrayResize関数とArraySize関数の違い
ArrayResize関数と名前が似た関数として、ArraySize関数があります。
名前こそ似ていますが、関数の役割としては全く異なります。
ArrayResize関数が配列のサイズを設定したり変更したりするのに用いるのに対し、
ArraySize関数は配列の現在のサイズを取得するために使用されます。
配列のサイズを規定・変更したいのであればArrayResize関数を使い、配列のサイズを確認したいのであればArraySize関数数を使う、という区別をして頂ければと思います。
ArraySize関数については↓のリンク先セクションをご覧ください。
さて、話をサンプルコードの中身に戻します。
サンプルコード中の配列「array」はこの配列サイズを「3」、つまり値を入れられる変数の連なりを3つ用意した、ということを意味します。
配列のサイズを「3」と規定した上で、
1番目の変数→array[0]に1.4、
2番目の変数→array[1]=4.5、
3番目の変数→array[2]=7.2
これがsetArray関数の全容です。
続いてメインプログラムである、OnStart関数側での処理を見ていきましょう。
setArray関数を稼働させるために、引数にあてがう配列を宣言します。
//setArray関数のための配列を宣言
double getArray[];
データ型の種類はdouble型で 配列名はgetArrayです。配列であることを明示する為に[]をつけます。
続いてsetArray関数を呼び出し、()内に配列「getArray」を記述します。
//setArray関数を呼び出し、引数として記述
setArray(getArray);
※この時、[]は必要ありません。関数の実引数として配列名を記述する時は、[]は付けない仕様になっています。
setArray関数を呼び出したことで、getArray配列は配列サイズが「3」にリサイズされ、1~3番目の変数に初期値が代入されているはずです。
最後に試しに、Print関数でgetArray配列の3番目の変数に格納されている値を確認してみましょう。
//ログ出力→7.2が出力される
Print(getArray[2]);
↓の動画を見ると・・・
予想通り「7.2」という結果がログ出力されました。
構造体も引数に渡すときは参照渡しで渡す
配列と同様に、参照渡しでしか引数に指定できないものが2つあります。
構造体は複数のデータ型の変数をひとまとめにしたカスタムデータ型です。
※構造体については詳しくは↓の記事をご覧ください。
クラスというのは、まだ一切言及できていませんが、簡単に言うと
のことです。
データを格納する変数と、処理を実行する関数、これらを一元管理できる便利なプログラム概念です。
・・・おそらく現段階では全然ピンとこないと思いますが、後の講座記事で十分解説するのでご安心ください。↓
この3つが、引数に指定する時は参照渡しでしか渡せない、ということだけ今は押さえておいてください。
void OnStart()
{
}
struct stExample
{
int stEx1;
double stEx2;
string stEx3;
};
class exampleClass
{
public:
int num;
};
//setObject関数を宣言し、引数に参照渡しで配列を指定
void setObject(exampleClass& ex)
{
}
↑のサンプルコードでは、極めて単純な構造体とクラスを宣言して、それを関数の引数に記述することを目的としたものです。
このサンプルコードを最初は、値渡しでコンパイルしようとすると
「objects are passed by reference only(オブジェクトは参照渡しによってのみ渡されます)」というコンパイルエラーが発生します。
コンパイルエラーを受けて、参照渡しで記述をし直して再コンパイルをすると、エラーが解消された・・・というのが下の動画です。
補足
最後にいくつか細かい参照渡しについての補足をしたいと思います。
補足その1:参照渡しで引数に指定できる配列は動的配列のみ
参照渡しで引数に配列を設定しようとするとき、指定できるのは動的配列のみです。
静的配列を指定しようとすると、“parameter can be a dynamic array only”「動的配列のみが引数に指定できます」というコンパイルエラーが発生します。↓
補足その2:引数のデータ型の後ろにアンパサンド(&)について
参照渡しの際、引数のデータ型の後ろにアンパサンド(&)をつける訳ですが、データ型の後ろであれば(1)データ型の後ろにくっつける、(2)変数の前にくっつける、(3)どちらからも半角スペース以上を置いて独立して書く、 のどれでも構いません
コメント