【独学C言語入門⑨】関数について学ぼう

今回は《関数について学ぼう》というテーマで記事をまとめます。

「関数」まで習得すれば、おおよそ折り返し地点を突破したといっても過言ではありません。頑張りましょう!

尚、現在執筆中のC言語入門記事は以下の人を対象としています。

  • プログラミング未経験の人
  • C言語プログラミングの学習をこれから始めたい人
  • C言語プログラミングの学習を始めたばかりの人

関数について学ぼう

関数について学ぼう

関数とは

【独学C言語入門④】C言語プログラムの基本構成を知ろう」で、関数について少し触れましたが、今回は改めて作り方や使い方を含めた解説をしたいと思います。

  • 関数とは「複数の命令文をまとめたもの」
    • 関数内の命令文は必ず上から順番に処理が行われる
    • 関数は他の関数から呼び出すことができる

関数定義と関数呼び出し

早速ですが、「関数定義」と「関数呼び出し」の基本的な書式は次のようになります。

/* 関数定義 */
戻り値の型 関数名(引数){
    命令文;
    ...

    return 戻り値;
}
/* 関数呼び出し */
関数名(引数);

見知らぬワード『引数』と『戻り値』が出てきましたね。まずは、これらのワードを説明していきたいと思います。

引数とは

引数とは「関数を呼び出す際に渡す値」のことで、次の2種類に分類されます。

名称説明
仮引数関数定義時に使用される引数
実引数関数を実際に使用する際に関数に引き渡される引数


"引数とは「関数を呼び出す際に渡す値」"と書きましたが、誤解なきよう補足しておくと、引数にはベタで書いた値(数値や文字)以外にも「変数」を使用することができます

void func(int n){
    printf("ここはfunc関数内です。n = %d\n", n);
}

void main(){
    int num = 0;
    func(num);
}

ここで注意してほしいのが、仮引数には実引数のコピーが渡される(仮引数と実引数は別物)という点です。

つまり、関数内で仮引数の値を変更しても呼び出し元の実引数に変化はない、ということです。

void func(int n){
    n = 100;    /* n に 100 を代入しても、main関数の num に変化はない */
}

void main(){
    int num = 0;
    func(num);
}


また、関数は引数なしでも定義できます。当然ですが、その場合は関数呼び出しも引数なしで記述します。

/* 引数なし関数定義 */
void func(){
    printf("ここはfunc関数内です。\n");
}

void main(){
    func();    /* 引数なし関数呼び出し */
}


ここまで引数の解説をしてきました。賢い皆さんならお気づきかと思いますが、関数の引数には次のルールがあります。

  • 定義と呼び出しにおいて、引数の型と数が同じであること
    (仮引数の型と実引数の型、仮引数の数と実引数の数がそれぞれ同じであること)

開発環境によってはコンパイルエラーとなる可能性もあるのでご注意ください。
(Visual Studio 2019ではワーニングが出力されます)

戻り値とは

戻り値とは「関数が処理を終了する際に呼び出し元に返す値」です。


「戻り値の型」には「変数の型」を指定することができます。

変数の基本的な型は「【独学C言語入門⑤】「変数」の基礎を学ぼう」の「基本的な型一覧」をご覧ください。

また、戻り値の型には基本的な型以外にも「構造体」と「ポインタ」というものを指定することができます。この2つは後日記事にしますのでご期待ください。


ここまでのお話から「関数は戻り値が必須なんだな」と思われるかもしれませんが、関数は戻り値なしで定義することもできます

戻り値なしの関数を定義する場合、「戻り値の型」に「void」と記述します。そして、戻り値なしの関数の場合、「return」を記述する必要がありません。
どうしても記述したい場合(関数内の処理をそこで終了したい場合など)は、「return;」と記述します。(「return」の後ろに値を記述しません。)

/* 戻り値なし関数定義① */
void func1(){
    printf("ここはfunc1関数内です。\n");
}

/* 戻り値なし関数定義② */
void func2(){
    printf("ここはfunc2関数内です。\n");
    return;
}

void main(){
    func1();    /* 戻り値なし関数呼び出し① */
    func2();    /* 戻り値なし関数呼び出し② */
}

※補足※
 「戻り値の有無を記述できるのはわかったけど、どういう場合に使い分ければいいの?」という疑問が生まれている方もいるかと思います。
 そこで、戻り値なしで良いケースと戻り値が必要なケースをまとめたので一読しておいてください。

 ▼戻り値なしで良いケース
  ・呼び出し元に結果を返す必要がない場合(処理が一方通行の場合)
    例)テキストを表示をするだけの関数

 ▼戻り値が必要なケース
  ・呼び出し元に結果を返す必要がある場合(処理が相互作用している場合)
    例)引数の値から計算結果を返す関数
      関数内の処理が成功したか失敗したかを戻り値で返す関数

まとめ(サンプルコードと実行結果)

関数定義と関数呼び出しにおけるサンプルを用意しました。実際に動かしてみてください。

#include<stdio.h>

int func1(int a, int b){
    int c = 0;
    c = a + b;

    printf("func1関数内の処理です。\n");
    printf(" %d + %d = %d\n", a, b, c);
    printf("\n");

    return c;
}


void func2(int a){
    printf("func2関数内の処理です。\n");
    if(a == 1){
        printf(" 引数に 1 が入力されました。\n");
        printf("\n");
        return;
    }

    printf(" 引数に 1 以外が入力されました。\n");
    printf("\n");
}

void func3(int a){
    a = 1234;   /* 引数 a の値を変更しても呼び出し元の変数の値は変わらない */

    printf("func3関数内の処理です。\n");
    printf(" 引数 a の値を %d に変更しました。\n", a);
    printf("\n");
}

void func4(){
    printf("func4関数内の処理です。\n");
    printf(" 本関数は表示のみ行います。\n");
    printf("\n");
}

void main(){
    int num = 0;
    int num15 = 15;

    printf("main関数内の処理です。(func1呼び出し前)\n");
    printf(" num = %d\n", num);
    printf("\n");

    num = func1(10, num15);

    printf("main関数内の処理です。(func1呼び出し後)\n");
    printf(" num = %d\n", num);
    printf("\n");


    func2(1);
    func2(0);


    printf("main関数内の処理です。(func3呼び出し前)\n");
    printf(" num = %d\n", num);
    printf("\n");

    func3(num);

    printf("main関数内の処理です。(func3呼び出し後)\n");
    printf(" num = %d\n", num);
    printf("\n");


    func4();

    printf("main関数内の処理です。(最後の処理)\n");
    printf(" num = %d\n", num);
    printf("\n");
}

関数の定義位置

ここまで書いてきてませんでしたが、関数の定義位置にはルールがあります。

  • 関数呼び出しよりも前に関数定義を行うこと

関数は呼び出されるタイミングでその情報(関数名、戻り値の型、引数の型と数)を知っておく必要があるのです。

/* 正しい関数の定義位置 */
void func1(){
}

void main(){
    func1();
    func2();
}

/* 間違った関数の定義位置 */
void func2(){
}

プロトタイプ宣言

関数は呼び出されるタイミングよりも前に関数定義を行う必要がある、と言われても、常に上下関係を意識して関数を作るのは大変ですよね。

C言語には『プロトタイプ宣言』という構文が用意されていて、これを利用すると関数の上下関係を意識せずにプログラムを書くことができます。

プロトタイプ宣言とは、関数の情報(関数名、戻り値の型、引数の型と数)のみを先に記述しておいて、関数の処理を後で記述するという手法です。

/* プロトタイプ宣言(関数の情報のみを書く) */
int func(int n1, int n2);

void main(){
    int num = 0;
    num = func(10, 15);
}

/* 関数定義(関数の処理を書く) */
int func2(int n1, int n2){
    int n3 = 0;
    n3 = n1 + n2;
    return n3;
}

グローバル変数とローカル変数

今回、関数の基礎を学んでもらいました。そこで、「【独学C言語入門⑤】「変数」の基礎を学ぼう」で簡単に解説した「グローバル変数」と「ローカル変数」について改めて解説したいと思います。

【復習】グローバル変数とローカル変数とは

まずは、復習です。

C言語における変数は定義位置によって次の2つに分類されます。

  • 関数の外で定義された変数を「グローバル変数」と呼ぶ
  • 関数の中で定義された変数を「ローカル変数」と呼ぶ

特性はそれぞれ次のようになっています。

名称特性
グローバル変数全ての関数から使用可能
ローカル変数定義された関数内のみで使用可能

サンプルコードを用意しました。まずは動かしてみてください。

#include<stdio.h>

int global = 0;      /* グローバル変数 */

void func(int n);

void main(){
    int local1 = 10;  /* ローカル変数①   */

    /* ここで使用できるのは global と local1 */

    printf("main関数内の処理です。(func呼び出し前)\n");
    printf(" global = %d, local1 = %d\n", global, local1);
    printf("\n");

    func(local1);

    printf("main関数内の処理です。(func呼び出し後)\n");
    printf(" global = %d, local1 = %d\n", global, local1);
    printf("\n");
    
}

void func(int n){
    int local2 = 20;  /* ローカル変数②   */

    /* ここで使用できるのは global と local2 */

    printf("func関数内の処理です。(計算前)\n");
    printf(" global = %d, local2 = %d\n", global, local2);
    printf("\n");

    /* local2をインクリメントして、nと掛け合わせた結果をglobalに格納する */
    local2++;
    global = local2 * n;

    printf("func関数内の処理です。(計算後)\n");
    printf(" global = %d, local2 = %d\n", global, local2);
    printf("\n");

}

変数名のルール

ここでグローバル変数とローカル変数に特化した「変数名のルール」について書きたいと思います。変数名の基本的なルールについては「【独学C言語入門⑤】「変数」の基礎を学ぼう」をご参照ください。

関数を跨いだ同名のローカル変数

通常、C言語では同名の変数名を2つ定義することはできません。

しかし、賢い皆さんならこう感じているかもしれません。

「ローカル変数は、定義された関数の外で使用することができない。ということは、別の関数内のローカル変数であれば、同名でも問題ないのでは?」

そう、確かにその通りなのです。そもそも「2つ定義されるとどちらを使えばよいかわからない」という考えから同名の変数が禁止されているので、閉じられた世界である"関数"を跨いでまで変数名を管理する必要はないのです。

ということで、実はC言語にはローカル変数は異なる関数内であれば同名での定義が可能という仕様が組み込まれています。

尚、当然ですが、異なる関数内の同名ローカル変数はそれぞれ別物として扱われます。例えば、関数①内のローカル変数の値を変更しても関数②内のローカル変数の値は変わりません。

サンプルコードを用意したので実行してみてください。

#include<stdio.h>

void func(){
    /* ここで定義されているlocalはmain関数内のlocalとは別物 */
    int local;
    local = 30;  /* localに 30 を代入してもmain関数内のlocalは更新されない */
}

void main(){
    int local;
    local = 10;

    printf("local = %d\n",local);

    func();

    printf("func関数実行後 local = %d\n",local);
}


尚、引数はローカル変数として扱われます。よって、次のような書き方はコンパイルエラーとなるので注意してください。

void func(int n){
    int n;
}

同名のグローバル変数とローカル変数

「ローカル変数は異なる関数内であれば同名での定義が可能」と書きましたが、グローバル変数とローカル変数の変数名が同じだった場合は話が少し変わってきます。

グローバル変数と同名のローカル変数を定義した場合、定義した関数内ではローカル変数のみ使用されます

サンプルコードを用意したので実行してみてください。

#include<stdio.h>

int num = 0;

void func1(int n) {
    /* グローバル変数とローカル変数が同名 */
    int num;
    num = n;
}

void func2(int num) {
    /* グローバル変数と引数が同名 */
    num = 100;
}

void func3(int n) {
    /* グローバル変数と同名の変数なし */
    num = n;
}

void main()
{
    num = 1;
    printf("num = %d\n", num);

    func1(2);
    printf("func1関数の結果 num = %d\n", num);

    func2(3);
    printf("func2関数の結果 num = %d\n", num);

    func3(4);
    printf("func3関数の結果 num = %d\n", num);
}

次回は「配列」について

今回は「関数」の基礎について学んでもらいました。

次回は「配列」について解説したいと思います。今まで1つの変数に1つの値を格納してきましたが、1つの変数に複数個の値を格納することができるのが「配列」です。

ループ制御文と合わせると楽しいことができそうですね!

ご期待ください。

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