【C言語】ポインタとはなんぞや?【ポインタの使い方】

C言語

この記事では、C言語のポインタについて解説します。ポインタはコンピュータのメモリにアクセスするための機能です。

この記事で学べること
  • アドレスとは
  • ポインタとは
  • ポインタの使い方(配列・関数・構造体)

ポインタとアドレスとは

ポインタ

アドレスを格納するための変数のことをいいます。

アドレス

メモリ内の場所を表す番号(16進数)のことをいいます。

メモリ

一時的にデータを保管するための領域(ハードウェア)です。

C言語では変数を宣言すると、その宣言した型のサイズだけメモリを確保します。そして、確保したメモリ上の位置(アドレス)が変数に与えられます。

アドレスを取得する

変数のアドレスを取得するには&演算子を使います。

#include <stdio.h>
 
int main(void) {

    int num = 1;  
    
    printf("値: %d\n", num);
    printf("アドレス: %p\n", &num);
}

実行結果

値: 1
アドレス: 0x7ffee04cfb0c

ポインタの使い方

ポインタ変数を使用するには通常の変数と同様に宣言が必要です。

宣言

ポインタ変数は*演算子を使って以下のように宣言します。

型 *変数名;

*演算子を型にくっつけても問題ありません。

型* 変数名;

サンプル

以下のコードではアドレスをポインタに格納して出力しています。

#include <stdio.h>
 
int main(void) {

    int num = 1;  // int型の変数

    int *p;    // int型のポインタ変数
    p = &num;   // アドレスを代入
    
    printf("値: %d\n", num);
    printf("ポインタ: %p\n", p);
}

実行結果

値: 1
ポインタ: 0x7ffee6d4cb0c

アドレス先の値を取得・変更

アドレスの前に*演算子を記述することでアドレスの中身にアクセスすることができます。

アドレス先の値を取得

以下のコードではアドレス先の値を出力しています。

#include <stdio.h>
 
int main(void) {

    int num = 1; 

    // ポインタ宣言
    int *p;    

    // アドレス代入
    p = &num;
    
    printf("値: %d\n", num);
    printf("アドレスの中身を取得: %d\n", *(&num));     // アドレスから中身にアクセス
    printf("ポインタから参照先の中身を取得: %d\n", *p);  // ポインタ(アドレス)から中身にアクセス
}

実行結果

値: 1
アドレスの中身を取得: 1
ポインタから参照先の中身を取得: 1

アドレス先の値を変更

以下のコードではポインタを用いて参照先の値を変更しています。

#include stdio.h
 
int main(void) {

    int num = 1; 

    // ポインタ宣言
    int *p;    

    // アドレス代入
    p = &num;

    // 参照先の値の書き換え
    *p = 100;

    printf("numの値: %d\n", num);
    printf("ポインタから参照先の中身を取得: %d\n", *p); 
}

実行結果

numの値: 100
ポインタから参照先の中身を取得: 100

ポインタpの参照先の値を変更したので、参照先である変数numの値も書き換わりました。

ポインタのポインタ

ポインタ変数にも通常の変数と同様にアドレスが与えられます。つまり、ポインタのアドレスをポインタに格納することができます。これをポインタのポインタといいます。

宣言

ポインタのポインタを宣言するには*(アスタリスク)を増やして以下のように記述します。

型名 **変数名;

サンプル

以下のコードではポインタのポインタを使って値を出力しています。

#include <stdio.h>

int main(void) {

    int num = 1;

    // ポインタ宣言
    int *p;
    // アドレス代入
    p = &num;

    // ポインタのポインタ宣言
    int **pp;
    // ポインタのアドレス代入
    pp = &p;

    printf("num: %d\n", num);
    printf("ポインタ: %p\n", p);
    printf("ポインタのポインタ: %p\n", pp);

    printf("*pp: %p\n", *pp);
    printf("**pp: %d\n", **pp);
}

実行結果

num: 1
ポインタ: 0x7ffeed566b0c
ポインタのポインタ: 0x7ffeed566b00
*pp: 0x7ffeed566b0c
**pp: 1

ポインタ変数のポインタppから参照先にアクセスする際、

  • *pp = ポインタpの値(numのアドレス)にアクセス
  • **pp = numの値にアクセス

*の数に注意しないと予想外の値にアクセスする可能性があるので気をつけましょう!

ポインタの演算

ポインタは演算することができます。その際の挙動を見てみましょう!

足し算・引き算

以下のコードでは配列の真ん中の要素のアドレスを受け取り、ポインタに±1したアドレスと中身を出力しています。

#include <stdio.h>
 
int main(void) {

    char str[] = "abc";
    int nums[] = {1, 2, 3};

    // ポインタ
    char *p_char;
    int *p_int;

    // アドレス代入
    p_char = &str[1];
    p_int = &nums[1];

    printf("[char] ポインタ: %p, アドレス先の値: %c\n", p_char, *p_char);
    printf("[char] ポインタ+1: %p, アドレス先の値: %c\n", p_char + 1, *(p_char + 1));
    printf("[char] ポインタ-1: %p, アドレス先の値: %c\n", p_char - 1, *(p_char - 1));

    printf("[int] ポインタ: %p, アドレス先の値: %d\n", p_int, *p_int);
    printf("[int] ポインタ+1: %p, アドレス先の値: %d\n", p_int + 1, *(p_int + 1));
    printf("[int] ポインタ-1: %p, アドレス先の値: %d\n", p_int - 1, *(p_int - 1));
}

実行結果

[char] ポインタ: 0x7ffee995baf9, アドレス先の値: b
[char] ポインタ+1: 0x7ffee995bafa, アドレス先の値: c
[char] ポインタ-1: 0x7ffee995baf8, アドレス先の値: a
[int] ポインタ: 0x7ffee995bb00, アドレス先の値: 2
[int] ポインタ+1: 0x7ffee995bb04, アドレス先の値: 3
[int] ポインタ-1: 0x7ffee995bafc, アドレス先の値: 1

ポインタに1足されるとその型のサイズ分だけアドレスに足されます。つまり、

ポインタ ± 整数 = アドレス ± 整数 * 型のサイズ

となります。

ちなみに、char型のサイズは1バイトで、int型のサイズは4バイトです。

インクリメント・デクリメント

ポインタはインクリメント・デクリメントもできます。足し算・引き算と変わりはないが代入演算であるところに気をつけましょう!

#include <stdio.h>
 
int main(void) {

    char str[] = "abc";
    int nums[] = {1, 2, 3};

    // ポインタ
    char *p_char;
    int *p_int;

    // アドレス代入
    p_char = str;
    p_int = nums;

    printf("[char] ポインタ: %p, アドレス先の値: %c\n", p_char, *p_char);
    ++p_char;
    printf("[char] インクリメント: %p, アドレス先の値: %c\n", p_char, *p_char);
    --p_char;
    printf("[char] デクリメント: %p, アドレス先の値: %c\n", p_char, *p_char);

    printf("[int] ポインタ: %p, アドレス先の値: %d\n", p_int, *p_int);
    ++p_int;
    printf("[int] インクリメント: %p, アドレス先の値: %d\n", p_int, *p_int);
    --p_int;
    printf("[int] デクリメント: %p, アドレス先の値: %d\n", p_int, *p_int);
}

実行結果

[char] ポインタ: 0x7ffee689eaf8, アドレス先の値: a
[char] インクリメント: 0x7ffee689eaf9, アドレス先の値: b
[char] デクリメント: 0x7ffee689eaf8, アドレス先の値: a
[int] ポインタ: 0x7ffee689eafc, アドレス先の値: 1
[int] インクリメント: 0x7ffee689eb00, アドレス先の値: 2
[int] デクリメント: 0x7ffee689eafc, アドレス先の値: 1

配列のポインタ

ポインタを使って配列の要素を取得することができます。

配列のアドレスをポインタに代入

そのまま配列をポインタ変数に代入することで先頭の要素のアドレスを代入できます。

ポインタ = 配列;

途中の要素のアドレスを代入したい場合は以下のように&演算子を使います。

ポインタ = &配列[1];

ポインタを使った要素の取得

sizeof演算子を使って配列の要素数を計算しました。そして、for文を使ってiの数だけアドレスを移動しながら値を出力しています。

#include <stdio.h>
 
int main(void) {

    int nums[] = {1, 2, 3};
    int length;

    // ポインタ
    int *p;

    // アドレス代入
    p = nums;

    // サイズから要素数の計算
    length = sizeof(nums) / sizeof(nums[0]);

    for(int i = 0; i < length; ++i){
        printf("num: %d\n", *(p+i));
    }
}

実行結果

num: 1
num: 2
num: 3

関数のポインタ

関数でもポインタを使うことができます。

引数で使う(アドレス参照)

引数にポインタを指定することで、参照渡しでオブジェクトを引き渡すことができます。参照渡しでオブジェクトを引き渡すことで、関数内の処理でその引数の値を変更することができます。

#include <stdio.h>

// 関数のプロトタイプ宣言
void func(int a, int *b);

int main(void) {

    int a = 0, b = 0;

    // 関数呼び出し時にアドレス(参照)を渡す
    func(a, &b);
    printf("a: %d, b: %d\n", a, b);
}

// 関数の定義
void func(int a, int *b){
    a = 1;
    *b = 2;
}

実行結果

a: 0, b: 2

関数ポインタ

関数ポインタとはその名の通り関数のポインタです。関数も定義した際にメモリに領域が確保され、アドレスが与えられます。

関数ポインタを使うことで動的に呼び出す関数を変更することができます。

宣言

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

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

サンプル

以下のコードは関数ポインタを使って関数を呼び出しています。

#include <stdio.h>

// 関数のプロトタイプ宣言
void func(int num);

int main(void) {

    // 関数ポインタ宣言
    void (*f)(int);

    // 関数ポインタに関数アドレス代入
    f = func;
    printf("アドレス: %p\n", f);

    // 関数ポインタを使った関数の呼び出し
    (*f)(1);
}

// 関数の定義
void func(int num){
    printf("num: %d\n", num);
}

実行結果

アドレス: 0x106c91f50
num: 1

typedef句を使う

typedef句を使うことでもう少し簡潔に関数ポインタを記述することができます。

#include <stdio.h>

//  関数ポインタの宣言
typedef void (*func_pointer)(int);

// 関数のプロトタイプ宣言
void func(int num);

int main(void) {

    // func_pointer型の関数ポインタ宣言
    func_pointer fp;

    // 関数ポインタに関数アドレス代入
    fp = func;
    printf("アドレス: %p\n", fp);

    // 関数ポインタを使った呼び出し
    (*fp)(1);
}

// 関数の定義
void func(int num){
    printf("num: %d\n", num);
}

実行結果

アドレス: 0x10f022f50
num: 1

構造体のポインタ

構造体型のポインタを生成することもできます。宣言方法などは通常のポインタと変わりません。

メンバへのアクセス

構造体の実体のポインタからメンバを呼び出す場合はアロー演算子(->)を使います。

構造体ポインタ->メンバ名

サンプル

試しに構造体の実体のポインタを使って値を初期化・出力してみます。

#include <stdio.h>

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


int main(void) {

    person mike;

    // 構造体のポインタ宣言
    person *p;

    // 構造体のアドレス代入
    p = &mike;

    // 初期化
    p->name = "mike";
    p->age = 25;
    
    printf("name: %s\n", p->name);
    printf("age: %d\n", p->age);

}

実行結果

name: mike
age: 25

まとめ

今回の記事ではC言語のポインタについて解説しました。

今回のおさらい

  • ポインタとは = 『アドレスを格納するための変数』
  • アドレスとは = 『メモリ内の場所を表す番号』
  • ポインタ宣言 = 『型 *変数名;』
  • アドレス取得 = 『&変数名;』
  • アドレスから値にアクセス = 『*アドレス』 

なんだか難しく見えたかもしれませんが、ポインタはアドレスを格納して、アドレスの先が示す値にアクセスしているだけです。

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

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