ポインタ

ブロックとスコープ

if文ブロック、for文ブロックなど、{}で囲まれた範囲をブロックと呼ぶ。
ブロック内で宣言された変数は、そのブロック内でのみ有効である。変数の使用可能な範囲を変数のスコープと呼ぶ。スコープを超えた変数は破棄される。

#include <stdio.h>

int main()
{
    int hoge = 14;
    {
        int fuga = 42;
        printf("hoge = %d\n", hoge);
        printf("fuga = %d\n", fuga);
    }
    printf("hoge = %d\n", hoge);
    //printf("fuga = %d\n", fuga); //エラー!

    return 0;
}

グローバル変数

では、main関数のスコープよりも外で変数を宣言するとどうなるのだろうか?

#include <stdio.h>

int hoge = 14;

void change(int num)
{
    hoge = num;
}

int main()
{
    printf("hoge = %d\n", hoge);
    change(65535);
    printf("hoge = %d\n", hoge);
    return 0;
}

ご覧のように、関数の外側で宣言した変数はプログラム全体でスコープを持つ。
このような変数をグローバル変数と呼ぶ。
グローバル変数はどこからでも読み書きるため非常に強力であるが、同時にバグの温床にもなりやすいため、取り扱いには注意が必要である。
ちなみに、関数の内側で宣言された変数(ローカル変数と呼ぶ)と名前が衝突した場合は、ローカル変数が優先される。

アドレスとポインタ

次に君は、「じゃあ、スコープを飛び越えることができて、グローバル変数よりも安全な機能はないのか?」という。
もちろん存在するし、既に君はそれを知っている。思い出してほしい、scanf関数は渡した変数を書き換えていた!

宣言した変数は、メモリ上のどこかに存在する。存在する場所を変数のアドレスと呼ぶ。変数名の前に&をつけることで、その変数のアドレスを示すことができる。
また、アドレスを格納するための型をポインタと呼ぶ。ポインタが何を指しているかによって、int型へのポインタ、char型へのポインタ……などと分けられる。
ポインタの前に*をつけることで、そのポインタが指す値を示すことができる。
以下に、関数の中で値を入れ替えるサンプルを示す。記号で混乱しないように。

#include <stdio.h>

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

int main()
{
    int foo = 3;
    int bar = 5;
    printf("foo = %d, bar = %d\n", foo, bar);
    swap(&foo, &bar);
    printf("foo = %d, bar = %d\n", foo, bar);

    return 0;
}

配列とポインタ

関数に配列名を渡すと、その関数の中で配列の値を書き換えることができた。
つまり、配列名は先頭要素へのポインタ=&配列[0]である!

ポインタを加減算すると、型のサイズ分だけアドレスをずらすことができる。
これを利用すると、こんなコードが書ける。

#include <stdio.h>

void printarr(const int* const arr, int size)
{
    for (int i = 0; i < size; ++i)
    {
        printf("%d\n", *(arr + i));
    }
}

void printstr(const char* str)
{
    while (*str != 0)
    {
        printf("%c", *str++);
    }
    printf("\n");
}

int main()
{
    int arr[5] = { 1, 1, 2, 3, 5 };
    char str[15] = "This is a pen.";
    printarr(arr, 5);
    printstr(str);

    return 0;
}

構造体とポインタ

構造体のポインタも宣言することができる。
その場合、(*ポインタ).メンバでもアクセスできるが、ポインタ->メンバという記法がある(アロー演算子と呼ぶ)ので、そちらを使うと良い。

#include <stdio.h>

typedef struct
{
    int x, y;
}wrap;

int main()
{
    wrap hoge = { 22, 33 };
    wrap* p = hoge;
    (*p).x *= 2;
    p->y *= 3;
    printf("%d, %d\n", p->x, p->y);

    return 0;
}

参照渡し

この章は読み飛ばしても構わない。

関数へ引数を渡すとき、実体の書き換えが不可能な値渡しと、書き換えが可能なポインタ渡しがあった。 C++の領域になるが、より便利な機能として参照渡しというものがある。 これを使うと、値渡しのように書いて、実体を書き換えることができる。 上の方のスワップ関数と比較してもらいたい。

#include <stdio.h>

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

int main()
{
    int foo = 3;
    int bar = 5;
    printf("foo = %d, bar = %d\n", foo, bar);
    swap(foo, bar);
    printf("foo = %d, bar = %d\n", foo, bar);

    return 0;
}

課題

  • 127文字以内の文字列を引数にとり、母音だけを表示する関数を作成せよ。
  • 以下のenemy構造体を初期化する関数を作成せよ。
    ただし、x、yは-100以上100以下の乱数、isaliveは1とする。
typedef struct
{
    int x, y;
    int isalive;
}enemy;
  • 適当なenemyの配列を初期化し、xとy両方が0以上のもの以外のisaliveを0にする関数を作成せよ。
    また、isaliveが1のもののみを表示する関数を作成せよ。