C言語 PR

【C言語】ポインタの使い方を解説

記事内に商品プロモーションを含む場合があります

この記事では、C言語のポインタについて解説します。

ポインタは、コンピュータのメモリにアクセスするための機能です。ポインタからアドレス先の値にアクセスすることができます。

それでは、ポインタの使い方を見ていきましょう!

ポインタとアドレスとは?

ポインタとアドレスを一言で説明すると以下のようになります。

ポインタ

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

アドレス

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

メモリ

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

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

与えられたアドレスにアクセスすることで、そのアドレスが指し示すメモリの値を変更したり、取得したりすることができます。


▲ int型の変数を宣言した時のイメージ

ポインタの基本的な使い方

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

ポインタ変数の宣言

ポインタ変数は、*演算子を使って宣言します。

型 *変数名;

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

型* 変数名;

アドレスを取得する

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

&変数;

サンプル

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

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

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

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

実行結果

値: 1
アドレス: 0x7ffee27e27f8

ポインタ変数にアクセスする

ポインタにアクセスするには、以下の2つの方法があります。

アドレスにアクセスする

通常の変数と同じようにアクセスすることでポインタ変数が保持するアドレスにアクセスすることができます。

#include 
 
int main(void) {
    int num1 = 1;  // int型変数
    int *p;        // int型ポインタ変数
    p = &num1;     // pポインタ変数にnum1変数のアドレスを代入
    
    printf("num1のアドレス:%p\n", p);

    int num2 = 2;
    p = &num2;  // pにnum2のアドレスを代入

    printf("num2のアドレス:%p\n", p);
 
    return 0;
}

実行結果

num1のアドレス:0x7ffee62696a8
num2のアドレス:0x7ffee626969c

アドレス先の値にアクセスする

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

#include 
 
int main(void) {
    int num = 1;  // int型変数
    int *p;       // int型ポインタ変数
    p = &num;     // ポインタ変数pに変数numのアドレスを代入
        
    printf("numのアドレス:%p\n", p);
    printf("参照先の値:%d\n", *p);

    *p = 2;  // 参照先に2を代入

    printf("numのアドレス:%p\n", p);
    printf("参照先の値:%d\n", *p);
 
    return 0;
}

実行結果

numのアドレス:0x7ffeedaf47d8
参照先の値:1
numのアドレス:0x7ffeedaf47d8
参照先の値:2

ポインタとconst

ポインタ変数の宣言時にconstキーワードを配置することができます。constキーワードを配置する位置によって意味が異なります。

アドレス先のデータを読み取り専用にする

const 型名* 変数名

ポインタを読み取り専用にする

型名* const 変数名

アドレス先のデータもポインタも読み取り専用にする

const 型名* const 変数名
【C言語】constを使って変数やポインタ変数を読み取り専用にする方法この記事では、C言語のconstの使い方を解説します。cosntを使うことで変数を書き換え不能にし、読み取り専用にすることができます。また、ポインタ変数に使うことでアドレスを書き換え不能にしたり、アドレス先の値を書き換え不能にできます。それでは、constの使い方を「変数」と「ポインタ変数」に分けて確認していきましょう!...

ポインタのポインタ

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

このポインタのことを「ポインタのポインタ」といいます。

ポインタのポインタを宣言

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

型名 **変数名;

サンプル

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

#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足されると、その型のサイズ分だけアドレスに足されます。つまり、

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

となります。

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

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

#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

配列のポインタ

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

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

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

ポインタ = 配列;

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

ポインタ = &配列[インデックス];

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

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言語のポインタについて解説しました。

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

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

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