【C言語】引数と戻り値の違いや使い方を解説

C言語のプログラミングにおいて、関数は非常に重要な役割を果たします。

この記事では、関数にデータを渡す「引数」と、関数からデータを受け取る「戻り値」について詳しく解説します。

目次から探す

引数の使い方

C言語において、関数はプログラムの基本的な構成要素です。

関数は特定のタスクを実行し、その結果を返すことができます。

関数にデータを渡す方法として「引数」があります。

ここでは、引数の使い方について詳しく解説します。

関数に引数を渡す方法

関数に引数を渡す方法には「値渡し」と「参照渡し」の2つがあります。

値渡し

値渡しは、関数に引数として渡された値のコピーを渡す方法です。

関数内で引数の値を変更しても、元の値には影響を与えません。

#include <stdio.h>
void addTen(int num) {
    num += 10; // 引数の値を変更
    printf("関数内の値: %d\n", num);
}
int main() {
    int value = 5;
    addTen(value);
    printf("関数外の値: %d\n", value);
    return 0;
}

このプログラムの実行結果は以下の通りです。

関数内の値: 15
関数外の値: 5

関数内で引数の値を変更しても、関数外の値には影響がありません。

参照渡し

参照渡しは、引数として渡された変数のアドレスを渡す方法です。

関数内で引数の値を変更すると、元の値にも影響を与えます。

#include <stdio.h>
void addTen(int *num) {
    *num += 10; // 引数の値を変更
    printf("関数内の値: %d\n", *num);
}
int main() {
    int value = 5;
    addTen(&value);
    printf("関数外の値: %d\n", value);
    return 0;
}

このプログラムの実行結果は以下の通りです。

関数内の値: 15
関数外の値: 15

関数内で引数の値を変更すると、関数外の値にも影響があります。

引数のデータ型

引数として渡すことができるデータ型には、基本データ型、ポインタ型、配列型があります。

基本データ型(int, float, charなど)

基本データ型は、int、float、charなどのデータ型です。

これらのデータ型は値渡しで渡されます。

#include <stdio.h>
void printInt(int num) {
    printf("整数: %d\n", num);
}
void printFloat(float num) {
    printf("浮動小数点数: %.2f\n", num);
}
void printChar(char ch) {
    printf("文字: %c\n", ch);
}
int main() {
    printInt(10);
    printFloat(3.14);
    printChar('A');
    return 0;
}

ポインタ型

ポインタ型は、変数のアドレスを指すデータ型です。

参照渡しで使用されることが多いです。

#include <stdio.h>
void increment(int *num) {
    (*num)++;
}
int main() {
    int value = 5;
    increment(&value);
    printf("インクリメント後の値: %d\n", value);
    return 0;
}

配列型

配列型は、同じデータ型の要素が連続して並んだデータ型です。

配列の先頭アドレスが渡されます。

#include <stdio.h>
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int main() {
    int array[] = {1, 2, 3, 4, 5};
    int size = sizeof(array) / sizeof(array[0]);
    printArray(array, size);
    return 0;
}

可変長引数

可変長引数は、関数に渡す引数の数が可変である場合に使用されます。

標準ライブラリのstdarg.hを使用して実現します。

可変長引数の定義

可変長引数を使用する関数は、少なくとも1つの固定引数を持ち、その後に省略記号(...)を続けます。

#include <stdarg.h>
#include <stdio.h>
void printNumbers(int count, ...) {
    va_list args;
    va_start(args, count);
    for (int i = 0; i < count; i++) {
        int num = va_arg(args, int);
        printf("%d ", num);
    }
    va_end(args);
    printf("\n");
}

可変長引数の使い方

可変長引数を使用する関数を呼び出す際には、固定引数の後に任意の数の引数を渡します。

int main() {
    printNumbers(3, 10, 20, 30);
    printNumbers(5, 1, 2, 3, 4, 5);
    return 0;
}

このプログラムの実行結果は以下の通りです。

10 20 30 
1 2 3 4 5

可変長引数を使用することで、関数に渡す引数の数を柔軟に変更することができます。

戻り値の使い方

関数は特定の処理を行い、その結果を呼び出し元に返すことができます。

この返される値を「戻り値」と呼びます。

戻り値を使うことで、関数の結果を他の部分で利用することができます。

関数から値を返す方法

基本的な戻り値の使い方

基本的な戻り値の使い方は、関数の定義で戻り値のデータ型を指定し、return文を使って値を返します。

以下は、整数を返す関数の例です。

#include <stdio.h>
// 2つの整数の和を返す関数
int add(int a, int b) {
    return a + b;
}
int main() {
    int result = add(3, 5);
    printf("Result: %d\n", result); // 出力: Result: 8
    return 0;
}

この例では、add関数が2つの整数を受け取り、その和を返しています。

main関数add関数を呼び出し、その結果をresult変数に格納しています。

複数の値を返す方法

C言語では、関数は1つの値しか返すことができません。

しかし、ポインタや構造体を使うことで、複数の値を返すことができます。

以下は、ポインタを使って複数の値を返す例です。

#include <stdio.h>
// 2つの整数の和と差を計算する関数
void calculate(int a, int b, int *sum, int *diff) {
    *sum = a + b;
    *diff = a - b;
}
int main() {
    int sum, diff;
    calculate(10, 3, &sum, &diff);
    printf("Sum: %d, Difference: %d\n", sum, diff); // 出力: Sum: 13, Difference: 7
    return 0;
}

この例では、calculate関数が2つの整数の和と差を計算し、ポインタを使って結果を返しています。

戻り値のデータ型

基本データ型(int, float, charなど)

関数の戻り値として、基本データ型を使用することができます。

以下は、いくつかの基本データ型を戻り値として使う例です。

#include <stdio.h>
// 整数を返す関数
int getInt() {
    return 42;
}
// 浮動小数点数を返す関数
float getFloat() {
    return 3.14f;
}
// 文字を返す関数
char getChar() {
    return 'A';
}
int main() {
    printf("Int: %d\n", getInt()); // 出力: Int: 42
    printf("Float: %.2f\n", getFloat()); // 出力: Float: 3.14
    printf("Char: %c\n", getChar()); // 出力: Char: A
    return 0;
}

ポインタ型

ポインタ型を戻り値として使うことで、関数から配列や文字列などの複雑なデータを返すことができます。

以下は、文字列を返す関数の例です。

#include <stdio.h>
// 文字列を返す関数
char* getString() {
    return "Hello, World!";
}
int main() {
    printf("String: %s\n", getString()); // 出力: String: Hello, World!
    return 0;
}

構造体型

構造体を使うことで、複数のデータをまとめて返すことができます。

以下は、構造体を使って複数の値を返す例です。

#include <stdio.h>
// 2次元の点を表す構造体
typedef struct {
    int x;
    int y;
} Point;
// 点を返す関数
Point getPoint() {
    Point p = {10, 20};
    return p;
}
int main() {
    Point p = getPoint();
    printf("Point: (%d, %d)\n", p.x, p.y); // 出力: Point: (10, 20)
    return 0;
}

void型の戻り値

void型の定義

void型は、関数が値を返さないことを示すために使われます。

void型の関数は、主に副作用を持つ操作(例えば、画面にメッセージを表示するなど)を行うために使われます。

void型の使い方

以下は、void型の関数の例です。

#include <stdio.h>
// メッセージを表示する関数
void printMessage() {
    printf("Hello, World!\n");
}
int main() {
    printMessage(); // 出力: Hello, World!
    return 0;
}

この例では、printMessage関数が画面にメッセージを表示しますが、値は返しません。

void型の関数は、return文を使わずに終了することができますが、必要に応じてreturn文を使って関数を早期に終了することもできます。

#include <stdio.h>
// メッセージを表示する関数
void printMessage(int flag) {
    if (flag == 0) {
        return; // 何も表示せずに終了
    }
    printf("Hello, World!\n");
}
int main() {
    printMessage(0); // 何も出力しない
    printMessage(1); // 出力: Hello, World!
    return 0;
}

このように、void型の関数は特定の条件で早期に終了することも可能です。

引数と戻り値の実践例

ここでは、引数と戻り値を使った具体的な関数の例を紹介します。

基本的な関数から複雑な関数、そして実際のプロジェクトでの応用例までを解説します。

基本的な関数の例

引数を使った関数の例

まずは、引数を使った基本的な関数の例を見てみましょう。

以下のコードは、2つの整数を引数として受け取り、その和を計算して表示する関数です。

#include <stdio.h>
// 2つの整数を受け取り、その和を表示する関数
void add(int a, int b) {
    int sum = a + b;
    printf("Sum: %d\n", sum); // 和を表示
}
int main() {
    add(3, 5); // 関数を呼び出し
    return 0;
}

この例では、add関数が2つの整数引数を受け取り、その和を計算して表示します。

戻り値を使った関数の例

次に、戻り値を使った基本的な関数の例を見てみましょう。

以下のコードは、2つの整数を引数として受け取り、その和を計算して戻り値として返す関数です。

#include <stdio.h>
// 2つの整数を受け取り、その和を返す関数
int add(int a, int b) {
    return a + b; // 和を返す
}
int main() {
    int result = add(3, 5); // 関数を呼び出し、戻り値を受け取る
    printf("Sum: %d\n", result); // 戻り値を表示
    return 0;
}

この例では、add関数が2つの整数引数を受け取り、その和を計算して戻り値として返します。

複雑な関数の例

複数の引数を持つ関数

次に、複数の引数を持つ関数の例を見てみましょう。

以下のコードは、3つの整数を引数として受け取り、その平均を計算して表示する関数です。

#include <stdio.h>
// 3つの整数を受け取り、その平均を表示する関数
void average(int a, int b, int c) {
    float avg = (a + b + c) / 3.0; // 平均を計算
    printf("Average: %.2f\n", avg); // 平均を表示
}
int main() {
    average(3, 5, 7); // 関数を呼び出し
    return 0;
}

この例では、average関数が3つの整数引数を受け取り、その平均を計算して表示します。

複数の戻り値を持つ関数

C言語では、関数が複数の戻り値を直接返すことはできませんが、ポインタや構造体を使って間接的に複数の値を返すことができます。

以下のコードは、2つの整数を引数として受け取り、その和と差を計算してポインタを使って返す関数です。

#include <stdio.h>
// 2つの整数を受け取り、その和と差を計算してポインタを使って返す関数
void calculate(int a, int b, int *sum, int *diff) {
    *sum = a + b; // 和を計算してポインタに格納
    *diff = a - b; // 差を計算してポインタに格納
}
int main() {
    int sum, diff;
    calculate(10, 3, &sum, &diff); // 関数を呼び出し
    printf("Sum: %d, Difference: %d\n", sum, diff); // 結果を表示
    return 0;
}

この例では、calculate関数が2つの整数引数を受け取り、その和と差を計算してポインタを使って返します。

実際のプロジェクトでの応用

引数と戻り値を使ったプロジェクト例

実際のプロジェクトでは、引数と戻り値を使って複雑な処理を行うことがよくあります。

例えば、以下のコードは、配列の要素をソートする関数を定義し、その結果を表示する例です。

#include <stdio.h>
// 配列をソートする関数
void sort(int arr[], int n) {
    int i, j, temp;
    for (i = 0; i < n-1; i++) {
        for (j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                // 要素を交換
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}
int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    int n = sizeof(arr)/sizeof(arr[0]);
    sort(arr, n); // 関数を呼び出し
    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]); // ソートされた配列を表示
    }
    printf("\n");
    return 0;
}

この例では、sort関数が配列とその要素数を引数として受け取り、配列をソートします。

効率的な引数と戻り値の使い方

効率的な引数と戻り値の使い方にはいくつかのポイントがあります。

例えば、大きなデータ構造を引数として渡す場合、値渡しではなくポインタ渡しを使うことでメモリの使用量を減らすことができます。

また、戻り値として複数の値を返す必要がある場合、構造体を使うことでコードの可読性を向上させることができます。

以下のコードは、構造体を使って複数の値を返す例です。

#include <stdio.h>
// 和と差を格納する構造体
typedef struct {
    int sum;
    int diff;
} Result;
// 2つの整数を受け取り、その和と差を計算して構造体で返す関数
Result calculate(int a, int b) {
    Result result;
    result.sum = a + b;
    result.diff = a - b;
    return result;
}
int main() {
    Result result = calculate(10, 3); // 関数を呼び出し
    printf("Sum: %d, Difference: %d\n", result.sum, result.diff); // 結果を表示
    return 0;
}

この例では、calculate関数が2つの整数引数を受け取り、その和と差を計算して構造体で返します。

これにより、複数の値を効率的に返すことができます。

以上が、引数と戻り値の実践例に関する解説です。

これらの例を参考にして、実際のプログラムで引数と戻り値を効果的に活用してください。

よくある質問とトラブルシューティング

引数に関するよくある質問

引数のデータ型の不一致

C言語では、関数に渡す引数のデータ型が一致していないとコンパイルエラーが発生します。

例えば、関数が int型の引数を期待しているのに float型の値を渡すとエラーになります。

#include <stdio.h>
void printInt(int num) {
    printf("%d\n", num);
}
int main() {
    float myFloat = 3.14;
    printInt(myFloat); // エラー: 引数のデータ型が一致しない
    return 0;
}

この場合、printInt関数int型の引数を期待していますが、main関数から float型の値を渡しているためエラーが発生します。

正しいデータ型にキャストするか、適切なデータ型の値を渡すように修正します。

#include <stdio.h>
void printInt(int num) {
    printf("%d\n", num);
}
int main() {
    int myInt = 3;
    printInt(myInt); // 正しい引数のデータ型
    return 0;
}

可変長引数の使い方

可変長引数は、関数が異なる数の引数を受け取ることができる機能です。

stdarg.h ヘッダファイルを使用して実装します。

#include <stdio.h>
#include <stdarg.h>
void printNumbers(int count, ...) {
    va_list args;
    va_start(args, count);
    for (int i = 0; i < count; i++) {
        int num = va_arg(args, int);
        printf("%d ", num);
    }
    va_end(args);
    printf("\n");
}
int main() {
    printNumbers(3, 1, 2, 3); // 1 2 3
    printNumbers(5, 10, 20, 30, 40, 50); // 10 20 30 40 50
    return 0;
}

この例では、printNumbers関数が可変長引数を受け取り、指定された数の整数を出力します。

戻り値に関するよくある質問

戻り値のデータ型の不一致

関数の戻り値のデータ型が一致していない場合もエラーが発生します。

例えば、int型の戻り値を期待している関数が float型の値を返すとエラーになります。

#include <stdio.h>
int getInt() {
    return 3.14; // エラー: 戻り値のデータ型が一致しない
}
int main() {
    int result = getInt();
    printf("%d\n", result);
    return 0;
}

この場合、getInt関数int型の戻り値を期待していますが、float型の値を返しているためエラーが発生します。

正しいデータ型の値を返すように修正します。

#include <stdio.h>
int getInt() {
    return 3; // 正しい戻り値のデータ型
}
int main() {
    int result = getInt();
    printf("%d\n", result);
    return 0;
}

複数の戻り値の扱い方

C言語では、関数が複数の値を返すことはできませんが、ポインタや構造体を使って複数の値を返すことができます。

#include <stdio.h>
void getTwoValues(int *a, int *b) {
    *a = 10;
    *b = 20;
}
int main() {
    int x, y;
    getTwoValues(&x, &y);
    printf("x: %d, y: %d\n", x, y); // x: 10, y: 20
    return 0;
}

この例では、getTwoValues関数がポインタを使って2つの値を返しています。

トラブルシューティング

コンパイルエラーの解決方法

コンパイルエラーは、コードが正しくないためにコンパイラがコードを翻訳できない場合に発生します。

以下は一般的なコンパイルエラーとその解決方法です。

  • シンタックスエラー: コードの文法が間違っている場合に発生します。

例えば、セミコロンの欠如や括弧の不一致など。

#include <stdio.h>
int main() {
	printf("Hello, World!\n") // セミコロンが欠如 
	return 0;
}
  • 修正:

#include <stdio.h>
int main() {
    printf("Hello, World!\n"); // セミコロンを追加
    return 0;
}
  • データ型の不一致: 関数の引数や戻り値のデータ型が一致していない場合に発生します。
#include <stdio.h> 
void printInt(int num) {
	printf("%d\n", num); 
} 
int main() {
	float myFloat = 3.14; 
	printInt(myFloat); // データ型の不一致 
	return 0; 
}
  • 修正:
#include <stdio.h>
void printInt(int num) {
	printf("%d\n", num);
}
int main() {
	int myInt = 3;
	printInt(myInt); // データ型を一致させる
	return 0;
}

実行時エラーの解決方法

実行時エラーは、プログラムが実行されている間に発生するエラーです。

以下は一般的な実行時エラーとその解決方法です。

  • ゼロ除算エラー: 0で除算しようとした場合に発生します。
#include <stdio.h> 
int main() { 
	int a = 10;
	int b = 0;
	int result = a / b; // ゼロ除算エラー 
	printf("%d\n", result); 
	return 0; 
}
  • 修正
#include <stdio.h>
int main() {
	int a = 10;
	int b = 0;
	if (b != 0) {
		int result = a / b;
		printf("%d\n", result);
	} else {
		printf("Error: Division by zero\n");
	}
	return 0;
}
  • ヌルポインタ参照: ヌルポインタを参照しようとした場合に発生します。
#include <stdio.h> 
int main() { 
	int *ptr = NULL; 
	printf("%d\n", *ptr); // ヌルポインタ参照 
	return 0; 
}
  • 修正
#include <stdio.h>
int main() {
	int value = 10;
	int *ptr = &value;
	if (ptr != NULL) {
		printf("%d\n", *ptr);
	} else {
		printf("Error: Null pointer reference\n");
	}
	return 0;
}

これらのトラブルシューティングの方法を理解しておくことで、C言語のプログラムをより効率的にデバッグし、エラーを解決することができます。

目次から探す