C++セミナー(初級)関数

HOME->講習会->プログラミングセミナー->C++セミナー(初級)


同じ処理を何回もするような場合に,何回も同じソースコードをコピー&ペーストするのは,スマートではありません.同じ処理を何回もするような場合には,サブルーチンと呼ばれることを行います.C++では,サブルーチンをする機能として関数があります.ここでは,関数の使い方について紹介します.

目次

関数
プロトタイプ宣言
参照型による引数の参照渡し
引数のプロテクト
演習

関数

関数とは,前述したようにC++でのサブルーチン機能のことです.「C++セミナー(初級)出力処理」や「C++セミナー(初級)入力処理」で紹介した,「std::printf」や「std::scanf」も関数の1つです.もちろん,「int main()」についても関数の1つです.このように,様々なところで使用するような処理は関数として扱うことでスマートになります.

関数は以下のようにして定義します.

返り値の型 関数名(引数型 引数名)
{
    処理;
}

返り値とは,関数の処理が終了した後に,呼び出し元に返すデータのことをいいます.引数とは,呼び出し元から関数に渡すデータのことを言います.引数については,引数がない場合には省略することが可能です.また,以下のようにカンマ区切りでいくつも設定することができます.

返り値の型 関数名(引数型 引数名, 引数型 引数名, 引数型 引数名)
{
    処理;
}

例えば,足し算をする関数について考えてみます.

int Sum(int a, int b)
{
    int ret = a + b;
    return ret;
}

「return ret;」とありますが,これは,「現在の関数の処理を終了し関数を呼出し元にretを返す」というような意味を表します.実際にmain関数と組み合わせた例を以下に示します.

#include <iostream>
int Sum(int a, int b)
{
    int ret = a + b;
    return ret;
}
int main()
{
    int i;
    int j;
    std::cin >> i >> j;
    std::cout << Sum(i,j) << std::endl;
}

main関数でstd::cinを用いて読み取ったデータiとjをSum関数の引数のaとbにコピーして渡します.そして,Sum関数で計算した結果をreturnでmainに返却するというような流れとなっています.

他にも,配列を引数として渡したい場合もあると思います.そのような場合には,ポインタ変数を引数として渡します.例えば,配列のすべての要素を加算した結果を返す関数は以下のようになります.

int Sum(int *array, int size)
{
    int ret=0;
    for(int i=0; i<size; i++){
        ret+=array[i];
    }
    return ret;
}
int main()
{
    int array[] = {1,2,3,4,5,6,7,8,9,10};
    std::cout << Sum(array, sizeof(array)/sizeof(int)) << std::endl;
}

ここで,引数として配列と配列のサイズを渡すようにしています.これは,配列を引数として渡したときに,配列の要素数は関数内で知るすべがないためです.配列を渡す場合には,サイズも一緒に渡すことを忘れないようにしましょう.また,main関数内でSum関数を呼び出す際にsizeofというものを使用しています.これは,変数や配列のサイズ[byte]を得る関数のようなものです.arrayのサイズを,intサイズで割ることにより,arrayの要素数が求めることができます.

なお,返り値がない場合には,返り値の型を「void」として定義します.たとえば,引数として渡したデータを出力する関数は以下のようになります.

void print(int data)
{
    std::cout << data << std::endl;
    return;  // 省略してもよい
}

返り値がない関数については,returnを省略することが可能です.ここで,main関数はint main()となっているから,返り値をreturnをつけなくていいのかと思う方もいっら者るかもしれませんが,main関数は,0を返したときに正常終了したと判定されます.そのため,C言語では,かならずプログラムの最後には「return 0;」と記述されていました.しかし,C++では,プログラムの最後には「return 0;」は絶対に書くものだし,省略可能にしてもいいというようにルール付けされました.そのため,main関数に限っては省略できます(return 0;をつけろという宗教の方もいらっしゃいますが).

プロトタイプ宣言

関数は,自身の関数より先に宣言・定義された関数しか呼び出すことができません.その場合,関数によっては呼び出せる呼び出せないといったややこしいことが発生してしまいます.これを解決するために,プロトタイプ宣言と呼ばれることを行います.これは,関数の返り値型と関数名,引数をあらかじめ宣言しておくものになります.

#include <iostream>
int Sum(int a, int b);
int main()
{
    int i;
    int j;
    std::cin >> i >> j;
    std::cout << Sum(i,j) << std::endl;
}
int Sum(int a, int b)
{
    int ret = a + b;
    return ret;
}

関数のプロトタイプ宣言をmain関数の前に行い,定義をmainの後に行っています.プロトタイプ宣言の部分を削除すると,コンパイルエラーが発生します.多くの関数を作成していくと,どの関数が存在するのかなどわかりにくくなるため,プロトタイプ宣言をまとめて行っておくとわかりやすくなると思います(一部,プロトタイプ宣言を使わないという宗教の人たちもいますが).

参照型による引数の参照渡し

データサイズが,4byteや1byteといったぐらいであれば,上記のようにしてきた関数の宣言や定義で十分です.しかし,今後学習する構造体やクラスと呼ばれるものでは,数キロbyteから数メガbyteと大きい変数型を自作することができてしまいます.これを関数を呼び出すときに引数として渡してしまうとコピーをして渡すことになるため,処理時間が長くなってしまいます.そのため,C++では参照型引数というものを使用することが一般的です.

返り値型 関数名(引数型 &引数名)
{
     処理;
}

上記のように引数名の前に「&」を付けると参照型になります.参照型にすることによって,呼び出し元の値をコピーするのではなく,そのまま参照する形になります.そのため,コピーする時間が削減されるので,処理速度の向上が見込まれます.また,呼出し元の変数と連動しているため,関数内で変更させると呼出し元の変数を書き換えることができます.これを利用して,通常では,関数は1つの出力しかできませんが,計算結果を引数で返すといったこともできます.ポインタ型引数もアドレスを用いているため,同じことができます.参照型の例を以下に示します(このぐらいのものであれば,処理速度はほぼ変わりません).

#include <iostream>
int Sum(int &a, int &b)
{
    int ret = a + b;
    return ret;
}
int main()
{
    int i;
    int j;
    std::cin >> i >> j;
    std::cout << Sum(i,j) << std::endl;
}

引数のプロテクト

値型の引数であれば,引数にデータをコピーするため特に何も考えなくてもよいですが,ポインタ型や参照型で引数を渡している場合,書き換えをしてはならない参照元のデータを誤って書き換えてしまう恐れがあります.これは,参照元の変数のアドレスを用いて処理を行うためです.これを防ぐために,書き換えができないようにするために,const修飾子を付けることが一般的です.

返り値 関数名(const 引数型 &引数名)

このようにすることで,引数をふいに書き換えてしまいそうになった場合にはコンパイルエラーとなるため,引数の変更を防ぐことができます.

個人的なルールではありますが,データの書き換えが発生するような引数についてはポインタ型,書き換えが発生してほしくない引数については,constの参照型を用いるようにしています.理由としては,ポインタ型で渡す場合には,変数の前に「&」を付けるので書き換えるということが見ただけでわかりますが,参照型は変数をそのまま入れるだけなので,書き換えるのか書き換えないのかが見ただけではわかりにくいためです.

演習

・入力された配列のデータの平均値を返す関数を作成してください.
・入力された配列のデータの標準偏差を返す関数を作成してください.