●はじめに
こんにちは。
今回は、C言語で皆さんが最もつまずくとされているポインタで配列変数と組み合わせたプログラムの説明をしていきたいと思います。
私もポインタは概念が難しく理解するのに時間がかかりましたがわかりやすく解説していきます。
「え、そもそもC言語とか初めてなんだけど。。。」
「プログラミングの基礎がわからない。」
という方向けの記事ではないので以下のサイトの基本編を学んでからこの記事を読んでください。
→一週間で身につくC言語の基本
「C言語の関数くらいまでならできるよー」
って方でポインタにつまずいている方は是非、読んでください。
また、前回はポインタ変数と配列変数の関係性を説明したので読んでいない方はこちらをご覧ください。(今回も配列変数を扱いますが微妙にプログラムが異なり、前回が基礎です)
→【C言語】難解なポインタに挑む「忍法写し身の術!」その2
●ポインタ変数としての配列変数
前回までの記事をご覧いただけた方はポインタと配列変数は相性が良いということがわかったと思います。実は配列変数は、ポインタの特殊な形だと考えることができるのです。
試しに以下のプログラムを実行してみてください。
#include <stdio.h>
int main(int argc, char** argv) {
// 配列とポインタ変数を用意する
double d[3] = { 0.2, 0.4, 0.6 };
double* p1 = NULL, * p2 = NULL;
int i;
p1 = d; // p1にdのアドレスを入力
p2 = d; // p2にdのアドレスを入力
for (i = 0; i < 3; i++) {
printf("%f %f %f\n", *(d + i), p1[i], *p2);
p2++; // p2のアドレスをインクリメント
}
return 0;
}
実行すると以下のような結果になると思います。
0.200000 0.200000 0.200000
0.400000 0.400000 0.400000
0.600000 0.600000 0.600000
このサンプルでは、double型の配列変数dと、2つのポインタ型変数p1、p2を用意し、それらのデータの中身を表示しています。すると、実行結果からこれらは全く同じ値を持つものであることがわかります。dがp1,p2に忍法写し身の術!を発動しているようですが、今回は配列の要素のアドレスではなく配列変数自体をp1,p2に代入しているようです。ではいったい、なぜそのような結果が得られるのでしょう。
まず注目してほしいのは、ポインタへのアドレスの代入です。
p1 = d;
p2 = d;
これは、配列変数dの先頭アドレス、つまり&d[0]の値を代入するのと同じ処理なのです。つまり、このことからわかるとおり、配列変数は添字がなければ、ポインタ変数と同じように扱うことができるのです。
そのため、値も、d[0]は、*d、d[1]は*(d+1)……といったように、ポインタ変数のように扱うことができます。つまり、配列変数というのはポインタ変数の特殊な形と考えることができるのです。
・配列dの配列としての側面とポインタとしての側面
配列変数 | 配列変数のアドレス | ポインタによる表現 | ポインタによる値 |
---|---|---|---|
d[0] | &d[0] | d | *d |
d[1] | &d[1] | d+1 | *(d+1) |
d[2] | &d[2] | d+2 | *(d+2) |
ただ、配列変数はポインタ変数とは違って、他の変数のアドレスを取得することはできないので注意が必要です。例えば、以下のような処理はできません。
double d[3] = { 0.2, 0.4, 0.6 };
double e[3];
e = d; // ←このような処理はできない。
以上のように、配列変数をポインタ変数のように扱うことができましたが、逆にポインタ変数を配列変数のように扱うことができます。「p1=d」と定義していますが、これによりp1は配列変数dと等価のものとして扱うことが可能です。
・ポインタ変数p1の配列としての側面とポインタとしての側面(「配列変数のアドレス」と「ポインタによる表現」がポインタで「配列変数」と「ポインタによる値」が値です。)
配列変数 | 配列変数のアドレス | ポインタによる表現 | ポインタによる値 |
---|---|---|---|
p1[0] | &p1[0] | p1 | *p1 |
p1[1] | &p1[1] | p1+1 | *(p1+1) |
p1[2] | &p1[2] | p1+2 | *(p1+2) |
この表を見ると、ポインタ変数p1と配列変数dは、「p1=d」という処理を施すことにより、ほぼ同等のものとして扱うことができていることがわかります。
前に、ポインタ変数はアドレスを指定することにより、別の変数に写し身できるということを説明したかと思いますが、配列変数の場合でも同じことが言えます。
以上のことから、*(d+i)とp1[i]は、ともにd[i]と同じ値が得られるわけです。
このように、ポインタ変数と配列変数はほぼ同等に扱うことができますが、ポインタ変数であればこそできるような処理もあります。
ポインタ変数p2は、インクリメントによってアドレスを変えています。最初に「p2=d」とすることで、p2は&d[0]と同じ値をとることになりますが、「p2++;」とすると、&d[1]と同じ値、さらにもう一度「p2++;」とすると、&d[2]と同じ値……という具合になります。
逆に「p2--;」といった処理を施すと、配列の番号に遡っていくこともできます。このような処理は配列変数では行うことができず、ポインタ変数のみでできる処理です。配列変数を繰り返し処理するときに大いに役立つのです。
以上のことから、「*p2」は「p2++;」という処理によって、値はd[0]、d[1]、d[2]を指すものに変化しているのです。
●scanf関数とポインタ関数
scanf関数はキーボードから入力した値を変数に代入する関数ですが、数値を入力するときと文字列を入力するときに書式が違うことを不思議に思った人も多いのではないでしょうか。実は、その謎の答えがこの配列変数とポインタ変数の関係性の中にあるのです。
scanf("%d", &n);
scanf("%s", s);
もうすでにおわかりの通り、文字列はchar型の配列変数であることから、sは、char型の配列の先頭アドレスになるわけです。すなわち、「s」は「&s[0]」と同じになるわけです。
このことから、表記の仕方は違うものの、scanf関数の第2引数に変数のアドレスを入れるということは一貫していることがわかります。
ここまでの内容は、少し面倒でわかりにくく思われるかもしれませんが、実はこの部分がわかると、ポインタの大部分を理解できたといっても過言ではありません。
次回は、文字列とポインタを扱いたいと思いますがサンプルプログラムを載せておきます。新しい関数が出てくるのでそちらは次回説明しますが、ポインタに関わる部分は今回学んだ内容でわかるはずなので目を通しておいてください。
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
char s[8];
int len;
// 文字列のコピー
strcpy(s, "ABC");
printf("s=%s\n", s);
// 文字列の結合
strcat(s, "DEF");
printf("s=%s\n", s);
// 文字列の長さ
len = strlen(s);
printf("文字列の長さ:%d\n", len);
return 0;
}
s=ABC
s=ABCDEF
文字列の長さ:6
余裕があればご自身でstring.hライブラリやstrcpy, strcat, strlenについて調べてもらい、このプログラムがどんな仕組みなのか考えてみてください。
次回答え合わせでお会いしましょう。
【投稿者】
エンジニアファーストの会社 株式会社CRE-CO 田渕浩之
【参考文献】
→1週間でC言語の基礎が学べる本