C言語 PR

【C言語】関数の定義方法と使い方

記事内に商品プロモーションを含む場合があります

この記事では、C言語の関数の使い方について解説します。

何度も実行する処理や長い処理を毎回書くのはめんどくさいですよね? そんな時は、関数を使います。関数を定義することで定義した処理を関数名で何度も呼び出すことができます。

また、便利な関数は別ファイルにまとめておくことで次にプログラムを作成する際にも再利用することができます。

それでは、C言語の関数の使い方を見ていきましょう!

関数の使い方

C言語の関数の定義・呼び出し方法を解説します。

定義

下記が一番シンプルな関数の定義方法です。

void 関数名() {
    なんらかの処理
}

呼び出し

関数を使用するには、以下のように呼び出します。

関数名()

サンプル

簡単な関数を定義し、呼び出してみましょう!

#include <stdio.h>

// 関数定義
void func(){
    printf("func関数が呼び出されました!\n");
}

int main(void) {

    // 関数呼び出し
    func();
}

実行結果

func関数が呼び出されました!

呼び出し時に値を受け取る:引数

関数には、呼び出し時に値を受け取る「引数」という機能が用意されています。関数内では引数、または関数内で定義したオブジェクトのみ使用可能です。

定義

引数は、以下のように定義します。

void 関数名(型名 引数1, 型名 引数2, ..., 型名 引数N){
    なにかしらの処理
}

呼び出し

引数が定義されている関数は、以下のように呼び出します。

関数名(値1, 値2, ..., 値N)

関数定義時に使用する引数のことを「仮引数(parameter)」、関数呼び出し時に引き渡される値のことを「実引数(argument)」と呼ぶ

サンプル

以下のコードでは、2つの引数を受け取る関数を定義し、使用しています。

#include <stdio.h>

// 関数定義
void add(int num1, int num2){
    printf("%d + %d = %d\n", num1, num2, num1 + num2);
}

int main(void) {

    // 関数呼び出し
    add(1, 2);
}

実行結果

1 + 2 = 3

呼び出し元に値を返す:戻り値

関数には、処理結果を呼び出し元に返せる「戻り値」という機能が用意されています。

定義

void句だった箇所に返す値の型を指定し、return句で返す値を定義します。

型名 関数名(){
    return 返す値
}

呼び出し

以下のように戻り値を受け取ります。

戻り値の型名 オブジェクト名 = 関数名()

サンプル

先ほどのまでのサンプルコードでは、関数内で結果を出力していましたが、今回は戻り値で処理結果を返し、呼び出し元で処理結果を出力してみましょう!

#include <stdio.h>

// 関数定義
int add(int num1, int num2){
    return num1 + num2;
}

int main(void) {

    // 関数の処理結果をresult変数で受け取る
    int result = add(1, 2);
    printf("result: %d\n", result);
}

実行結果

result: 3

プロトタイプ宣言

C言語では、定義よりも先に変数や関数を呼び出すとエラーになってしまいます。

例えば、以下のようなコードはエラーが発生します。

#include <stdio.h>

int main(void) {
    // あとで定義している関数の呼び出し
    func();
}

void func(){
    printf("func関数!!\n");
}

C言語では、上から順番に処理されていくので、5行目でfunc関数を呼び出した時点では、まだfunc関数は定義されていません。

あとで定義する関数を先に呼び出したい場合は「プロトタイプ宣言」をしておきます。プロトタイプ宣言とは「こんな関数を後から定義するよ」と先に宣言だけしておくことを言います。

書式

プロトタイプ宣言は、以下のように宣言します。

void 関数名()

戻り値や引数が定義されている場合は、以下のようにします。

型名 関数名(型名 引数1, 型名 引数2, ..., 型名 引数N)

サンプル

試しにプロトタイプ宣言をし、定義よりも先に関数を呼び出してみます。

#include <stdio.h>

// プロトタイプ宣言
void func();
int add(int num1, int num2);

// 関数を呼び出す
int main(void) {
    func();
    int result = add(1, 2);
    printf("result: %d\n", result);
}

// プロトタイプ宣言した関数を定義
void func(){
    printf("func関数!!\n");
}

int add(int num1, int num2){
    return num1 + num2;
}

実行結果

func関数!!
result: 3

値渡しとポインタ渡し

C言語では関数呼び出しの際に、引数に値を渡す方法として「値渡し」「ポインタ渡し」が存在します。

それぞれの特徴と使い方を見ていきましょう!
※ C言語では参照渡しは存在しません。C++で追加されました。

値渡し

値渡しとは、値をコピーして渡す方法です。値をコピーしているので関数内で値が書き換えられたとしても元のオブジェクトの値は書き換えられません。

#include <stdio.h>

// 関数定義
void func(int num){
    // 引数を書き換えてみる
    num = 0;
}

int main(void) {

    int num = 1;
    func(num);

    printf("%d\n", num);
}

実行結果

1

値渡しでは、型のサイズによってコストが変化します。特別な理由が無い限り、組み込み型以外では使わないようにしましょう。

ポインタ渡し

ポインタ渡しとは、アドレスをコピーして扱う方法です。アドレスを間接参照することで関数内で値が書き換えられた場合、元のオブジェクトの値も書き換えられます。

#include <stdio.h>

// 関数定義
void func(int* num){
    // 引数を書き換えてみる
    *num = 0;
}

int main(void) {

    int num = 1;
    // アドレスを渡す
    func(&num);

    printf("%d\n", num);
}

実行結果

0
ポインタ渡しでは、型のサイズによってコストは変化しません

値を書き換えられたくない場合

関数内で値を書き換えられたくない場合には、値渡しを使う...ではなくてconstを使いましょう!

// 値の書き換え不可
const 型名* 引数

// 値、アドレスの変更不可
const 型名* const 引数

例えば、以下のように使います。

#include <stdio.h>

// プロトタイプ宣言
void func(const int* num);

int main(void){
    int num = 1;
    func(&num);

    printf("%d\n", num);
}

void func(const int* num){
    *num = 0;
}

このコードを実行するとコンパイルエラーが発生します。これで関数内で間違えて書き換えてしまった場合もエラーで知らせてくれます。

配列や構造体を使う方法

関数の引数や戻り値には、配列や構造体を指定したい場合があります。
それぞれの指定方法を確認してみましょう!

配列の場合

C言語では、配列そのものを指定することはできません。なので、ポインタを使って配列のアドレスを指定します。

#include <stdio.h>

// プロトタイプ宣言
void func(int* nums);

int main(void){

    int nums[3] = {1, 2, 3};
    func(nums);

    // 出力
    for (int i = 0; i < 3; i++)
    {
        printf("%d\n", nums[i]);
    }
}

// 配列の要素をそれぞれ2倍にするだけの関数
void func(int* nums){
    nums[0] *= 2;
    nums[1] *= 2;
    nums[2] *= 2;
}

実行結果

2
4
6

ポインタとして受け取るので関数内でsizeof演算子を使って要素数を求めることはできません。なので、要素数を扱いたい場合は引数で受け取りましょう!

構造体の場合

構造体は実体を渡すこともできますが極力ポインタ渡しを活用しましょう。

#include <stdio.h>

// 構造体定義
typedef struct
{
    char* name;
    int age;
}person;

// プロトタイプ宣言
void print(person* p);

int main(void){

    person mike = {"Mike", 19};
    print(&mike);
}

void print(person* p){
    // アロー演算子を用いる
    printf("name: %s, age: %d\n", p->name, p->age);
}

実行結果

name: Mike, age: 19

関数形式マクロ

関数はマクロで定義することができます。

書式

引数に型名を指定する必要はなく、戻り値をreturn句で返す必要もありません。

#define 関数名(変数) (処理内容)

サンプル

試しにいろんな関数形式マクロを定義して使ってみましょう!

#include <stdio.h>

#define add(n1, n2) (n1 + n2)
#define print(i) printf("result: %d\n", i)
#define line() printf("---------------\n")

int main(void){
    line();
    print(add(1, 2));
    line();
}

実行結果

---------------
result: 3
---------------

関数ポインタ

C言語では、変数だけでなく関数もポインタとして扱うことができます。

宣言

関数ポインタは以下のように宣言します。

型名 (*変数名)(仮引数)

呼び出し

以下の2パターンで呼び出すことができます。

変数名(実引数)

(*変数名)(実引数)

前者の呼び出し方では、普通の関数の呼び出し方と変わらず見分けることができません。なので、後者の呼び出し方を使ってポインタからの間接参照であることがわかるように記述しましょう。

サンプル

以下のコードでは、関数ポインタを使って動的に呼び出している関数を変更しています。

#include <stdio.h>

int sum(int n1, int n2){
    return n1 + n2;
}

int diff(int n1, int n2){
    return n1 - n2;
}

int main(void){
    // 関数ポインタ宣言・初期化
    int (*fp)(int, int) = sum;
    // 関数ポインタから関数の呼び出し
    int result = fp(1, 2);
    printf("result: %d\n", result);

    // 関数ポインタに関数アドレスを代入
    fp = diff;
    // 関数ポインタから関数の呼び出し
    result = (*fp)(1, 2);
    printf("result: %d\n", result);
}

実行結果

result: 3
result: -1

まとめ

この記事では、C言語の関数について解説しました。

今回のおさらい

以下のコードには、今回の内容をだいたい詰め込みました。よくわからない箇所があったら記事を読み返してみてください。

#include <stdio.h>

// 構造体
typedef struct {
    int suugaku;
    int eigo;
    int kokugo;
} score;

// 関数形式マクロ
#define print(s, f) printf("%s: %.1f\n", s, f)

// プロトタイプ宣言
float total_score(score*);
float average_score(score*);

int main(void){
    // 構造体初期化
    score tanaka = {58, 36, 91};
    // 関数ポインタに関数アドレスを代入
    float (*fp)(score*) = total_score;
    // 関数形式マクロを使って結果を出力
    print("合計", (*fp)(&tanaka));

    fp = average_score;
    print("平均", (*fp)(&tanaka));
}

// 関数定義
float total_score(score* s){
    return s->kokugo + s->suugaku + s->eigo;
}

float average_score(score* s){
    return (s->kokugo + s->suugaku + s->eigo) / 3;
}

実行結果

合計: 185.0
平均: 61.0

それでは今回の内容はここまでです!ではまたどこかで〜( ・∀・)ノ