第11回 ポインタ,アドレスと配列

 この章では,いくつかのプログラム例を使ってポインタとアドレスそれに配列の関係を明らかにしてきます.プログラムを少しずつ変更しながら,ほとんど同等に使用できるポインタと配列の関係を説明していきますから,プログラムをコンパイル・実行しながらついてきて下さい.

[プログラム例11-1]


01: #include <stdio.h>
02: void main(void){
03:     char str[10] = "abcd";
04:     puts( str );
05:     str[0] = 'x';
06:     str[1] = 'y';
07:     puts( str );
08:     getchar();
09: }

[結果11-1]

コンパイル・実行すると次のよう表示されます.
abcd
xybc
リターンキーを押すとプログラムは終了します.
 このプログラム例は,配列の復習で例題8-3とほとんど同じです.5,6行目で配列に直接アクセス(代入)して配列を変更しています.出力結果をみればわかるように0番目の要素の文字がa→xに,1番目の要素の文字がb→yに変更されています.次に同じことをポインタを利用して実行します.

[プログラム例11-2]


01: #include <stdio.h>
02: void main(void){
03:  char *ps, str[10] = "abcd";
04:   ps = &str[0]; /* この行は ps = str; と変更できます. */
05:   puts( str );
06:  *ps = 'x';
07:   ps++;
08:   *ps = 'y';
09:   puts( str );
10:  getchar();
11: }

[結果11-2]

コンパイル・実行結果は結果11-1と同じです.

 2行目は,配列strの0番目の要素のアドレスをポインタpsに代入しています
 6行目は,10章でやったように,ポインタpsの指す値に文字'x'を代入します.ポインタpsはいま配列strの0番目の要素を指していますから,文字'x'は配列strの0番目の要素に代入されます.
 7行目は,ポインタpsをインクリメント(1を加える)します.インクリメントによりポインタpsは配列strの0番目の要素を指していたものが,1つ増えて1番目の要素をさすようになります.
 8行目は,ポインタspの指す値に文字'y'を代入します.ポインタspは配列strの1番目の要素を指していますから,文字'y'は配列strの1番目の要素に代入されます.

 4行目の文ps = &str[0];は,文ps = str;と変更できます.それは,配列名str自体が0番目の(先頭の)要素のアドレスだからです.したがって配列名もポインタと考えてよいわけです.しかし,配列名にはstr=&c;のようにポインタの値を代入することはできません.配列名strは0番目の(先頭の)アドレスを持つポインタ定数だと考えれば,わかりやすいでしょう.

 配列にポインタを使ってアクセスしましたが,結果は予定通り結果はプログラム例11-1と同じものになりました.

[プログラム例11-3]


01: #include <stdio.h>
02:     void main(void){
03:     char *ps, str[10] = "abcd";
04:     ps = str;
05:     puts( str );
06:     *(ps+0) = 'x'; /* この行は *ps = 'x'; と変更できます. */
07:     *(ps+1) = 'y';
08:     puts( str );
09:     getchar();
10: }

[結果11-3]

コンパイル・実行すると次のよう表示されます.
abcd
xxcd
 リターンキーを押すとプログラムは終了します.

 プログラム例11-2からの変更は4,6,7行です.
 04行目の変更はすでに説明しました.
 06,07行目は直接,*(ps+1),*(ps+2)としてポインタを使っています.(プログラム例11-2では,ポインタpsをインクリメントしながら使っていました.)コーディングは違いますが,結果は同じです.ポインタはこのように直接,整数と同じように加減算ができます.ポインタps+1が指す値に文字'x'を代入すること.つまり配列strの0番目の次の要素=1番目の要素に文字'x'が代入されます.

[プログラム例11-4]


01: #include <stdio.h>
02: void main(void){
03:     char *ps, str[10] = "abcd";
04:     ps = &str[2];
05:     puts( str );
06:     *(ps+0) = 'x'; /* この行は*ps = 'x'; と変更できます. */
07:     *(ps+1) = 'y';
08:     puts( str );
09:     getchar();
10: }

[結果11-4]

コンパイル・実行すると次のよう表示されます.
abcd
abxy
 リターンキーを押すとプログラムは終了します.

 プログラム例11-3からの変更は4行目だけです.
 4行目は,プログラム例11-2では文ps=&str[0];として配列の先頭のアドレスをポインタpsに与えていましたが,ここでは&str[2]として配列の2番目の要素のアドレスを与えています.したがって6,7行目での代入文では,配列strの2,3番目の要素にそれぞれ文字'x','y'が代入されることになります.
 予想通りの結果を確認して下さい.

[プログラム例11-5]


01: #include <stdio.h>
02: void main(void){
03:     char str[10] = "abcd";
04:     puts( str );
05:     *(str+0) = 'x'; /* この行は *str = 'x'; と変更できます. */
06:     *(str+1) = 'y';
07:     puts( str );
08:     getchar();
09: }

[結果11-5]

コンパイル・実行すると次のよう表示されます.
abcd
xycd
 リターンキーを押すとプログラムは終了します.

 こんとは直接,配列名をポインタのように扱ってみます.
 プログラム例11-1と比べてみて下さい.結果はご覧のように全く同じです.コーディングの変更は5,6行目だけです.文str[1]='x';と*(str+1)='x';は全く同じなのです.
 説明しますと,str[1]は「配列strの1番目の要素の値」です.*(str+1)は「ポインタ定数strに1を加えたポインタが指す値」となります.ここでstr[1]と*(str+1)のアドレスを考えると,str[1]が配列strの1番目の要素のアドレス,*(str+1)がポインタ定数strに1を加えたアドレスとなります.もともと,配列名strと,ポインタ定数strは同じアドレスを持ちますから,str[1]と*(str+1)は同じアドレスと言うことになます.つまり,文str[1]='x';と*(str+1)='x';は全く同じなのです.

[文法11]

配列strとポインタpsがあり
(例)char str[整数], *ps;
のように型が等しいとき(charに限らず,どんな型でも)

①p=str;
(1)配列名はポインタ定数ですので,ポインタにそのまま代入できます.
(2)そのとき代入されたポインタは配列の先頭を指します.

②p=&str[整数];
(1)配列の要素のアドレスをポインタに代入できます.
(2)そのときポインタは配列の整数番目の要素を指します.

③*p *(p+n) n:整数
(1)ポインタの指す値を使いたいときはポインタ名の左にポインタ演算子*を付けます.
(2)このとき*pや*(p+n)は,宣言された型の変数として振る舞います.

④p++ p--
(1)ポインタの自体の値を変更します.
(2)実際に加減算される値は,ポインタが指す型で違ってきますが,++のとき次の,--のとき前のデータを指すようにコンパイラが自動的に調整します.
(3)もちろん変数nを整数として,p=p+n,p+=nやp=p-n,p-=nも使えますがそのときはn個後またはn個前のデータを指すようにポインタを変更します.
(4)③④をコーディングするときは,ポインタが宣言した配列の範囲をはずれないよう特に注意しください.例えばchar s[10];と宣言しているのに,
ポインタ操作でs[-1]を使ったり,s[10](添字の範囲は[0]~[9]の10個)を使ったりすると予期しない実行結果が現れます.別な方法でメモリを確保したときも同じです.



このドキュメントは http://icrus.org/c_language_beginers_course/ 上にあります.

2017,1 ssatoh@ 足立工科大学 工学部 情報通信工学科