【C言語】ポインタ

ポインタって分かりにくいですよね。。。

改めて自分の理解のためにメモ書きしていきたいと思います。

今日のお話

環境

対象言語

  • c

メモ

  • int型、double型のように、ポインタ型がある。

  • ポインタ型にも ポインタ(int)型:int *ポインタ(double)型:double * がある。

→型として理解すればOKの認識。

  • ポインタ型は変数のアドレスを保持する

  • 1回作成した変数に対して、操作をしたい時に、アドレスに対して操作を行えばよいため、効率が良い。 →「正直使わなくても問題ないですよね?」と言われてしまうと、この段階では問題は発生しない。

#include <stdio.h>

void AddNumber(int *value)
{
    *value = *value + 100; // 直接対象のアドレスに対して値を加えている
}

int main()
{
    int a = 100;
    AddNumber(&a);
    printf("%d\n", a);
    return 0;
}
#include <stdio.h>

int AddNumber(int value) // 仮引数が生成される時に新たにvalue変数が生成されている
{
    value = value + 100;
    return value;
}

int main()
{
    int a = 100;
    a = AddNumber(a); // 戻ってきた値を改めてaに代入
    printf("%d\n", a);
    return 0;
}
  • ポインタが必要になるのは配列の場合で、生成した(or 引数で受け取った)値をアドレスを通じて戻さないといけない。
#include <stdio.h>
#include <stdlib.h>

int *AddNumber()
{
    int *values = (int *)malloc(sizeof(int) * 100);
    for (int i = 0; i < 100; i++)
    {
        values[i] = 100;
    }
    return values;
}

int main()
{
    // int a[100];
    int *a;
    a = AddNumber();

    for (int i = 0; i < 100; i++)
    {
        printf("No.%d: %d\n", i, a[i]);
    }
    return 0;
}

関数から配列(値)返すことができない。 →ローカル変数は関数の中でのみ有効になるため

↓はNG(出力するとおかしな値になる)

#include <stdio.h>

int *AddNumber()
{
    int values[100];
    for (int i = 0; i < 100; i++)
    {
        values[i] = 100;
    }
    return values; // returnした後にvaluesが解放されてしまう
}

int main()
{
    // int a[100];
    int *a;
    a = AddNumber();

    for (int i = 0; i < 100; i++)
    {
        printf("No.%d: %d\n", i, a[i]); // 正しい値が出力されない
    }
    return 0;
}
  • 関数の引数は値渡しになるため NULL 値を渡してローカル変数で設定しても 関数のスコープを抜けたときに解放されてしまう。 どうしてもやりたい場合は、ダブルポインターを使う。
#include <stdio.h>
#include <stdlib.h>

void AddNumber(int **value)
{
    *value = (int *)malloc(sizeof(int));
    **value = **value + 100;
}

int main()
{
    int *a = NULL;
    AddNumber(&a);
    printf("%d\n", *a);
    return 0;
}
  • 配列の指定方法はどちらで同じ
    • a[i] = 100;
    • *(pa + i) = 200;
int a[10];
int *pa = a;  // &a[0]と同じ

for (int i; i < 10; i++)
{
    a[i] = 100;
    *(pa + i) = 200;
    printf("No.%d: %d\n", i, *(pa + i));
}
  • 関数の引数で配列を使う場合、以下は全てパターン1と同じ意味になる。 →どれで書いても同じなので、パターン1を使った方が無難そう
int SampleFunc(int *a);  // パターン1
int SampleFunc(int a[]);  // パターン2
int SampleFunc(int a[5]);  // パターン3
  • 下記は全て同じ結果になる。
int a[10];
printf("&a    : %p\n", (void *)&a);
printf("&a[0] : %p\n", (void *)&a[0]);
printf("a     : %p\n", (void *)a);

printf("============\n");

for (int i = 0; i < 5; i++)
{
    printf("%d      : %p\n", i, (void *)&a[i]);
}

printf("============\n");

int *p = a;
for (int i = 0; i < 5; i++)
{
    printf("%d      : %p\n", i, (void *)&p[i]);
}

printf("============\n");

// この形式で追加しても良いが型がint[10]へのポインタになる
int(*pa)[10];
pa = &a;
for (int i = 0; i < 5; i++)
{
    // (int *)にキャストして、そこからポインタ演算でインクリメント
    printf("%d     : %p\n", i, (void *)(((int *)pa) + i));
}

戯言

  • ポインタもそうだけど、C言語に詳しくならないと。。。