C言語でプログラムを書くとき、必ず登場するのが「関数」です。
関数は、処理をひとまとまりにして名前を付けたもので、コードの見通しを良くし、再利用しやすくしてくれます。
本記事では、C言語の関数とは何かから始めて、宣言と定義の書き方、自作関数の作り方、標準関数との組み合わせ方まで、順を追って丁寧に解説します。
これから関数を本格的に使えるようになりたい方に向けた入門ガイドです。
C言語の関数とは
関数の基本概念と役割

プログラムは、入力を受け取り、処理を行い、結果を出力する一連の流れで構成されます。
しかし、すべてを1つのかたまりとして書いてしまうと、読みづらく、修正も大変になります。
そこで登場するのが関数です。
関数とは、ある特定の処理に名前を付けて、ひとまとまりにしたものです。
例えば「2つの整数を足す処理」や「配列の平均値を求める処理」などを関数にしておくことで、必要なときに名前を呼ぶだけでその処理を実行できます。
関数には次のような役割があります。
1つ目は、処理の整理と見通しの向上です。
長い処理を複数の関数に分けることで、プログラム全体の構造がわかりやすくなります。
2つ目は、再利用性の向上です。
同じ処理を何度も使いたいとき、関数にしておけば1回定義して何度でも呼び出せます。
3つ目は、バグの原因箇所を見つけやすくすることです。
関数単位で動作を確認できるため、どの部分に問題があるか切り分けやすくなります。
C言語では、プログラムは必ず1つ以上の関数から構成されると考えてよく、実行の起点になる関数も決まっています。
main関数とは

C言語のプログラムは、必ずmainという名前の関数から実行が始まるという決まりがあります。
これは言語仕様のルールで、main関数が存在しないと、プログラムは正しくリンク・実行できません。
典型的なmain関数の形は次のようになります。
#include <stdio.h> // printfを使うためのヘッダ
// プログラムの実行は必ずここから始まる
int main(void) {
printf("Hello, world!\n"); // 画面に文字を表示
return 0; // 正常終了をOSに知らせる
}
上記のサンプルでは、int main(void)という形でmain関数が定義されています。
intは戻り値の型で、OSに「実行がどう終わったか」を数値で知らせます。
慣習的に0が正常終了、それ以外はエラーを表すことが多いです。
Hello, world!
環境によってはint main(int argc, char *argv[])という形も使われます。
これは、コマンドライン引数を受け取るための書き方です。
入門段階ではint main(void)を基本形として覚えるとよいです。
C言語の関数の書き方
関数宣言(プロトタイプ宣言)の書き方

C言語では、コンパイラに対して「この関数はこういう形で存在します」と事前に知らせる必要があります。
これを関数宣言あるいはプロトタイプ宣言と呼びます。
一般的な関数宣言の形は次のようになります。
戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, ...);
例として、2つの整数を足し算する関数を宣言してみます。
// 2つのint型の値を受け取り、その合計をint型で返す関数の宣言
int add(int a, int b);
宣言の段階では、中身の処理は書かず、最後をセミコロン;で終わらせる点が重要です。
プロトタイプ宣言は通常、mainより前の位置、もしくは別ファイルのヘッダ(.h)に書きます。
宣言を行っておけば、コンパイラは「この関数がどのような引数と戻り値を持つか」を理解した上で、後のコード(例えばmainの中)での呼び出しを正しくチェックできます。
関数定義の構成要素

関数定義とは、関数の実際の処理(中身)を書く部分のことです。
一般的な構造は次の通りです。
戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, ...) {
// 関数の処理を書く部分
return 戻り値; // 必要に応じて
}
先ほど宣言だけしていたadd関数を、定義まで含めて書くと次のようになります。
#include <stdio.h>
// 関数宣言(プロトタイプ宣言)
int add(int a, int b); // この後に本体がどこかで定義されることを知らせる
// main関数
int main(void) {
int x = 5;
int y = 7;
int result = add(x, y); // add関数を呼び出し
printf("結果: %d\n", result);
return 0;
}
// 関数定義(実際の処理)
int add(int a, int b) {
int sum = a + b; // aとbの合計を計算
return sum; // 合計値を呼び出し元に返す
}
結果: 12
このサンプルから、関数定義には主に4つの要素があることがわかります。
1つは戻り値の型で、「この関数がどの型の値を呼び出し元に返すか」を示します。
2つ目は関数名で、その関数を呼び出すときの識別子になります。
3つ目は引数リストです。
ここで、関数が受け取る値の型と名前を定義します。
4つ目が本体(ブロック)で、波括弧{}で囲まれた中に実行したい処理を書きます。
戻り値の型とvoid関数

関数は、処理の結果を呼び出し元に返す場合と、単に何かの処理だけ行って結果は返さない場合の両方があります。
結果を返す関数では、戻り値の型としてintやdouble、charなどの型を指定し、return文で実際の値を返します。
// 3つの整数の最大値を返す関数
int max3(int a, int b, int c) {
int max = a; // 仮にaが最大とする
if (b > max) max = b;
if (c > max) max = c;
return max; // 最終的な最大値を返す
}
一方で、戻り値を返さない関数もよく使われます。
その場合、戻り値の型としてvoidを指定します。
#include <stdio.h>
// 区切り線を表示するだけの関数(戻り値なし)
void print_line(void) {
printf("--------------------\n");
}
int main(void) {
print_line(); // 区切り線表示
printf("メニュー\n");
print_line(); // もう一度区切り線表示
return 0;
}
--------------------
メニュー
--------------------
void関数ではreturnを書かなくてもかまいませんが、処理を途中で抜けたいときなどにreturn;と書くこともできます。
この場合、戻り値は書きません。
どんな種類の関数を作りたいかによって、戻り値の型を適切に選ぶことが重要です。
計算結果を使い回したい場合は戻り値あり、画面出力やログ記録のように「動作そのもの」が目的のときはvoid関数にする、といった使い分けをするとよいです。
引数の型と個数

関数の引数は、関数が処理のために外部から受け取る値です。
引数があることで、同じロジックをさまざまな入力に対して再利用できます。
C言語の関数は、引数の型と個数を明確に指定する必要があります。
よく使われるパターンをいくつか紹介します。
引数なしの関数は、何も受け取らずに内部だけで処理を完結します。
void hello(void) {
printf("こんにちは!\n");
}
1つだけ引数を受け取る関数は、例えば「整数の2乗を返す」ようなケースで使います。
int square(int x) {
return x * x;
}
複数の引数を受け取るときは、カンマで区切って並べます。
double average(double a, double b, double c) {
return (a + b + c) / 3.0;
}
また、配列を扱う関数では、配列の先頭アドレスと要素数を引数として受け取るのが一般的です。
// 配列の要素の合計を求める関数
int sum_array(const int arr[], int size) {
int sum = 0;
int i;
for (i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
このように、「何を渡して、何をしたいか」を具体的に考えたうえで、引数の型と個数を決めることが、関数設計の第一歩です。
自作関数の作り方と使い方
自作関数を定義して呼び出すまでの流れ

自分で関数を作り、実際に使うまでの基本的な流れを、小さな例で確認してみます。
ここでは、整数を受け取って偶数か奇数かを判定し、結果を表示する関数を作ります。
#include <stdio.h>
// 1. プロトタイプ宣言
void print_even_or_odd(int n); // 引数nが偶数か奇数かを表示する
int main(void) {
int value;
printf("整数を入力してください: ");
scanf("%d", &value); // 整数を入力してもらう
// 2. 自作関数を呼び出す
print_even_or_odd(value);
return 0;
}
// 3. 関数定義(本体)
void print_even_or_odd(int n) {
if (n % 2 == 0) {
printf("%d は偶数です。\n", n);
} else {
printf("%d は奇数です。\n", n);
}
}
実行例(入力に応じて変化します):
整数を入力してください: 10
10 は偶数です。
このコードでは、最初にprint_even_or_oddの宣言を書き、そのあとmainから呼び出し、最後に定義を記述しています。
宣言→呼び出し→定義という順番を意識すると、複数の関数からなるプログラムでも見通しを保ちやすくなります。
値渡しと参照のような使い方

C言語の関数は、基本的に「値渡し」です。
つまり、引数として渡した変数の中身の値がコピーされて関数に渡されるため、関数内で引数の値を変更しても、呼び出し元の変数には影響しません。
#include <stdio.h>
// 値渡しの例
void increment(int x) {
x = x + 1; // このxはコピーなので、呼び出し元には影響しない
printf("関数内 x = %d\n", x);
}
int main(void) {
int a = 5;
increment(a);
printf("main内 a = %d\n", a); // ここではaの値は変わらない
return 0;
}
関数内 x = 6
main内 a = 5
呼び出し元の変数そのものを関数内から書き換えたい場合は、ポインタを使ってアドレス(場所)を渡すことで、結果的に「参照渡しのような」ことができます。
#include <stdio.h>
// ポインタを使って元の変数を書き換える例
void increment_ptr(int *px) {
*px = *px + 1; // *pxは、pxが指す先の変数そのもの
}
int main(void) {
int a = 5;
increment_ptr(&a); // aのアドレスを渡す
printf("main内 a = %d\n", a); // aの値が変化している
return 0;
}
main内 a = 6
このように、「値のコピーだけでよいか」「元の変数を直接変更したいか」に応じて、普通の引数かポインタ引数かを選択します。
ヘッダファイルで関数を分割管理する方法

プログラムが大きくなってくると、1つの.cファイルにすべての関数を書くのは管理が大変です。
そこで、機能ごとにソースファイルを分け、ヘッダファイルで関数宣言をまとめるのが一般的です。
例えば、「数学的な処理をまとめた関数群」をmath_utils.cに書き、その宣言をmath_utils.hに用意し、main.cから使う、という構成を考えます。
math_utils.h(関数宣言をまとめるファイル):
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 2つの整数の最大値を返す関数の宣言
int max2(int a, int b);
// 階乗(n!)を計算する関数の宣言
int factorial(int n);
#endif // MATH_UTILS_H
math_utils.c(実際の処理を書くファイル):
// math_utils.c
#include "math_utils.h" // 自分自身のヘッダをインクルード
int max2(int a, int b) {
return (a > b) ? a : b;
}
int factorial(int n) {
int i;
int result = 1;
for (i = 1; i <= n; i++) {
result *= i;
}
return result;
}
main.c(関数を利用する側):
// main.c
#include <stdio.h>
#include "math_utils.h" // 関数宣言を参照する
int main(void) {
int x = 3, y = 7;
printf("max2(%d, %d) = %d\n", x, y, max2(x, y));
printf("factorial(5) = %d\n", factorial(5));
return 0;
}
出力結果(リンクしてコンパイルした場合):
max2(3, 7) = 7
factorial(5) = 120
このようにヘッダファイルには「宣言」、ソースファイルには「定義」を置くのが基本です。
#ifndef〜#define〜#endifはインクルードガードと呼ばれ、同じヘッダが重ねて読み込まれるのを防ぎます。
よくあるエラーとデバッグのポイント

関数を扱う際によく出会うエラーと、その対処のポイントをいくつか紹介します。
まず多いのが、宣言と定義の不一致です。
例えば、宣言ではint func(double x);としておきながら、定義をint func(int x) { ... }としてしまうと、「型が一致しない」というコンパイルエラーになります。
宣言と定義は、戻り値の型・関数名・引数の型と個数すべてが一致している必要があることを意識してください。
次に、未定義の関数を呼び出すケースもよくあります。
宣言は書いたが定義を忘れていたり、別ファイルに定義しているのにコンパイル・リンク時にそのファイルを指定し忘れたりすると、「未定義の参照」などのリンクエラーになります。
複数ファイルを使う場合は、コンパイルコマンドにすべての.cファイルを含めることが大切です。
また、実行時エラーとしては、ポインタ引数の扱いミスが目立ちます。
例えば、NULLポインタを渡してしまったり、配列のサイズを誤って渡し、範囲外アクセスが起きたりすると、プログラムが異常終了することがあります。
関数にポインタを渡すときには、「実際に有効なメモリを指しているか」「要素数の情報も一緒に渡しているか」を確認する習慣をつけると安全です。
デバッグの際は、printfを使って、関数が受け取っている引数の値や、中間結果を表示してみると、処理の流れを追いやすくなります。
必要ならば、一時的に関数を小さく分割して原因箇所を切り分けるのも有効です。
C言語の標準関数の基本
標準ライブラリ関数とは

C言語には、標準ライブラリと呼ばれる、あらかじめ用意された便利な関数群があります。
これらは、#includeで対応するヘッダファイルをインクルードすることで利用できます。
例えば、画面への出力やキーボードからの入力にはstdio.h、文字列操作にはstring.h、数学的な計算にはmath.hといった具合に、機能ごとにヘッダが分かれています。
車で言えば、標準ライブラリはあらかじめ搭載されている部品のようなもので、自作関数は自分で追加するパーツだとイメージするとわかりやすいです。
まずは標準ライブラリの持つ機能を活用し、その上で足りない部分を自作関数で補うのが効率的です。
入出力関数

入出力には標準入出力関数がよく使われます。
代表的なものはprintfとscanfです。
これらは#include <stdio.h>で利用できます。
#include <stdio.h>
int main(void) {
int age;
double height;
printf("年齢を入力してください: ");
scanf("%d", &age); // 整数の入力
printf("身長(cm)を入力してください: ");
scanf("%lf", &height); // 倍精度実数の入力
printf("あなたは %d 歳で、身長は %.1f cm ですね。\n", age, height);
return 0;
}
年齢を入力してください: 20
身長(cm)を入力してください: 170.5
あなたは 20 歳で、身長は 170.5 cm ですね。
printfでは、"%d"や"%f"といった書式指定子を使って、整数や浮動小数点数などを自由な形式で表示できます。
scanfでも同様に書式指定子を使いますが、引数には変数のアドレス(先頭に&)を渡す点に注意が必要です。
文字列操作関数

文字列の扱いにはstring.hの関数が便利です。
C言語では文字列を終端に'\0'(ヌル文字)を持つ文字配列として表現します。
代表的な関数には次のようなものがあります。
strlen: 文字列の長さを調べるstrcpy: 文字列をコピーするstrcat: 文字列を連結するstrcmp: 文字列を比較する
具体例を見てみます。
#include <stdio.h>
#include <string.h> // 文字列関数を使う
int main(void) {
char name[20] = "Taro";
char greeting[40];
// 文字列をコピー
strcpy(greeting, "Hello, "); // greetingに"Hello, "をコピー
// 文字列を連結
strcat(greeting, name); // greetingの末尾にnameを連結
printf("%s\n", greeting);
printf("名前の長さ: %zu 文字\n", strlen(name));
// 文字列比較
if (strcmp(name, "Taro") == 0) {
printf("名前はTaroです。\n");
}
return 0;
}
Hello, Taro
名前の長さ: 4 文字
名前はTaroです。
文字列操作では、配列の大きさを超えないように注意することが重要です。
コピー先・連結先のバッファが十分なサイズを持つようあらかじめ設計し、必要に応じてstrncpyやstrncatなど、コピー・連結する文字数を制限できる関数の利用も検討します。
数学関数とユーティリティ関数

数学的な計算にはmath.hの関数が、汎用的な処理にはstdlib.hの関数が用意されています。
例えばmath.hには、平方根を求めるsqrt、累乗のpow、三角関数のsinやcosなどがあります。
#include <stdio.h>
#include <math.h> // 数学関数
int main(void) {
double x = 2.0;
printf("sqrt(%.1f) = %.3f\n", x, sqrt(x)); // 平方根
printf("pow(%.1f, 3) = %.3f\n", x, pow(x, 3)); // 3乗
return 0;
}
sqrt(2.0) = 1.414
pow(2.0, 3) = 8.000
stdlib.hには、乱数を生成するrand/srandや、整数の絶対値を求めるabs、文字列を整数に変換するatoiなどがあります。
#include <stdio.h>
#include <stdlib.h> // ユーティリティ関数
#include <time.h> // 時刻を扱うため
int main(void) {
int i;
// 乱数の種を現在時刻で初期化
srand((unsigned int)time(NULL));
// 0〜9の乱数を5個表示
for (i = 0; i < 5; i++) {
int r = rand() % 10;
printf("%d ", r);
}
printf("\n");
// 文字列を整数に変換
const char *str = "1234";
int value = atoi(str);
printf("atoi(\"%s\") = %d\n", str, value);
return 0;
}
出力結果例(乱数部分は毎回変わります):
3 7 0 9 1
atoi("1234") = 1234
これらの標準関数を組み合わせることで、かなり複雑な処理も短いコードで書けるようになります。
標準関数と自作関数を組み合わせるコツ

効果的なプログラムを書くには、標準関数に任せる部分と、自作関数で実装する部分の役割分担を意識することが大切です。
標準関数は、入出力、文字列処理、数学計算、メモリ管理といった多くの共通処理をカバーしています。
これらを自前で実装する必要はほとんどありません。
一方で、自分のプログラム特有のロジック、たとえば「テストの点数から成績を判定する」「ゲームのルールに従った状態遷移を行う」といった部分は、自作関数で実装する必要があります。
次の例は、標準関数と自作関数を組み合わせて「配列の平均値と最大値を計算して表示する」プログラムです。
#include <stdio.h>
// 配列の平均値を求める自作関数
double calc_average(const int arr[], int size) {
int sum = 0;
int i;
for (i = 0; i < size; i++) {
sum += arr[i];
}
return (double)sum / size;
}
// 配列の最大値を求める自作関数
int calc_max(const int arr[], int size) {
int i;
int max = arr[0];
for (i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
int main(void) {
int scores[] = {80, 92, 75, 88, 90};
int size = (int)(sizeof(scores) / sizeof(scores[0]));
// 自作関数で処理を行い、標準関数printfで結果を表示
double avg = calc_average(scores, size);
int max = calc_max(scores, size);
printf("平均点: %.1f\n", avg);
printf("最高点: %d\n", max);
return 0;
}
平均点: 85.0
最高点: 92
ここでは、入出力にはprintfを使い、配列の解析ロジックはcalc_averageとcalc_maxという自作関数に切り分けています。
「標準関数は道具、自作関数はレシピ」と考え、うまく組み合わせて使うとコードが整理されやすくなります。
まとめ
C言語の関数は、処理を名前付きの部品としてまとめる仕組みであり、プログラムの見通しや再利用性を大きく高めてくれます。
main関数を起点に、プロトタイプ宣言と定義を正しく書き分け、戻り値や引数の型・個数を意識して自作関数を設計することが重要です。
また、値渡しとポインタを使った「参照のような」渡し方を理解することで、より柔軟な関数設計が可能になります。
標準ライブラリ関数(入出力、文字列操作、数学、ユーティリティなど)を積極的に活用しつつ、自作関数で問題固有のロジックを組み立てていくことで、C言語のプログラミングは一段と効率的で楽しいものになります。
