再びポインタと配列
さて、再びポインタについて、取り上げてみました。
「ポインタでつまづいた人のための・・」という本があるくらいですから、ポインタがCの大きな特徴のひとつで
あることは間違いありません。
BASICにはない概念ですから、BASICでプログラムしていた人には、つまずきやすい点なのかも知れません。
簡単に言ってしまえば、ポインタは変数のアドレスを現しているので、BASICであえて記述すれば
varptr(変数)
ということになりますが、Cのポインタは、なんというかかめばかむほどに味のでるもっと味わい
深いものです。、
画面に出力(printf)・キーボードから入力(scanf)・変数 変数の有効範囲、ポインタ、関数のデータ受け渡し
のところでも、簡単にポインタについて触れてますので、お暇でしたら参照して下さい。
1 ポインタの記述の仕方。
何度もかいてますが、ポインタは変数の格納されているアドレスを現します。
普通に int a; のように変数を宣言した場合、ポインタは &a
のように 変数の前に&をつけて現します。
&a はaの格納されているアドレスです。
また、 int *b; のように * をつけて変数をポインタとして宣言することができます。この場合は、
bがポインタ、 *b が変数の値を現します。すなわち、*はそのポインタに格納されている値を現します。
そして、配列の場合、ポインタはもっと便利な道具になります。例えば
int c[3]; のように配列の宣言をした場合、 &c[0]は c[0]のポインタになります。
そして面白いことに、ただcと記述すると、cは配列cの先頭アドレスをさすことになっているので、
&c[0] = c
ということになります。また、&c[1]= c+1 ということになります。
逆に、 c[0] = *c, c[1]= *(c+1) です。
例えば、 int c[4] = {0,1,2,3}; と宣言すると、以下のようになります。
変数 | C[0] *C |
C[1] *(C+1) |
C[2] *(C+2) |
C[3] *(C+3) |
値 | 0 | 1 | 2 | 3 |
ポインタ | &c[0] または c |
&c[1] または c+1 |
&c[2] または c+2 |
&c[3] または c+3 |
それでは、じっさいのプログラムで確認してみます
/* ポインタ coded by Tsuyoshi Kasai for LSIC */ #include <stdio.h> main(){ int a,i; int *b; int c[3]={1,2,3}; a=1; b=&a; printf("a:値--> %d &a:ポインタ--> %04x\n",a,&a); printf("*b:値--> %d b:ポインタ--> %04x\n",*b,b); for (i=0;i<3;i++){ printf("c[%d]:値--> %d &c[%d]:ポインタ--> %04x\n",i,c[i],i,&c[i]);} printf("c:ポインタ--> %04x",c); |
変数aは通常の宣言です。
bはポインタとして宣言しました。 cは配列です。
b=&a としていますが、これで、変数bのポインタ=変数aのポインタ
となりました。すなわち、同じアドレス
をさしていることになります。
さて、実行結果です。
C:\C>pointa a:値--> 1 &a:ポインタ--> 0fc2 *b:値--> 1 b:ポインタ--> 0fc2 c[0]:値--> 1 &c[0]:ポインタ--> 0fc4 c[1]:値--> 2 &c[1]:ポインタ--> 0fc6 c[2]:値--> 3 &c[2]:ポインタ--> 0fc8 c:ポインタ--> 0fc4 C:\C> |
a = *b, &a = b, c = &c[0] ですね。
ということで、再度ポインタについて簡単にまとめてみました。
既にBASICにはない世界に踏み込んでいますが、ポインタの味が出るというレベルには程遠いですね。
まだ、ポインタを舐めてみたというところでしょうか。
2 ポインタの加算
上の例で、int c[3]; と配列を宣言した場合、
&c[0] = c, &c[1] = c+1 ・・・となると説明しました。
この時、ちょっと注意しなくてはならないのは、cのポインタに1を加えた時、アドレスはcの型(int型)の分だけ
進むということです。
int型が16bit(=2byte)なら、c+1はcより2byte分進んでいます。
実例を見たほうが分かりやすいと思いますので、以下のプログラムを実行して見てください。
/* ポインタ2 coded by Tsuyoshi Kasai for LSIC */ #include <stdio.h> main(){ char a[4]="abc"; int i,b[3]={1,2,3}; long c[3]={1,2,3}; for (i=0;i<3;i++){ printf("a+%d--> %04x b+%d--> %04x c+%d--> %04x\n",i,a+i,i,b+i,i,c+i);} } |
これは、LSICでコンパイルしました。従って、変数aはChar型(1byte),bはint型(2byte)、
cはlong型(4byte)となっています。
以下の実行例を見てください。それぞれのポインタの進みかたに注意して下さい。
C:\C>pointa2 a+0--> 0f76 b+0--> 0f70 c+0--> 0f64 a+1--> 0f77 b+1--> 0f72 c+1--> 0f68 a+2--> 0f78 b+2--> 0f74 c+2--> 0f6c |
これは、ポインタ=変数の格納されているアドレス とは言っても、Basicのvarptr(変数)と単純に同じ
ということではないということに、注意してください。ポインタの方が使いやすくできていることがわかると
思います。
3 2次元配列
2行*3列の配列
[0][0] | [0][1] | [0][2] |
[1][0] | [1][1] | [1][2] |
例えば上のような、2行*3列の配列は、以下のように宣言します。
int data[2][3] = { {0,1,2},
{3,4,5}};
この配列は、メモリーの連続した領域に次のような順番で作られます
[0][0] | [0][1] | [0][2] | [1][0] | [1][1] | [1][2] |
2次元配列で、ただ data と書くと data[0][0]
のアドレスをさします。
すなわち、 data = &data[0][0] です
また、 data[0] は data[0][0] のアドレスをさし、data[1]
は data[1][0] のアドレスをさします。
data[0]+1 は data[0][1] のアドレスです。
すなわち、data = data[0] = &data[0][0] data[1]
= &data[1][0] data[0]+1 = &data[0][1] です
それでは、*dataはなにものでしょうか、1次元配列から類推すると、
*data = data[0] ですが、
どうでしょうか。じつは、その通りです。従って、
data = *data = data[0] = &data[0][0] ということになります。
ここまでは、わりとわかりやすかったと思います。それでは、data+1 は何をさすでしょうか。
単純に、 data+1 = data[0]+1 = &data[0][1]
と考えてしまいがちですが、おおはずれです。ここらへんは、ポインタがかめばかむほど味がでる部分でも
あります。人によっては、ちょっと苦い味と感じるかもしれませんが。
実際は、 data+1 = data[1] =&data[1][0] です。
それでは、data+1, *(data+1), *data+1
はどうでしょう。全て同じ物をっさすのでしょうか。
少し、頭の中が混乱してきたかもしれませんが、あわてずに考えて見てください。
data+1 = data[1] でした。 一方、 data = *data = data[0]
ですから、 *data+1 は data[0]+1 と同じ
になります。違いがわかりましたか。
ちなみに、*(data+1) は data+1 と同じです。
結構、ごちゃごちゃしてきましたので、表にしてまとめてみましょう
2次元配列のアドレス(ポインタ)の記述の仕方です。
[0][0] &data[0][0] data[0] *data data |
[0][1] &data[0][1] data[0]+1 *data+1 |
[0][2] &data[0][2] data[0]+2 *data+2 |
[1][0] &data[1][0] data[1] *(data+1) *data+3 data+1 |
[1][1] &data[1][1] data[1]+1 *(data+1)+1 *data+4 |
[1][2] &data[1][2] data[1]+2 *(data+1)+2 *data+5 |
次は、2次元配列の値を記述する方法にいってみましょう。
data[0][0] = *data[0] ですね。
さて、上のポインタのところで、 data[0] = *data
という関係がありましたが、これから考えると
data[0][0] = *data[0] = **data となりそうです。これは、正しいでしょうか。
実は正解です。Cでは、こういう記述の仕方ができます。
では、data[0][1]
は他にどう記述できるでしょうか。
data[0][1] = *(data[0]+1) = *(*data+1) です。
また、data[1][0] = *data[1] = **(data+1) = *(*data+3) です。
もう、おわかりだと思いますが、**data+1,
*(*data+1), **(data+1) はみな異なる変数です。
**data+1 = data[0][0]+1 ということですから。
それでは、2次元配列の値についても、表を作ってみましょう。
[0][0] data[0][0] *data[0] **data |
[0][1] data[0][1] *(data[0]+1) *(*data+1) |
[0][2] data[0][2] *(data[0]+2) *(*data+2) |
[1][0] data[1][0] *data[1] **(data+1) *(*data+3) |
[1][1] data[1][1] *(data[1]+1) *(*data+4) |
[1][2] data[1][2] *(data[1]+2) *(*data+5) |
以下、実際にプログラムで実験してみました。実行結果と合わせて見て確認して見てください。
3行*3列の配列です。
/* nijigen0.c Coded by Tsuyoshi Kasai for LSIC */ /* 2次元配列の実験 */ #include <stdio.h> int data[3][3] = {{0,10,20}, {30,40,50}, {60,70,80}}; main(){ int i; for (i=0;i<6;i++) printf("*data+%d:%4d, data[0]+%d:%4d\n",i,*data+i,i,data[0]+i); printf("\n"); for (i=0;i<6;i++) printf("**data+%d:%2d, *(*data+%d):%2d, *(data[0]+%d):%2d \n" ,i,**data+i, i,*(*data+i), i,*(data[0]+i)); printf("\n"); for(i=0;i<3;i++) printf("data+%d:%4d, *(data+%d):%4d, data[%d]:%4d\n" ,i,data+i ,i,*(data+i), i,data[i]); printf("\n"); for (i=0;i<3;i++) printf("**(data+%d):%2d, *data[%d]:%2d \n" ,i,**(data+i), i,*data[i]); } |
C:\C>nijigen0 *data+0: 102, data[0]+0: 102 *data+1: 104, data[0]+1: 104 *data+2: 106, data[0]+2: 106 *data+3: 108, data[0]+3: 108 *data+4: 110, data[0]+4: 110 *data+5: 112, data[0]+5: 112 **data+0: 0, *(*data+0): 0, *(data[0]+0): 0 **data+1: 1, *(*data+1):10, *(data[0]+1):10 **data+2: 2, *(*data+2):20, *(data[0]+2):20 **data+3: 3, *(*data+3):30, *(data[0]+3):30 **data+4: 4, *(*data+4):40, *(data[0]+4):40 **data+5: 5, *(*data+5):50, *(data[0]+5):50 data+0: 102, *(data+0): 102, data[0]: 102 data+1: 108, *(data+1): 108, data[1]: 108 data+2: 114, *(data+2): 114, data[2]: 114 **(data+0): 0, *data[0]: 0 **(data+1):30, *data[1]:30 **(data+2):60, *data[2]:60 |