C言語で理解する参照渡し!初心者のための完全ガイド10選

C言語と参照渡しのロゴを背景に、ガイドのタイトルが大きく表示されています。C言語
この記事は約12分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

本記事では、C言語における参照渡しの理解を深めることを目的に、初心者から中級者までが参照渡しの概念、実装方法、注意点、そしてそれらをどのように対処・カスタマイズするかを理解できるように解説します。

これにより、あなたのコードはより効率的で安全になります。

●参照渡しとは

参照渡しはC言語における重要な概念で、関数が変数の実際の値を直接操作できるようにするテクニックです。

○ポインタと参照

参照渡しの基本は「ポインタ」にあります。

ポインタは、変数がメモリ上に存在するアドレスを格納する特殊な変数です。

参照渡しは、これらのポインタを利用して、関数が呼び出し元の変数を直接操作できるようにします。

○参照渡しのメリット

参照渡しの最大のメリットは、関数内での変更がその変数の実際の値に影響を及ぼすことです。

これにより、複数の値を同時に返すことが可能になり、また大きなデータ構造をコピーせずに渡すことが可能になるため、プログラムのパフォーマンスを向上させることができます。

●参照渡しを使った関数の作り方

次に、参照渡しを用いた関数の作成方法について説明します。

基本的な参照渡しを行う関数と、参照渡しを用いて配列を操作する関数のサンプルコードを紹介します。

○サンプルコード1:基本的な参照渡しの関数

void changeValue(int* p) {
    *p = 10;
}

int main() {
    int x = 5;
    changeValue(&x);
    printf("%d\n", x);  // 10を出力
    return 0;
}

このコードでは、changeValueという関数を使って、main関数内の変数xの値を直接変更しています。

この例では、changeValue関数の引数pには、main関数の変数xのアドレスが渡され、そのアドレスにある値が直接書き換えられています。

その結果、main関数内の変数xの値も変更され、”10″が出力されます。

○サンプルコード2:参照渡しを使った配列操作

void changeArray(int* arr) {
    arr[0] = 10;
}

int main() {
    int numbers[3] = {1, 2, 3};
    changeArray(numbers);
    printf("%d\n", numbers[0]);  // 10を出力
    return 0;
}

このコードでは、changeArray関数を使って、main関数内の配列numbersの要素を直接変更しています。

この例では、changeArray関数の引数arrには、main関数の配列numbersのアドレスが渡され、そのアドレスにある値が直接書き換えられています。

その結果、main関数内の配列numbersの最初の要素も変更され、”10″が出力されます。

●参照渡しの詳細な使い方

次に、複数の値を参照渡しで変更する方法と、参照渡しを使った再帰関数の作り方を解説します。

○サンプルコード3:複数の値を参照渡しで変更

次に、参照渡しを使用して関数内で複数の値を変更する方法を見ていきましょう。

下記のコードでは、2つの変数aとbを関数changeValuesの引数として渡し、その値を変更しています。

#include <stdio.h>

void changeValues(int *a, int *b) {
    *a = 5;
    *b = 10;
}

int main() {
    int a = 0;
    int b = 0;
    changeValues(&a, &b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

このコードでは、「changeValues」という関数を作成し、その引数としてポインタ’a’と’b’を取っています。

関数内部で、これらのポインタが指す値を直接変更することが可能です。

そのため、main関数内の変数’a’と’b’の値も変更されます。関数内で’a’に5を、’b’に10を代入しています。

これらの代入された値は、関数の外側からも反映されるため、main関数で変数の値を出力すると’a’は5、’b’は10と表示されます。

以上のように、参照渡しを使うことで関数内部で複数の変数の値を一度に変更することが可能になります。

特に大きなプログラムでは、一度に多くの値を更新する場合が頻繁にあるため、このような特性をうまく利用することが重要です。

次に進む前に、上記のコードを自身で試して、複数の変数を参照渡しで変更できることを確認してみてください。

その結果、コンソールには「a = 5, b = 10」と表示されるはずです。

ここからも、関数内で行った変更がmain関数に反映されていることがわかります。

○サンプルコード4:参照渡しを使った再帰関数

参照渡しは再帰関数と一緒に使うことも多いです。再帰関数は自分自身を呼び出す関数のことで、特定の問題を解決するためによく使用されます。

参照渡しを利用した再帰関数のサンプルコードを紹介します。

#include <stdio.h>

void countDown(int *n) {
    if(*n == 0) {
        return;
    } else {
        printf("%d\n", *n);
        (*n)--;
        countDown(n);
    }
}

int main() {
    int count = 5;
    countDown(&count);
    return 0;
}

このコードでは、「countDown」という再帰関数を作成しています。

この関数は引数としてポインタ’n’を取り、その値が0になるまで自分自身を呼び出し続けます。

呼び出すたびに、’*n’の値を1ずつ減らしています。

その結果、main関数では元の値である5から1までの数字が順番に出力されます。

再帰関数と参照渡しを組み合わせることで、再帰的に状態を更新しながら処理を進めることが可能になります。

これにより、ループ構造を使わずに一定の処理を繰り返すことができます。

このサンプルコードを自身で試すと、「5」「4」「3」「2」「1」という結果が表示されるはずです。

これは、再帰的に呼び出した関数内でポインタが指す値を更新しているからです。

一度この動作を理解すれば、より高度な再帰処理も可能になるでしょう。

●参照渡しの注意点

C言語での参照渡しを使用する際には、特定の注意点が存在します。

参照渡しによって生じる可能性がある問題を理解することは、コードの安全性と効率性を確保するために重要です。

○サンプルコード5:参照渡しにおけるセグメンテーション違反

参照渡しにおける一つの頻出エラーは「セグメンテーション違反」です。

これはメモリ領域を誤ってアクセスした際に発生するエラーで、下記のコードはその一例です。

#include <stdio.h>

void changeValue(int *ptr) {
    ptr = NULL;
    *ptr = 100;
}

int main() {
    int x = 10;
    changeValue(&x);
    printf("%d\n", x);
    return 0;
}

このコードでは、changeValue関数内でポインタptrをNULLに設定しています。

そして、そのNULLポインタに値を設定しようとするため、セグメンテーション違反が発生します。

このコードを実行すると、実際にはエラーが発生し、プログラムはクラッシュします。

○サンプルコード6:参照渡しにおける未初期化変数の使用

参照渡しを使う際のもう一つのよくある問題は、未初期化の変数を使用することです。

次のサンプルコードを見てみましょう。

#include <stdio.h>

void printValue(int *ptr) {
    printf("%d\n", *ptr);
}

int main() {
    int *x;
    printValue(x);
    return 0;
}

ここでは、printValue関数にポインタxを渡していますが、このポインタは初期化されていません。

したがって、*ptrが何を指しているのかは不明であり、このコードを実行すると予期しない結果が発生する可能性があります。

これらの問題は初心者がよく遭遇する問題であり、C言語でのプログラミングにおいて常に意識するべき問題です。

次のセクションでは、これらの問題に対処するための対策を見ていきます。

●参照渡しの対処法

参照渡しにおける問題に対処するためには、エラーチェックと例外処理が必要となります。

ここでは、それらを実装する方法について説明します。

○サンプルコード7:エラーチェックを行う関数

まず、下記のコードは、引数として与えられたポインタがNULLでないことを確認するエラーチェックを行う関数の例です。

#include <stdio.h>

void changeValue(int *ptr) {
    if (ptr == NULL) {
        printf("Error: NULL pointer received\n");
        return;
    }
    *ptr = 100;
}

int main() {
    int *x = NULL;
    changeValue(x);
    return 0;
}

changeValue関数内で、最初にポインタがNULLであるかどうかを確認しています。

もしNULLであれば、エラーメッセージを表示し、関数の実行をすぐに終了します。

これにより、セグメンテーション違反を防ぐことができます。

○サンプルコード8:例外処理を行う関数

次に、未初期化の変数の使用に対する例外処理を見てみましょう。

下記のコードは、未初期化のポインタを安全に扱う方法を表しています。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *x = malloc(sizeof(int));
    if (x == NULL) {
        printf("Error: Failed to allocate memory\n");
        return 1;
    }
    *x = 10;
    printf("%d\n", *x);
    free(x);
    return 0;
}

このコードでは、メモリの動的確保を行っています。

malloc関数は新しく確保したメモリのアドレスを返すか、もしメモリの確保に失敗した場合はNULLを返します。

したがって、この返り値をチェックすることで、未初期化のポインタを安全に扱うことができます。

これらの対処法を理解し、自身のコードに適用することで、参照渡しを用いたプログラミングがより安全になります。

参照渡しは非常に便利なツールですが、適切な知識と理解がなければ、問題を引き起こす可能性もあります。

●参照渡しのカスタマイズ

参照渡しの基本的な使い方、注意点、対処法を理解したうえで、更にその能力を引き出す方法を学びましょう。

それは参照渡しをカスタマイズすることです。

これにより、自分だけの特別な関数を作り出すことができます。

○サンプルコード9:カスタム参照渡し関数の作り方

下記のコードでは、値のスワップを行う関数を自作しています。

このコードでは、2つの整数型変数の値を入れ替えるswap関数を作成しています。

#include<stdio.h>

void swap(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

int main() {
  int x = 5;
  int y = 10;
  printf("交換前のx: %d, y: %d\n", x, y);
  swap(&x, &y);
  printf("交換後のx: %d, y: %d\n", x, y);
  return 0;
}

このコードでは、swap関数を作成し、その引数に2つの整数のポインタを取ります。

関数内部では一時的にaの値を保存し、aにbの値を代入、次にbに保存しておいた*aの元の値を代入します。

この方法で、2つの変数の値を交換することができます。メイン関数内で変数xとyの値を交換前後で表示させています。

実行すると、「交換前のx: 5, y: 10」と「交換後のx: 10, y: 5」と表示され、値が交換されていることが確認できます。

参照渡しの力を活用することで、このように自分だけの関数を作ることが可能となります。

一度理解すれば、自分だけの特別なコードを書くことが可能となるのです。

○サンプルコード10:参照渡しを活用した高度な関数

次のコードでは、参照渡しを使って配列内の最大値とそのインデックスを一度に取得する関数を作ります。

このような複数の結果を同時に得られる関数は、参照渡しの特性を活かしています。

#include<stdio.h>

void findMax(int *arr, int size, int *maxValue, int *maxIndex) {
  *maxValue = arr[0];
  *maxIndex = 0;
  for(int i=1; i<size; i++) {
    if(arr[i] > *maxValue) {
      *maxValue = arr[i];
      *maxIndex = i;
    }
  }
}

int main() {
  int arr[5] = {1, 3, 7, 6, 2};
  int maxValue;
  int maxIndex;
  findMax(arr, 5, &maxValue, &maxIndex);
  printf("最大値: %d, インデックス: %d\n", maxValue, maxIndex);
  return 0;
}

このコードでは、findMaxという関数を作成しています。

この関数は、配列とそのサイズ、最大値を格納する変数のアドレス、最大値のインデックスを格納する変数のアドレスを引数にとります。

関数内部では、最大値とそのインデックスを探し、結果を引数に渡されたアドレスに保存します。

メイン関数内では、配列と最大値、インデックスを格納する変数を定義し、findMax関数を呼び出しています。

実行すると、「最大値: 7, インデックス: 2」と表示され、配列内の最大値とそのインデックスが正しく得られていることが確認できます。

このように、参照渡しを活用すれば、一つの関数で複数の結果を得ることも可能となります。

これは値渡しでは実現できない強力な特性です。

まとめ

以上、C言語における参照渡しの理解と活用について解説しました。

初心者から中級者まで、参照渡しを理解し、活用することで、より効率的で高度なコーディングが可能となります。

特に、参照渡しを活用した関数の作成やカスタマイズにより、自分だけの特別な関数を作ることが可能となります。

この知識を活かして、ぜひ自分だけのコードを書いてみてください。