関数

C言語では、処理をまとめるための関数という機能があります。

ここでは、その関数を説明します。

関数

数学には、関数と呼ばれるものがあります。

数学の関数は、「f(x) = 2x」のように、xを与えると、計算して何らかの値(ここでは 2x)が結果となります。

C言語の関数も同じで、何かデータを与えて、関数の中で計算して何らかの結果が返ってきます。

例として、関数に整数を渡して、二倍する関数 two_times を定義して、使ってみます。

渡すと言う言葉は、関数にデータを与える事を意味します。

#include <stdio.h>

int two_times(int x);

int main(void)
{
    int a;
    int ans;

    scanf("%d", &a);

    ans = two_times(a);

    printf("%d\n", ans);

    return 0;
}

int two_times(int x)
{
    return 2 * x;
}

実行すると、入力した整数を2倍したものを出力します。

ひとつひとつ見ていきます。

まず最初に、一番上の #include <stdio.h> の下に、「int two_times(int x);」というものがあります。これは、プロトタイプ宣言というもので、後で詳しくします。

次に、「ans = two_times(a);」というものがあります。これは、two_times という関数を呼び出して、その関数の結果を変数ans に代入しています。

プログラマーの間では、このように関数を使うことを、呼び出すと言います。

two_times関数が何をしているのかは、最後の4行にあります。

int two_times(int x)
{
    return 2 * x;
}

これは、関数の定義です。

関数が呼び出された時に、この定義にしたがって、処理が始まります。

関数の呼び出しは、関数の定義の内、「戻り値の型」「関数名」「引数」によって変わります。

戻り値の型、関数名、引数は「int two_times(int x)」の部分で決定しています。

最初の、「int」は戻り値の型であり、関数の最後にどのような値を返すのかを指定します。

次の、「two_times」は関数名です。関数名は、戻り値の型の後に空白で区切って書き、かっこの前までのものです。

最後の、「int x」は引数です。引数は、関数名の後のかっこの中に書き、関数に渡されるデータに関することを書きます。int x の場合、引数として整数を受け取ることができ、その整数は変数x で参照できます。

ここでは引数がひとつだけですが、引数が「(intx, int y, int z)」のように複数の場合、左側から順番に第一引数、第二引数、第三引数と呼びます。

関数内の処理は、その後の中かっこの中に書き「return 2 * x;」となっています。

これは、関数の戻り値として、2 * x したものを返すということです。

戻り値とは、関数の最後に関数の結果として返す値のことです。

戻り値として関数の呼び出し元に渡したい値を return の後に書きます。

これらの情報から、関数の呼び出しは、two_times(a) のように、「関数名」の後にかっこを書き、かっこの中には、関数に渡す値である引数について書きます。今回は、引数は1つなので、a とひとつしか書いていませんが、引数が複数ある場合は、カンマで区切って複数書きます。

これで、two_times関数が引数として渡された整数を2倍したものを結果として返していることがわかったと思います。

two_times関数の結果は、変数ans に代入しているので、後は「printf(“%d\n”, ans);」のように出力します。

このように、関数を使うと、一部の処理を関数として分けることができるので、ソースコードが見やすくなるなどの効果があります。

プロトタイプ宣言

上の説明では、「int two_times(int x);」のことをプロトタイプ宣言と説明しました。

プロトタイプ宣言は、コンパイラにこのような関数があるということを教えるための仕組みです。

コンパイラは、コンパイルをする時にソースコードの上から順に読み込んでいます。

もし、上のソースコードでプロトタイプ宣言がない場合は、コンパイル中に「ans = two_times;」の所で、two_times関数が初めて呼び出されますが、two_times関数が呼び出された時よりも前に、two_times関数に関する情報がないため、コンパイルに失敗します。

そのため、two_times関数の呼び出しよりも、先にプロトタイプ宣言によって、このような関数があるということをコンパイラに知らせます。

プロトタイプ宣言の書き方はいくつかあり、基本的には、関数の定義と同じですが、最後の引数だけ違います。

プロトタイプ宣言は、「戻り値の型」「関数名」「引数の型」で構成されます。

最後は、引数ではなく、引数の型なので、上のソースコードを例にすると、「int two_times(int);」のように変数名を書いても書かなくてもいいのです。

実は、このようなプロトタイプ宣言がなくてもコンパイルできる場合があります。

それは、関数の呼び出しよりも前に、関数の定義がある場合です。

#include <stdio.h>

int two_times(int x)
{
    return 2 * x;
}

int main(void)
{
    int a;
    int ans;

    scanf("%d", &a);

    ans = two_times(a);

    printf("%d\n", ans);

    return 0;
}

このように、関数の定義を呼び出されるよりも前にすると、コンパイラは two_times関数とはどのようなものか分かるので、プロトタイプ宣言はいりません。

void 型

void 型とは、関数の戻り値の型や引数の型として使用し、「ない」ということを示します。

例えば、戻り値の型であれば、戻り値がないということを表し、引数の型として利用すれば、引数がないということを表します。

これは、出力のみしたい場合など、関数で結果を必要としない場合や、引数として渡すデータがない場合に使用します。

#include <stdio.h>

void hello(void)
{
    printf("Hello\n");
}

int main(void)
{
    hello();

    return 0;
}

実行すると、「Hello」と出力します。

引数がないので、関数の呼び出しの際に、hello() のようにかっこの中には何も書きません。

そして、戻り値がないので、戻り値を表す return を書かなくても大丈夫です。

もし書きたいのであれば、「return ;」のように、return のみを書くようにします。

引数とポインタ

引数は、関数に渡すことができるデータのことです。

しかし、引数として渡すデータがどのようなものか分かっていないと、思わぬ落とし穴にはまってしまうことがありません。

このソースコードを実行して見ましょう。

#include <stdio.h>

void two_times(int ans, int x);

int main(void)
{
    int a;
    int ans;

    ans = 0;

    scanf("%d", &a);

    two_times(ans, a);

    printf("%d\n", ans);

    return 0;
}

void two_times(int ans, int x)
{
    ans = 2 * x;
}

実行すると、「ans = 0;」で代入した 0 が出力されます。

どういうことかというと通常、関数の引数として渡すことができる値は代入と同じように、値がコピーされたものが渡されます。

そのため、two_times関数の変数ans や x は、関数を呼び出している方の変数ans や a の値が代入されたもので、two_times関数の変数ans や x を変更しても、関数を呼び出している方の変数ansやa は変更されません。

では、どうすれば呼び出し元の引数の変数を変更できるのかというと、ポインタを使います。

#include <stdio.h>

void two_times(int *ans, int x);

int main(void)
{
    int a;
    int ans;

    ans = 0;

    scanf("%d", &a);

    two_times(&ans, a);

    printf("%d\n", ans);

    return 0;
}

void two_times(int *ans, int x)
{
    *ans = 2 * x;
}

結果を代入する変数ans の方を int型のポインタとしました。

「two_times(&ans, a);」のように、引数としてアドレスを渡します。そのため、関数の引数は「void two_times(int *ans, int x)」のようにポインタとなっています。

これで、two_times関数の引数のポインタ変数ans は、呼び出し元の変数ans を指し示すことになり、変数ans の値を変更することができます。

main関数

ここから、今までおまじないとして、覚えておくように説明していた部分について説明していきます。

まずは、もう一度おまじないを見て見ましょう。

#include <stdio.h>

int main(void)
{

    return 0;
}

「int main(void)」の部分に注目してください。

これは、戻り値の型が int、関数名がmain、引数がない関数です。

そして、中身を見ると戻り値として 0 を返しています。

つまり、今まで書いていたソースコードは、mainという名前の関数の中に処理を書いていたということです。

main関数は特別な関数で、プログラムを実行する際に、最初にmain関数が呼び出されるため、そこに一番最初に実行される処理を書きます。

そのため、今まで書いていた入力や出力、さらには関数の呼び出しなどは全てmain関数の中から行われていました。

mainの関数が特別なように、main関数の戻り値は、特別な意味があります。

過去に、プログラムは OS から実行されているという事を説明しました。

main関数の戻り値は、その実行元である OS にプログラムが正常に終了したかどうかを知らせるために返す値です。

正常にプログラムが終了する場合は0を、終了しない場合は0以外の1などの数字を戻り値とします。

#include

今まで、「scanf(“%d”, &a);」や「printf(“%d”, a);」のように入力や出力をしていました。

これは、それぞれscanf関数、printf関数と呼ばれる関数で、入力や出力をする機能を提供してくれます。

しかし、scanf関数やprintf関数は定義していないため、使えないはずです。

実は、入力や出力など、よく使う機能に関しては、既に定義されているため、ある手順を行うと、それらを使うことができるようになります。

その手順がソースコードの一番最初にある「#include <stdio.h>」です。

scanf関数やprintf関数のような入出力に関わる関数は、stdio.h というヘッダファイルと呼ばれるファイルに宣言されています。それを、#include というものを使い、山かっこ(< >)の中でファイルを指定して読み込んでいるため使えるというわけです。

#include によるファイルの読み込みはソースコードの先頭に書かなければなりません。

末尾の .h はヘッダファイルということ表すためのものです。

stdio.h では関数の宣言が記述されています。わざわざ宣言という言葉を使ったのは、ヘッダファイルには基本的にプロトタイプ宣言しか書かれておらず、関数の定義は別のファイルに書かれているからです。定義の書かれた別のファイルはヘッダファイルの方で勝手に読み込むので、普段あまり意識することはありません。

文字列処理で使った strcpy関数や strcmp関数なども関数であり、それらは string.h の中に宣言されているため、これらを使うために「#include <string.h>」と書く必要がありました。

ちなみに、stdio.h や string.h のような既に用意されているヘッダファイルを標準ヘッダファイルと呼びます。

ここで気をつけて欲しいのはprintf関数やscanf関数は、特別な関数で、引数をいくつでも受け取ることができる関数なので、今までの関数とは定義の方法が違い、同じものとして扱うと、混乱します。

スコープ

変数には、使える範囲があります。その範囲の事をスコープといいます。

例えば、関数内で作った変数は、その関数内でしか使うことができません。

#include <stdio.h>

int two_times(int x);

int main(void)
{
    int a;
    int ans;
    // ans が使える場所の始まり

    ans = 0;

    scanf("%d", &a);

    ans = two_times(a);

    printf("%d\n", ans);

    return 0;
    // ans が使える場所の終わり
}

int two_times(int x)
{
    int ans;
    // ans が使える場所の始まり
    ans = 2 * x;
    return ans;
    // ans が使える場所の始まり
}

main関数の「int ans;」とtwo_times関数の「int ans;」で作成される変数は別物で、それぞれ、変数が確保されてから、関数の終わりまで使うことができます。

条件分岐や繰り返しでも、中かっこのすぐ下であれば変数を作ることができます。

その場合のスコープはこのようになります。

#include <stdio.h>

int two_times(int x);

int main(void)
{
    int a = 3;
    int b = 5;
    int i;

    if (a == 3 && b == 5) {
        int tmp;
        // tmp が使える場所の始まり
        tmp = a;
        a = b;
        b = tmp;
        // tmp が使える場所の終わり
    }

    for (i = 0; i < 10; i++) {
        int tmp;
        // tmp が使える場所の始まり
        tmp = a;
        a = b;
        b = tmp;
        // tmp が使える場所の終わり
    }

    return 0;
}

a ソースコードの内容に深い意味はありませんが、処理の内容は if 文と for 文共に同じで、変数a と b の値を入れ替えています。

同じスコープ内に変数名の同じ変数があった場合、スコープの小さい変数が優先して使われます。

#include <stdio.h>

int two_times(int x);

int main(void)
{
    int a = 3;
    int i;


    printf("aの値は%dです\n", a);
    for (i = 0; i < 5; i++) {
        int a;
        a = i;
        printf("aの値は%dです\n", a);
    }
    printf("aの値は%dです\n", a);

    return 0;
}

for 文中の変数a はmain関数の処理が始まってすぐに作った変数よりもスコープが小さいため、for 文中では、for 文中に作った変数が利用され、実行すると、最後の表示は「aの値は3です」になります。

これらの関数内で作成した変数は「ローカル変数」や「局所変数」と呼ばれます。

それらとは違い、全ての関数内で使用できる変数を「グローバル変数」や「大域変数」と呼び、関数の外で、変数を作成します。

#include <stdio.h>

void two_times(int x);

int ans;

int main(void)
{
    int a;

    scanf("%d", &a);

    two_times(a);

    printf("%d\n", ans);

    return 0;
}

void two_times(int x)
{
    ans = 2 * x;
}

同じ変数名の変数がある場合、スコープが小さい変数が使われるというルールは同じです。

グローバル変数は、ローカル変数とは違い、初期値として必ず0 が代入されます。

ファイル分割

大規模なプログラムを書くためには、たくさんのソースコードが必要です。

しかし、たくさんのソースコードが必要であれば、一つのファイルだけにソースコードを書くのは現実的ではありません。

そこで、関数を使って処理を分けて、さらにファイルも分けていきます。

先ほどのソースコードを例として、別々のファイルに、ソースコードを分けます。

これらのソースコードは、実行しやすいように、最初のものを「main.c」、次のものを「func.h」、最後のものを「func.c」という名前でファイルを同じ場所に保存しておきましょう。

#include <stdio.h>
#include "func.h"

int main(void)
{
    int a;
    int ans;

    scanf("%d", &a);

    ans = two_times(a);

    printf("%d\n", ans);

    return 0;
}
#include "func.c"

int two_times(int x);
int two_times(int x)
{
    return 2 * x;
}

関数を別のファイルとして分けました。

実行すると、今まで通り、2倍した数字を出力します。

見て欲しいのが、main.c の「#include “func.h”」として、two_times関数の定義が書かれたファイルを読み込んでいます。

このように、別のファイルに関数の定義や宣言を書いた場合、#include “func.h”のように読み込むことができます。標準ヘッダファイル以外のファイルを使う場合は、ダブルクォートで囲む必要があります。

func.h の中では、#include によるファイルの読み込みと、プロトタイプ宣言があります。

ヘッダファイルには、関数の定義を書くことができますが、なるべくプロトタイプ宣言のみを書くべきであるので、プロトタイプ宣言をかき、関数の定義は、func.c の方に書き、それを読み込むようにしています。

func.cの中では、two_times関数の定義が書かれています。

このように、ソースコードをファイルごとに分けることによって、プログラムを作成しやすくなります。

まとめ

関数では、処理を関数として分けることによって、ソースコードを見やすくしたり、書きやすくしたりします。

さらに、色々な便利な機能は関数として提供されています。

関数の仕組みを覚えて、使いこなせるようにしましょう。

コメント

タイトルとURLをコピーしました