カテゴリー
C

C(Step4-3)

ポインタの利用

まず、ポインタは、変数のアドレスを記憶する変数を指します。アドレスは、メモリ上の住所みたいなもので、「0014DF7C」などの番号を与えらています。今まで色々な変数を宣言してきましたが、その変数にはアドレスが割り当てられていたということです。また、アドレスにアクセスすることができ、そのアドレスの変数の値を取得することも可能です。アドレスは基本的に16進数で表されます。

ポインタの使い方

続いて、ポインタの使い方について説明していきます。ポインタはポインタ型変数として以下のように宣言します。

   型名 *変数名;
例: int *pt;

ポインタ変数は、変数名の前に「*(アスタリスク)」を付けて宣言します。加えて、変数のアドレスは変数名の前に「&(アンパサンド)」を付けて表します。以下のサンプルを実行して確かめてみましょう。

#include <stdio.h>
 
int main(void) {
    int num = 5; // int型普通の変数
    int *ptr_num; // int型ポインタ変数
    ptr_num = &num; // ポインタ変数ptr_numに変数numのアドレスを代入
    
    printf("int型変数numの値:%d\n", num);
    printf("int型ポインタ変数ptr_num:%p\n", ptr_num);
 
    return 0;
}
sample4-2.1実行結果

このプログラムでは、int型のポインタ変数「ptr_num」を宣言しています。ポインタ変数「ptr_num」にint型の変数「num」のアドレス「&num」を代入しています。ちなみに同じ型でなければ、エラーとなります。ポインタ変数をprintf()で出力表示するには「%p」の変換子を利用し、アドレスを表示しています。「%p」 はアドレスを16進数で表示する変換子です。

ここで、上記のプログラムに以下の文を追加して実行してみましょう。

printf("int型ポインタ変数p_numの参照先の値:%d\n", *ptr_num);
sample4-2.2実行結果

実行結果を見て分かる通り、printf()で、「*(アスタリスク)」を付けることでポインタ変数の参照先の値を表示することができます。また、「*(&num)」と記載しても同様の結果を得ることができます。

ポインタと配列

C言語では、配列はメモリの連続した区間を使うという約束になっています。そのため、ポインタとは深く関係しています。次のプログラムを使って配列の要素をポインタで扱ってみましょう。

#include <stdio.h>
 
int main()
{
    int a[5] = {10, 20, 30, 40, 50}; //配列
    int *p; //ポインタ変数
 
    p = &a[0]; //配列の先頭のアドレスを代入する
 
    /*配列の中身表示*/
    printf("繰り返し処理による配列内の値表示:");
    for(int i=0; i<5; i++){
        printf("%d ", a[i]);
    }
    printf("\n");

    /*ポインタを使った配列の中身表示*/
    printf("ポインタを使った配列内の値表示:");
    for(int i=0; i<5; i++){
        printf("%d ", *p);
        p++;
    }

    return(0);
}
sample4-2.3実行結果

ポインタ変数のpに注目しましょう。プログラム内では始めに配列の1番目のアドレスを代入しています。その後、ポインタ変数p++することで、アドレスを位置を順番に進めています。このアドレスの位置は配列の順番と同じことがわかると思います。つまり、ポインタの位置と配列の順番は連動しているとわかります。

ポインタのポインタ

前の説明で、ポインタとは変数のアドレスを記憶する変数と説明しました。しかし、ポインタ自体もある種の変数です。そのため、ポインタ変数のアドレスもメモリの何処かにあり、アドレス番号が与えられているということです。ポインタ自体にポインタのアドレスを格納することも出来ます。これをポインタのポインタと言います。宣言するには以下のように、さらに「*(アスタリスク)」を付けます。

型名 **変数名;

次のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
int main() {
    int num = 5; //int型変数
    int *p_num; //int型ポインタ変数
    int **pp_num; //int型ポインタ変数のポインタ
    
    p_num = &num; //ポインタ変数p_numに変数numアドレスを代入
    pp_num = &p_num; //ポインタ変数のポインタpp_numにポインタ変数p_numのアドレスを代入
    
    printf("int型変数numの値:%d\n", num);
    printf("int型ポインタ変数p_num:%p\n", p_num);
    printf("int型ポインタ変数のポインタpp_num:%p\n", pp_num);
    
    return 0;
}
sample4-2.4実行結果

プログラム内では、int型の変数numのアドレスを格納するポインタ変数p_numをまず宣言、定義しています。さらに、このポインタ変数p_numのアドレスを格納するために、ポインタ変数のポインタpp_numを次に宣言、定義しています。実行結果を見て分かる通り、ポインタに対してもポインタを扱うことができます。

関数ポインタの使い方

続いて、関数ポインタの使い方について説明します。関数ポインタと変数のポインタとの違いは関数が格納されたメモリアドレスを取得することです。関数を呼び出す時に関数ポインタの内容を別の関数のアドレスに変更することで、呼び出す関数を変更することができます。それによって、呼び出す関数を動的に変更することができます

利用するには、「typedef」を用いて、関数ポインタの型を宣言すると簡潔に記述できます。

typedef 戻り値の型 (*関数ポインタ型名)(引数);

そして、この関数ポインタ型名のオブジェクトを生成して使用します。

関数ポインタ型名 オブジェクト名;

以下のサンプルを実行して確認してみましょう。

#include <stdio.h>
 
// 関数ポインタの宣言
typedef int (*foAO)(int x, int y);
 
/*関数ポインタに代入する関数を定義
、関数ポインタの引数の型と引数の数を統一*/

//足し算を行う関数
int sum(int x, int y){
    return x + y;
}

//引き算を行う関数
int mns(int x, int y){
    return x - y;
}
 //掛け算を行う関数
int mul(int x, int y){
    return x * y;
}

//割り算を行う関数
int div(int x, int y){
    return x / y;
}
 
int main() {
    int x, y, result;
    foAO c; //foAO型関数ポインタのオブジェクトを生成
    
    x = 10;
    y = 2;
    printf("x:%d, y:%d\n", x, y);
    
    c = &sum; //関数ポインタに関数アドレスを代入
    result = c(x, y); //関数ポインタより関数の呼び出し
    printf("x + y = %d\n", result);
    
    c = &mns;
    result = c(x, y);
    printf("x - y = %d\n", result);

    c = &mul;
    result = c(x, y);
    printf("x * y = %d\n", result);

    c = &div;
    result = c(x, y);
    printf("x / y = %d\n", result);
 
    return 0;
}
sample4-2.5実行結果

プログラムでは、四則演算を行う関数を作成し、各関数のアドレスを渡すことで、目的の関数呼び出しています。このようにして、ポインタを用いることで関数であっても有効に利用することができます。