【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)
POINT

関数定義時に使用する引数のことを仮引数(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
POINT

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

ポインタ渡し

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

#include <stdio.h>

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

int main(void) {

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

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

実行結果

0
POINT

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

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

値を書き換えられたくない場合には、値渡しを使う…ではなくて、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
POINT

ポインタとして受け取るので、関数内で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

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

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