第8回 | C言語の関数の基本を学ぶ:処理をまとめて再利用する| UNIX Cafe

* 当サイトでは、コンテンツの一部に広告を掲載しています。

System Note $ cat /proc/ai-disclosure

本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。

第8回|C言語の関数の基本を学ぶ:処理をまとめて再利用する

はじめてのC言語 | 第8回

目次

はじめに

前回は、for 文と while 文で繰り返し処理を書く方法を学びました。
今回は、処理をひとまとめにして名前を付ける「関数」を扱います。

この回の目的は次の7点です。

  • 関数の役割を理解する
  • 関数の定義と呼び出しの書き方を覚える
  • 引数と戻り値の基本を知る
  • main 関数との関係を整理する
  • プロトタイプ宣言の役割を知る
  • グローバル変数とローカル変数の違いを知る
  • ソースコード分割の考え方を知る

関数とは何か

関数は、ひとまとまりの処理に名前を付けたものです。
同じ処理を何度も書かずにすみ、コードを整理しやすくなります。

たとえば、次のような処理を関数にできます。

  • メッセージを表示する
  • 2つの整数を足す
  • 条件を判定して結果を返す

C言語では、main も関数の1つです。

関数の基本形

書式

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

要素ごとの意味は次のとおりです。

  • 戻り値の型: 関数が返す値の型
  • 関数名: 関数の名前
  • 引数: 関数に渡す値
  • 波かっこの中: 関数の処理内容

最初のサンプルコード

戻り値がない関数

function_sample.c という名前で保存します。

#include <stdio.h>

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

int main(void) {
    say_hello();
    say_hello();
    return 0;
}

実行手順

1. 作業ディレクトリに移動する

cd ~/Desktop

2. コンパイルする

clang function_sample.c -o function_sample

3. 実行する

./function_sample

実行結果:

Hello
Hello

コードの読み方

void say_hello(void)

say_hello という関数を定義しています。

  • 最初の void : 戻り値がない
  • say_hello : 関数名
  • (void) : 引数を受け取らない

printf("Hello\n");

say_hello 関数の中で、文字列を表示しています。

say_hello();

関数を呼び出しています。
この1行で、say_hello の中の処理が実行されます。

関数を使う理由

関数を使う主な理由は次のとおりです。

  • 同じ処理を何度も書かなくてよい
  • 処理ごとに分けて読みやすくできる
  • 修正箇所を減らせる

たとえば、同じ表示処理を3回書く代わりに、関数を1つ作って3回呼び出せます。

引数のある関数

関数には値を渡せます。
この渡す値を引数と呼びます。

サンプルコード

#include <stdio.h>

void print_number(int n) {
    printf("%d\n", n);
}

int main(void) {
    print_number(10);
    print_number(25);
    return 0;
}

実行結果:

10
25

引数の意味

void print_number(int n)

この関数は、整数を1つ受け取ります。

  • int n : 整数型の引数 n
  • 呼び出し側から渡された値が n に入る

print_number(10);

10 を引数として関数に渡しています。

print_number(25);

今度は 25 を渡しています。
同じ関数でも、渡す値によって動作結果を変えられます。

戻り値のある関数

関数は、計算結果を返すこともできます。
返す値を戻り値と呼びます。

サンプルコード

#include <stdio.h>

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

int main(void) {
    int total = add(3, 5);
    printf("%d\n", total);
    return 0;
}

実行結果:

8

戻り値の読み方

int add(int a, int b)

add 関数は、整数を2つ受け取り、整数を1つ返します。

  • 最初の int : 戻り値の型
  • a, b : 引数
  • a + b を計算して返す

return result;

result の値を呼び出し元に返します。
return を実行すると、その時点で関数は終了します。

int total = add(3, 5);

add(3, 5) の戻り値を total に代入しています。

main 関数も関数である

これまで使ってきた main も関数です。

int main(void) {
    return 0;
}
  • int : 整数を返す
  • main : プログラム開始時に呼ばれる特別な関数
  • return 0; : 正常終了を表す値を返す

つまり、C言語では「処理を関数としてまとめる」考え方が基本になっています。

関数定義の位置に注意する

この段階では、main より前に関数を定義する形にしておくのが分かりやすいです。

#include <stdio.h>

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

int main(void) {
    say_hello();
    return 0;
}

関数を main の後ろに書く場合は、関数プロトタイプ宣言を使います。

プロトタイプ宣言とは

プロトタイプ宣言は、「このあと、この名前と形の関数が出てきます」とコンパイラに先に知らせるための書き方です。

C言語では、関数を呼び出す前に、その関数の戻り値の型や引数の型が分かっている必要があります。
main より後ろに関数定義を書く場合、main の時点ではまだ関数の中身が出てきていないため、先に宣言だけを書いておきます。

書式

戻り値の型 関数名(引数の型);

関数定義と似ていますが、プロトタイプ宣言では処理本体の { } を書きません。最後にセミコロン ; を付けます。

#include <stdio.h>

void say_hello(void);

int main(void) {
    say_hello();
    return 0;
}

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

この例では、void say_hello(void); がプロトタイプ宣言です。
これにより、main の中で say_hello(); を呼び出しても、コンパイラは「戻り値がなく、引数も受け取らない関数だ」と判断できます。

プロトタイプ宣言と実際の関数定義は、戻り値の型、関数名、引数の型を一致させる必要があります。

  • void say_hello(void); : プロトタイプ宣言
  • void say_hello(void) { ... } : 関数定義

最初のうちは、関数定義を main より前に書く方法でも問題ありません。
ただし、プログラムが長くなると、先にプロトタイプ宣言をまとめて書き、詳しい処理を後ろに書く形もよく使われます。

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

関数を使うときは、変数をどこで宣言するかも大切です。
変数には、使える範囲によって「グローバル変数」と「ローカル変数」があります。

ローカル変数

ローカル変数は、関数の中やブロックの中で宣言した変数です。
宣言した範囲の中だけで使えます。

#include <stdio.h>

void show_number(void) {
    int number = 10;
    printf("%d\n", number);
}

int main(void) {
    show_number();
    return 0;
}

この例の number は、show_number 関数の中で宣言されています。
そのため、show_number 関数の中では使えますが、main 関数の中から直接使うことはできません。

関数の引数も、その関数の中で使うローカル変数のように考えられます。

void print_number(int n) {
    printf("%d\n", n);
}

この n は、print_number 関数の中で使える変数です。

グローバル変数

グローバル変数は、関数の外で宣言した変数です。
同じファイル内の複数の関数から使えます。

#include <stdio.h>

int count = 0;

void increment(void) {
    count = count + 1;
}

int main(void) {
    increment();
    increment();
    printf("%d\n", count);
    return 0;
}

この例の count は、関数の外で宣言されているためグローバル変数です。
increment 関数からも main 関数からも使えます。

ただし、グローバル変数はどこから変更されたのか分かりにくくなることがあります。
小さなプログラムでは便利に見えますが、基本はローカル変数を使い、必要な値は引数や戻り値でやり取りする形を優先します。

  • ローカル変数: 関数やブロックの中だけで使える
  • グローバル変数: 関数の外で宣言し、複数の関数から使える
  • 迷ったら、まずローカル変数を使う

初心者がつまずきやすい点

void の意味を混同しない

void は「値を返さない」または「引数がない」という意味で使います。

void say_hello(void)

この場合は次の意味です。

  • 先頭の void : 戻り値なし
  • かっこの中の void : 引数なし

関数を呼び出すときは () が必要

関数名だけでは呼び出しになりません。

say_hello();

戻り値の型を合わせる

int を返す関数なら、return で整数を返す必要があります。

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

よくあるエラー

use of undeclared identifier

原因: 関数や変数を定義する前に使っています。
対処: この段階では、関数定義を main の前に書くか、先にプロトタイプ宣言を書きます。

too few arguments to function call

原因: 必要な引数の数が足りません。

例:

print_number();

print_numberint 型の引数を1つ必要とするなら、値を渡す必要があります。

non-void function should return a value

原因: 戻り値が必要な関数で return がありません。

誤った例:

int add(int a, int b) {
    int result = a + b;
}

対処:

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

練習用コード

メッセージを表示する関数

#include <stdio.h>

void print_message(void) {
    printf("C language\n");
}

int main(void) {
    print_message();
    return 0;
}

足し算をする関数

#include <stdio.h>

int add(int x, int y) {
    return x + y;
}

int main(void) {
    int answer = add(10, 20);
    printf("%d\n", answer);
    return 0;
}

実行結果:

30

ソースコードを分割する

ここまでのサンプルでは、すべての処理を1つの .c ファイルに書いてきました。
プログラムが大きくなると、関数ごと、役割ごとにファイルを分けることがあります。

これをソースコード分割と呼びます。
たとえば、足し算をする関数を calc.c に分け、関数の形を calc.h に書き、main.c から使う形にできます。

ファイル構成

main.c
calc.c
calc.h

.c ファイルには関数の処理を書きます。
.h ファイルには、外のファイルから使いたい関数のプロトタイプ宣言を書きます。

calc.h

int add(int a, int b);

これは、add 関数のプロトタイプ宣言です。
ヘッダファイルに書いておくことで、ほかの .c ファイルから同じ宣言を読み込めます。

calc.c

#include "calc.h"

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

calc.c には、add 関数の実際の処理を書きます。
先頭で calc.h を読み込んで、宣言と定義の形が合っているか確認できるようにします。

main.c

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

int main(void) {
    int answer = add(10, 20);
    printf("%d\n", answer);
    return 0;
}

main.c では、calc.h を読み込んでから add 関数を呼び出しています。
#include "calc.h" のようにダブルクォーテーションで書くと、自分で作ったヘッダファイルを読み込めます。

コンパイルする

複数の .c ファイルに分けた場合は、コンパイル時に必要な .c ファイルをすべて指定します。

clang main.c calc.c -o split_sample

実行します。

./split_sample

実行結果:

30

注意点として、calc.h は宣言を書くファイルであり、通常はコンパイルコマンドに直接指定しません。
実際にコンパイルするのは main.ccalc.c のような .c ファイルです。

最初は1つのファイルで十分です。
ただし、関数が増えて読みづらくなってきたら、役割ごとにファイルを分ける考え方も覚えておくとよいでしょう。

まとめ

今回のポイントは次のとおりです。

  • 関数は処理をひとまとめにして再利用する仕組みである
  • 関数には引数を渡せる
  • 関数は戻り値を返せる
  • main も関数の1つである
  • 関数を後ろに定義する場合は、先にプロトタイプ宣言を書く
  • ローカル変数は関数の中で使い、グローバル変数は関数の外で宣言する
  • 関数が増えたら、.c ファイルと .h ファイルに分けて整理できる

この回では、関数の基本的な使い方を確認しました。
処理をまとめ、必要に応じてファイルを分ける考え方が少し見えてきたと思います。

次回予告

次回は、配列を学びます。
同じ型のデータをまとめて扱う方法を知ることで、繰り返し処理と組み合わせた実用的なプログラムが書けるようになります。

復習してみよう

復習したい方は、こちらもあわせてご覧ください。

C言語の次にUnityやUnreal Engineを学ぶ選択肢

用途ごとに選ぶ C言語のおすすめ本

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

目次