コンテンツにスキップ

C言語/関数

出典: フリー教科書『ウィキブックス(Wikibooks)』

はじめに

[編集]

関数とは何か

[編集]

関数とは、特定の目的を持った一連の命令をまとめたもので、プログラムの一部として呼び出して実行することができます。関数を使うことで、プログラムの構造を整理し、再利用可能なコードを作成することができます。

関数の利点

[編集]

関数には以下のような利点があります:

再利用性
一度定義した関数を何度でも呼び出して利用することができます。
可読性の向上
プログラムを小さな部分に分割することで、理解しやすくなります。
デバッグの容易さ
エラーが発生した場合、問題のある関数を特定して修正することが容易です。

関数を使用する場面の例

[編集]

例えば、数値の平均を計算するプログラムがあるとします。同じ計算をプログラムの複数箇所で行う場合、関数として定義しておけば、コードを繰り返すことなく利用できます。

関数の基本構造

[編集]

関数の定義

[編集]

関数は以下のように定義されます:

戻り値の型 関数名(引数リスト) {
    関数本体
}

例えば、整数を引数に取り、その二倍の値を返す関数は次のように定義します:

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

関数の宣言(プロトタイプ宣言)

[編集]

関数を使用する前に、そのプロトタイプを宣言することで、コンパイラに関数の存在を知らせることができます。プロトタイプ宣言は、関数定義より先に関数を呼び出す(前方参照)、あるいは翻訳単位の外で外で定義されている場合に必要になります。

int doubleValue(int x);

関数の作成と呼び出し

[編集]

基本的な関数の作成例

[編集]

次に、引数として二つの整数を取り、それらの和を返す関数を作成します:

int add(int a, int b) {
    return a + b;
}

関数の呼び出し方

[編集]

関数を呼び出すには、関数名と引数を使います:

int result = add(3, 4);
printf("結果: %d\n", result);

main関数とユーザー定義関数の関係

[編集]

main関数はプログラムのエントリーポイントであり、他のユーザー定義関数を呼び出すことができます。例えば、先ほどのadd関数をmain関数から呼び出す例です:

#include <stdio.h>

int add(int a, int b);

int main() {
    int sum = add(5, 7);
    printf("和: %d\n", sum);
    return 0;
}

int add(int a, int b) {
    return a + b;
}

引数と戻り値

[編集]

引数の型と順序

[編集]

関数は複数の引数を取ることができ、それぞれに型と順序があります。例えば、次の関数は二つの浮動小数点数を引数に取り、その積を返します:

double multiply(double x, double y) {
    return x * y;
}

戻り値の型とreturn文

[編集]

関数は特定の型の値を返すことができます。return文を使って値を返します:

int subtract(int a, int b) {
    return a - b;
}

void型の関数(引数なし、戻り値なし)

[編集]

引数も戻り値もない関数を定義することもできます。その場合、void型を使用します:

void greet() {
    printf("Hello, World!\n");
}

値渡しと参照渡しの違い(基本概念)

[編集]

関数に引数を渡す方法には、値渡しと参照渡しの二つがあります。C言語では基本的に値渡しが使われますが、ポインタを使うことで参照渡しに似た動作を実現できます。

void changeValue(int *x) {
    *x = 10;
}

int main() {
    int value = 5;
    changeValue(&value);
    printf("Value: %d\n", value);
    return 0;
}

スコープとライフタイム

[編集]

変数のスコープ(ローカル変数とグローバル変数)

[編集]

変数にはスコープ(有効範囲)があります。ローカル変数は関数内で宣言され、その関数内でのみ有効です。グローバル変数は関数外で宣言され、プログラム全体で有効です。

int globalVar = 10; // グローバル変数

void function() {
    int localVar = 5; // ローカル変数
    printf("Local Var: %d\n", localVar);
}

int main() {
    printf("Global Var: %d\n", globalVar);
    function();
    return 0;
}

自動変数と静的変数のライフタイム

[編集]

自動変数(ローカル変数)は関数が呼び出されるたびに作成され、関数が終了すると破棄されます。一方、静的変数は関数が終了しても値を保持し続けます。

void staticExample() {
    static int staticVar = 0; // 静的変数
    staticVar++;
    printf("Static Var: %d\n", staticVar);
}

int main() {
    staticExample();
    staticExample();
    staticExample();
    return 0;
}

スタティック変数の使い方

[編集]

スタティック変数は、関数が呼び出されるたびに初期化されず、前回の値を保持します。これを利用して、例えば関数の呼び出し回数をカウントすることができます。

標準ライブラリ関数

[編集]

標準ライブラリとは

[編集]

C言語の標準ライブラリには、よく使われる機能があらかじめ実装されています。これらの関数は、プログラムの効率を高めるために利用できます。

よく使用される標準ライブラリ関数の紹介(例:printf, scanf, strlen, etc.)

[編集]

標準ライブラリ関数のいくつかを紹介します:

printf
文字列をフォーマットして出力する。
scanf
フォーマットに従って入力を読み取る。
strlen
文字列の長さを返す。
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, World!";
    printf("文字列: %s\n", str);
    printf("文字列の長さ: %lu\n", strlen(str));
    return 0;
}

ヘッダーファイルの役割

[編集]

標準ライブラリ関数を使うには、対応するヘッダーファイルをインクルードする必要があります。例えば、printfscanfを使う場合は<stdio.h>を、strlenを使う場合は<string.h>をインクルードします。

再帰関数

[編集]

再帰の基本概念

[編集]

再帰とは、関数が自分自身を呼び出すことです。再帰を利用すると、問題を小さな部分に分割して解決することができます。

再帰関数の例

[編集]

再帰関数の典型的な例として、階乗の計算があります。

int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

int main() {
    int result = factorial(5);
    printf("5の階乗: %d\n", result);
    return 0;
}

再帰関数の利点と注意点

[編集]

再帰関数は、問題を自然に表現できる場合に有効です。しかし、再帰が深すぎるとスタックオーバーフローを引き起こす可能性があるため、適切な基底条件を設定することが重要です。

関数ポインタ

[編集]

関数ポインタとは

[編集]

関数ポインタは、関数のアドレスを格納するためのポインタ

です。関数ポインタを使うことで、関数を引数として渡したり、動的に関数を呼び出すことができます。

関数ポインタの宣言と使用

[編集]

関数ポインタを宣言するには、次のようにします:

int (*funcPtr)(int, int);

例えば、先ほどのadd関数を関数ポインタを使って呼び出すには、次のようにします:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*funcPtr)(int, int) = add;
    int result = funcPtr(3, 4);
    printf("結果: %d\n", result);
    return 0;
}

関数ポインタを使った例

[編集]

関数ポインタを使うと、例えば動的に異なる関数を呼び出すことができます。

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

void executeOperation(int (*operation)(int, int), int x, int y) {
    int result = operation(x, y);
    printf("結果: %d\n", result);
}

int main() {
    executeOperation(add, 3, 4);
    executeOperation(multiply, 3, 4);
    return 0;
}

実践例

[編集]

小さなプログラムを作成して関数を活用する

[編集]

複数の関数を使った実践的なプログラムを作成し、関数の有用性を実感します。例えば、簡単な電卓プログラムを作成します:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(int a, int b) {
    return (double)a / b;
}

int main() {
    int x = 10, y = 5;
    printf("和: %d\n", add(x, y));
    printf("差: %d\n", subtract(x, y));
    printf("積: %d\n", multiply(x, y));
    printf("商: %.2f\n", divide(x, y));
    return 0;
}

モジュール化と関数の再利用

[編集]

関数をモジュール化することで、再利用性が向上します。例えば、数学関数を別のファイルにまとめておくことができます。

math_functions.h
#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);

#endif

// math_functions.c
#include "math_functions.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(int a, int b) {
    return (double)a / b;
}
main.c
#include <stdio.h>
#include "math_functions.h"

int main() {
    int x = 10, y = 5;
    printf("和: %d\n", add(x, y));
    printf("差: %d\n", subtract(x, y));
    printf("積: %d\n", multiply(x, y));
    printf("商: %.2f\n", divide(x, y));
    return 0;
}

デバッグとテスト方法

[編集]

関数をデバッグするためには、テストケースを作成して様々な入力に対して正しい出力が得られるかを確認します。例えば、数学関数のテストケースを作成します。

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

void test_add() {
    if (add(2, 3) != 5) {
        printf("add関数のテストに失敗しました。\n");
    } else {
        printf("add関数のテストに成功しました。\n");
    }
}

void test_subtract() {
    if (subtract(5, 3) != 2) {
        printf("subtract関数のテストに失敗しました。\n");
    } else {
        printf("subtract関数のテストに成功しました。\n");
    }
}

int main() {
    test_add();
    test_subtract();
    return 0;
}

演習問題

[編集]
基本的な関数の作成
1. 二つの整数を引数に取り、その和を返す関数addを作成してください。
2. 二つの浮動小数点数を引数に取り、その差を返す関数subtractを作成してください。
引数と戻り値を使った関数
3. 三つの整数を引数に取り、その平均を返す関数averageを作成してください。
4. 文字列を引数に取り、その長さを返す関数stringLengthを作成してください。
再帰関数の作成
5. 自然数nを引数に取り、nの階乗を返す再帰関数factorialを作成してください。
6. 自然数nを引数に取り、n番目のフィボナッチ数を返す再帰関数fibonacciを作成してください。
標準ライブラリ関数の利用
7. 標準ライブラリ関数printfを使って、任意の文字列を出力する関数printMessageを作成してください。
8. 標準ライブラリ関数scanfを使って、ユーザーから整数を入力し、その値を返す関数getInputを作成してください。
関数ポインタの活用
9. 二つの整数を引数に取り、異なる演算(和、差、積、商)を行う関数ポインタを使ったプログラムを作成してください。
10. 関数ポインタを使って、異なる比較関数(大なり、小なり、等しい)を動的に呼び出すプログラムを作成してください。

まとめ

[編集]

関数の重要性の再確認

[編集]

関数は、プログラムを整理し、再利用可能にするための強力なツールです。本章で学んだ関数の定義、呼び出し、引数と戻り値の取り扱い、スコープとライフタイム、標準ライブラリの活用、再帰、関数ポインタなどの概念をしっかりと理解し、実際のプログラムに応用してください。

本章のポイントの振り返り

[編集]
  • 関数の基本構造と定義方法
  • 関数の呼び出しとプロトタイプ宣言の重要性
  • 引数と戻り値の使い方
  • 変数のスコープとライフタイムの理解
  • 標準ライブラリ関数の活用方法
  • 再帰関数とその利点と注意点
  • 関数ポインタの使用と応用例