[ 前へ | 目次 | 次へ ]
Japanese English

関数

ここでは、処理をまとめる重要な手段である、関数の仕組み扱います。

スポンサーリンク


関数

例えば、ある適当な数 a,b に対して、「 a * a + b * b 」といった値をプログラム中のいくつもの箇所で行いたい場合があったとします。

このような場合、必要な箇所すべてに「 a * a + b * b 」を書いていってもいいのですが、それは少し面倒です。 特に、あとでやはり「 a * a + b * b - 2 」にしたい、等といった事が生じると、いくつもの箇所を書き直す事になってしまいます。

このような場合は、あらかじめ関数を定義しておくと便利です。関数は、変数の組を受け取って、 それに対する処理を行い、結果を出力するための仕組みです。

関数は、以下のような形で定義します:

戻り値の変数型   関数名( 引数宣言1, 引数宣言2, … ) {
    処理内容 ;
    return 戻り値 ;
}

ここで引数とは、関数が処理に使用するために受け取る変数/値のことです。 恐らく「引き受ける数」などから省略された用語なので、「ひきすう」と発音します。

※ より厳密な呼び方では、上のように、関数定義の中で宣言されている引数の変数は「仮引数(かりひきすう)」と呼ばれます。それに対して、関数を呼び出す側が、実際に渡した引数の値は「実引数(じつひきすう)」と呼ばれます。しかしながら、混同を防ぎたい特別な場面以外では、どちらも単に引数と呼ばれる事が多いです。

また、戻り値とは、関数が処理結果として出力する値の事です。 恐らく「関数から戻ってくる値」などから省略された用語です。 関数が戻り値を出力する事を、「値を返す(かえす)」などと言ったりします。そのため、「返り値(かえりち)」という呼び方もあります。

「 a * a + b * b 」を処理する関数の例

例として、ある適当な整数 a, b に対して、「 a * a + b * b 」の値を求めて返す関数を定義してみましょう:

この関数は、int 型の a,b を受け取り、内部で value という値に a * a + b * b を計算して代入し、それを戻り値として返します。

それでは、実際にこの関数を、プログラム中から呼び出してみましょう。

プログラム中から関数を呼び出すには、そこに「関数名の後にカッコを付けたもの」を記述します。 そしてそのカッコの中に、引数をカンマ記号「 , 」で区切って指定します。

以下のように記述し、実行してみてください:

- 実行結果 -

5

これを実行すると 5 が表示されます。関数fun の中で正しく1 * 1 + 2 * 2 が実行され、戻り値として返ってきた事が分かります。

引数宣言には、書き換え不可能にする「 const 」 も使用可能

ところで、普通の変数の宣言文では、先頭に「 const 」キーワードを付ける事で、書き換え不可能な「定数」にする事ができます。引数の宣言でも、全く同じように「 const 」を付けて宣言すると、関数内でその引数を書き換え不可能にできます。そうする事で、うっかり書き換えてしまうミスを防げるだけでなく、最適化が効きやすくなるという利点もあります。

上のサンプルコードでは、引数 a と b は書き換える必要がないため、実際に const を付けて宣言できます:

動作は先ほどと全く同じですが、関数内で a や b を書き換えようとするとエラーになります。

何も返さない関数

関数は、必ずしも戻り値を返す必要はありません。

戻り値を返さない場合は、「 void 型 」で関数を宣言します。 void とは英語で空(から)の意味で、void 型はその名の通り、戻り値が空である事を意味する、特別な型です。

引数を持たない関数

引数についても、何も持たない事が可能です。その場合は、引数記述部を空白にします。

配列を引数とする関数

関数の引数には、配列を指定する事もできます。それには以下のように記述します:

または、データ型の部分に [ ] を付けた、以下のような記述も可能です。

これで、int 型の配列 a, b を引数に受け取れます。 配列の要素数は、プログラム中から呼び出し時に入力された引数と同じになります。 必要であれば、length( array[ ], int dim ) 関数で要素数を確認する事もできます。

なお、上のように配列を引数に渡した場合、デフォルトでは値渡し(すぐ後で説明します)となります。 つまり、配列の全要素の値がコピーされます。そして、関数内で引数に加えた変更は、呼び出し元に反映されません。

C系の他言語との互換性には注意が必要

多くのC言語系の他言語では、配列引数の中身を、関数内で変更すると、その変更内容が、呼び出し元の(実引数として渡した)配列にも反映されます。というのも、多くの言語においては、「配列を引数に渡す」という処理が、配列のデータ領域のアドレス/参照を渡す処理になっているためです。

一方、VCSSLにおける配列引数の受け渡しは、非配列の引数と同様、デフォルトでは全要素の値をコピーする処理によって行われます。つまり、関数内で配列引数の中身を変更しても、それは呼び出し元の配列には反映されません。反映させたい場合は、引数を「参照渡し」するように、明示的に宣言しておく必要があります(方法は後で説明します)。

この違いは、C系他言語との互換性において、特に注意が必要な点となっています。

配列を戻り値とする関数

VCSSL の関数は、配列を戻り値に返す事もできます。それには以下のように、関数の型宣言の部分に [ ] を付けて宣言します。

これで、戻り値に配列を返す事ができます。プログラム中からこの関数を呼び出し、戻り値を配列に代入すると、その配列の要素数は戻り値と同じものに変更されます。

以下は、実際に配列を返す関数と、その戻り値を受け取る例です:

上のコードでは、関数呼び出し fun( 2, 3 ) の戻り値を、配列変数 m に代入しています。 m の要素数は、戻り値を代入する際に自動で調整されるため、要素数宣言を空の [ ] にしています。

C言語では、関数内で定義された配列を、戻り値として返してはいけない事に注意!
C言語では、関数内で定義された配列を、(ポインタを介して)戻り値として返してはいけません。そのような配列のスコープは、関数の実行が終わった瞬間に抜けるため、その後は配列のメモリ領域が存続している保証が無いためです。一方、いくつかの言語ではそのような事ができるようになっていて、VCSSLでも上の通り可能です。C言語でうっかり同じようなコードを書かないように、注意が必要です。C言語で似たような事をやるには、malloc 関数でヒープ上にメモリを確保したり(開放忘れに要注意)、static 化する(良し悪しは別として)などの対処が必要になります。

引数の値渡しと参照渡し

関数の引数は、ここまでの例のように単純に宣言すると、呼び出し時に値がコピー(代入)される 「 値渡し 」となります。

つまり、関数内で引数の値を変更しても、それが呼び出し元には反映されません。

それに対して、引数の変数名の前にアンパサンド記号「 & 」 を付けて宣言すると、 呼び出し元にも変更が反映される「 参照渡し 」という挙動になります:

- 実行結果 -

i=0   j=1

上の例では、VCSSL コンソールに「 i=0 j=1 」と表示されます。参照渡しで j の変更が反映されました。

配列の参照渡し

配列の引数を参照渡しする事もできます。その場合も以下のように、配列名の前にアンパサンド記号「 & 」 を付けて宣言します:

ただし参照渡しには、以下で述るように、いくつかの注意点や制限、デメリットなどがあります。それについては配列の場合でも同様です。

参照渡しの注意点

参照渡しは、データの値そのものではなく、「データがある場所」を渡すという仕組み上、いくつかの制約や注意点があります。

参照渡しができない場合もある

参照渡しは、常に行えるわけではなく、できない場合もあります。

例えば別の関数の戻り値を、参照渡しで引数として直接受け取る事はできません。 そういった場合は、関数の戻り値を一度別の変数に格納してから、その変数を参照渡しする必要があります。

配列要素の参照渡しには特に注意が必要

また、注意が必要なのが、配列の要素を参照渡しする場合です。

例えば、参照渡しで引数を受け取る関数 fun があったとして、fun( a[ i ] ) ように配列の要素を参照渡しした場合、関数の処理が終わるまで、関数外でインデックス変数 i の値を変更してはいけません。 例えば、fun( a[ i ] ) をコールした時点で i が 2 であったものを、処理途中に外部から i を 3 に切り替えるような事をしてはいけません。行った場合の動作は未定義です。

また、配列要素を参照渡しする場合、その関数の処理が終わるまでは、alloc 関数などで配列を再確保してはいけません。こちらも、行うと未定義の動作となります。

つまるところ、配列の要素を参照渡しする際には、処理中にその要素のアドレスが変わったり、参照の経路が曖昧になるような事を行ったりする事がないように、注意を払う必要があります。 これは、例えるなら、「 あなたが何か、住所を書く必要がある書類の手続きをしている最中に、引っ越して住所を変えてはいけない 」という事と、まさに同じような制限です。

参照渡しの利点とデメリット

参照渡しで受け取った引数に対する演算は、行える最適化に制約が多いため、処理効率が少し悪くなりがちです。そのため、重い計算処理を行う関数で、計算に頻繁に絡んでくる値を、参照渡しで受け取る事は避けた方が無難です。

半面、引数に渡す配列のサイズがかなり大きい時などは、値渡しするとコピー処理のコストがかさみ、そこが処理速度のボトルネックになってしまう場合があります。そのような場合には、参照渡しの方が、サイズによらず一定のコストで済むため有利です。

なお、普通に値渡しで引数を受け取って、結果を戻り値で返せば済むような場面などで、 あまり頻繁に参照渡しを用いると、プログラム全体の可読性を低下させる要因になり得ます。一般に、「参照渡しされた引数の値を書き換える」という行為は、可読性の観点ではあまり推奨されない事とされています。

参照渡しを用いる際は、上述のようなメリットとデメリットを秤にかけて、一度じっくり検討するようにしましょう。



スポンサーリンク



Japanese English
この階層の目次
お知らせ

各ソフトウェアをアップデート、リニアングラフのコマンド拡張やVCSSLの英語対応など
2024/02/05 - 各ソフトの一斉アップデートの内容をお知らせします。今回は、リニアングラフのコマンド機能を大幅拡張したのがメインです。また、VCSSLのメッセージ類の英語対応も行いました。

Vnano の Ver.1.1 で実装した反復計算高速化の内側
2024/01/17 - 前回のお知らせ記事の続編です。スクリプトエンジン Vnano の Ver.1.1 において実施した高速化を、エンジン内部の実装面から掘り下げて解説します。

スクリプトエンジン Vnano の Ver.1.1 を公開、同じ内容の反復実行が劇的に高速化
2023/12/22 - スクリプトエンジン Vnano の最新版を公開しました。同じ計算式やスクリプトを反復実行する速さが、大幅に向上しました。次期版リニアングラフ3D内での活用例を交えつつ、詳しく解説します。

新着
[公式ガイドサンプル] 各種の図形や画像を描画する

「VCSSL 2DCG開発ガイド」内のサンプルコードです。図形や画像などを描画します。
2022/12/16
角度の「度」とラジアンとを相互変換し、図示もするツール

45度などの「度」の値と、ラジアンの値とを相互に変換できるツールです。対応する角度の図示もできます。
2022/11/22
[公式ガイドサンプル] 立体モデルを回転させるアニメーション

「VCSSL 3DCG開発ガイド」内のサンプルコードです。立体モデルを、アニメーションで回転させ続けます
2022/11/18
[公式ガイドサンプル] 立体モデルを回転させる

「VCSSL 3DCG開発ガイド」内のサンプルコードです。立体モデルを、指定した角度だけ回転させます。
2022/11/17
[公式ガイドサンプル] 四則演算の計算順序や、カッコを使った順序指定

「VCSSLスタートアップガイド」内のサンプルコードです。四則演算が計算される順序や、カッコを使って順序を指定する方法を例示します。
2022/10/31