ポインタ
ブロックとスコープ
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のもののみを表示する関数を作成せよ。