ポインタと配列(ネタ)
- 配列はポインタに書き換えられる
- int a[100];のときに、a[i]=j;とかくと、Cコンパイラは内部で次のようなコードを生成する。
*(a+i)=j;
- これが意味するところは、aの配列は結局はメモリのどこかに100個ぶん割り当ててあるのであって、その先頭アドレスにiを加えると、a[i]の格納アドレスになる。だから、メモリのその場所にjの値を書き込む、ということである。
- ということで、配列を使うということは、実は間接的にポインタを使っていることになるんですよー、というわけだ。
- と、こんなことはたいていの教科書に書いてあるので、面白くも何ともない。面白くなるのはこれからだ(笑)。
- ポインタは配列に書き換えられる
- a[i]という表現が*(a+i)に書き換え可能なら、それはつまり*(a+i)はa[i]と書き換えてもいいわけだ。
- これをさらにおしすすめて、*(0+i)という表記を0[i]に書き換えてしまおうではないか。0+i==iなので、つまりこれは、ポインタをすべて配列にしてしまおうということである。
- いけにえの例:
void my_strcpy(char *s, char *t)
{
while (*s++ = *t++);
return;
}
- 変換後:
#define CHAR0 ((char *) 0)
void my_strcpy(char *s, char *t)
{
int is = s - CHAR0, it = t - CHAR0; /* ポインタを添え字に変換 */
while (CHAR0[is++] = CHAR0[it++]);
return;
}
- 呼び出し元が最初から文字列をintで渡してくれるのなら、
#define CHAR0 ((char *) 0)
void my_strcpy(int s, int t)
{
while (CHAR0[s++] = CHAR0[t++]);
return;
}
- いけにえと最後のやつを比べてほしい。#defineを除けば基本的にとてもよく似ている。
- ちなみにここでsやtとは何ですかといわれたら、メモリの番地です、とも答えられる。
- もっと高級な例:たとえば2分木
- 変換前:
struct BINTREE {
struct BINTREE *left, *right;
int index, data;
};
struct BINTREE *get_newnode(void)
{
static struct BINTREE node_pool[10000], *p = node_pool;
return p++;
}
struct BINTREE *make_top(int index, int data)
{
struct BINTREE *top = get_newnode();
top->left = top->right = NULL;
top->index = index;
top->data = data;
return top;
}
struct BINTREE *search_data(struct BINTREE *top, int index)
/* 目的のindexが見つからない場合は、手前で止まる */
/* 見つかったかどうかは、返ってきたポインタのindexと、引数で渡したindexを比較すれば分かる */
{
struct BINTREE *next;
for (;;) {
if (index == top->index)
break; /* 見つかった */
if (index < top->index)
next = top->left;
else
next = top->right;
if (next == NULL)
break; /* 見つからなかったので手前を報告 */
top = next;
}
return top;
}
void add_data(struct BINTREE *top, int index, int data)
{
struct BINTREE *node;
top = search_data(top, index, data);
if (index == top->index) {
/* 既に存在するindexであれば、データを上書き */
top->data = data;
} else {
node = make_top(index, data);
if (index < top->index)
top->left = node;
else
top->right = node;
}
return;
}
- 変換後:
struct BINTREE {
int left, right, index, data;
};
#define BT0 ((struct BINTREE *) 0)
int get_newnode(void)
{
static struct BINTREE node_pool[10000];
static int i = node_pool - BT0;
return i++;
}
int make_top(int index, int data)
{
int top = get_newnode();
BT0[top].left = BT0[top].right = -1; /* 添え字-1はNULLの代りに使う */
BT0[top].index = index;
BT0[top].data = data;
return top;
}
int search_data(int top, int index)
/* 目的のindexが見つからない場合は、手前で止まる */
/* 見つかったかどうかは、返ってきたポインタのindexと、引数で渡したindexを比較すれば分かる */
{
int next;
for (;;) {
if (index == BT0[top].index)
break; /* 見つかった */
if (index < BT0[top].index)
next = BT0[top].left;
else
next = BT0[top].right;
if (next == -1)
break; /* 見つからなかったので手前を報告 */
top = next;
}
return top;
}
void add_data(int top, int index, int data)
{
int node;
top = search_data(top, index, data);
if (index == BT0[top].index) {
/* 既に存在するindexであれば、データを上書き */
BT0[top].data = data;
} else {
node = make_top(index, data);
if (index < BT0[top].index)
BT0[top].left = node;
else
BT0[top].right = node;
}
return;
}
- 強引にまとめ:
- とまあ、なんというか、つまり配列を全部ポインタにできるということと同様に、ポインタは全て配列にもできるといいたいわけだ。
- 世間にはなぜかポインタバンザイ派がいて、その人はポインタでないとできないことがあるんだといわんばかりの勢いでポインタを薦めるわけだが、この例でも分かるように、配列でも同じことはできる。何でもポインタにするべきだというのは、なんでも配列にするべきだというのと、同じくらいに笑っちゃうと僕は思う。
- ただし、ポインタで書いておくほうが、コンパイルしたときに良いバイナリになる、というのは正しい。それはアクセス時にスケーリングをしなくていいからである。
- 一方、世間にはポインタなんてわかんねーよ、という人もこれまたたくさんいる。たしかに最初はわからんかもしれない。しかし、「CHAR0[s++] = CHAR0[t++];」なら分かると思う。 a[s++] = a[t++]; と同じである。そしてそれを *s++ = *t++; というふうに書いたら、4文字すくないではないか。いろいろややこしい話をまとめてしまえば、結局はそれだけのことなのだ(ここでの例をくらべてごらんよ!)。 BT0[top].right = node; と書くよりも、 top->right = node; と書くほうが4文字すくないではないか。それだけのことなのだ。そして好きなほうを使えばいいのだ。どちらが正しいということはない。
- 註:
- ここでは関数ポインタのことは考えてないので、それに関する突込みはしないでね。
- 実はアラインの問題もあったりする。まあそれも本質ではないということで、大目にみてください。
こめんと欄
|