実は、配列名はその先頭要素のアドレスである。 よって次のプログラムは同じ答えを出す。
#include <stdio.h>
int main() {
int a[10];
printf("%p.\n", &a[0]);
printf("%p.\n", a);
}
配列の要素 a[i] という書き方はすでに学んだ。 実は、a[i] は *(a+i) である。 ここで a+i は、a が 100 番地のとき 100+i 番地、ではない。 もしも a[ ] が int 型の配列であり、 一つの int 型が 4 バイトだとしたら、100+4*i 番地、となる。 この「4*」はコンパイラが自動でやってくれるので、考えなくてよい。 よって、*(a+i) は配列 a[ ] の i 番目の要素 a[i] を意味する。
これで、次のプログラムの三つ目の for までは理解できるであろう。
#include <stdio.h>
int a[10];
int main() {
int i, *p; /* int 型変数 i と int 型へのポインタ p とをひとまとめに宣言できる */
for (i = 0; i < 10; i++) { /* a[i] を i の二乗で埋める */
a[i] = i * i;
}
for (i = 0; i < 10; i++) { /* a[i] を出力。前にもやった */
printf("%d ", a[i]);
}
putchar('\n');
for (i = 0; i < 10; i++) { /* 別の書き方でもう一度 */
printf("%d ", *(a+i));
}
putchar('\n');
for (p = a; p < a + 10; p++) { /* また別の方法でもう一度 */
printf("%d ", *p);
}
putchar('\n');
}
四つ目の for ループでは、 ポインタ p に a[0] のアドレスを入れ、 その p の値を 1 ずつ増やしている。 このときも、アドレスは 1 番地ずつ増えるのではなく、 int 型は 4 バイトなら、4 番地ずつ増えてゆく。
ここで、不思議に思った人がいるかもしれない。 四つ目の for では、p は a + 10 まで増えて、 その時点で継続条件を満たさなくなってループを抜ける。 a[10] は存在しないから、p は“ないもの”を指す。 しかし、これは許されるという規則である。 K&R2 の 5.4, A7.7 参照。 ただし、*p を読み書きしようとしてはならない。
なお、反対側の、a の一つ手前は許されない。 逆順に出力するつもりで、次のように書くのは間違いである。
for (p = a + 9; p >= a; p--) { /* これは間違い! */
printf("%d ", *p);
}
C 言語には、文字列型はない。 文字列は、char 型の配列として扱う。 たとえば char a[128]; と宣言しておいて、 そこに "hello" を収めるとは、 a[0] に 'h' を、 a[1] に 'e' を、 a[2] に 'l' を、 a[3] に 'l' を、 a[4] に 'o' を入れ、終わりの印として a[5] に '\0' を入れることである。 最後に置く '\0' は、どの文字とも異なる数値である。 その手前までを文字列と呼ぶか、 '\0' も含めて文字列と呼ぶかは、 時と場合によると思う。
プログラム例は次の節であげる。
#include <stdio.h>
int main() {
char a[6];
char b[100] = "hello";
char c[ ] = "hello";
a[0] = 'h';
a[1] = 'e';
a[2] = 'l';
a[3] = 'l';
a[4] = 'o';
a[5] = '\0';
printf("puts() で三行、出力します.\n");
puts(a);
puts(b);
puts(c);
printf("print() で「%s」と出力します.\n", a);
printf("fputs() で出力します.\n");
fputs(a, stdout);
putchar('\n');
printf("これでこのプログラムは終わりです.\n");
}
まずは、最初の、文字列を宣言するところを説明する。 char a[6]; では、6 個の要素からなる char 型を用意する。 char b[100] = "hello"; では、100 個の配列を用意し、 初めのほうから "hello" を入れる。 つまり、前の節で示したようになる。 b[6] 以降は不明である。 char c[ ] = "hello"; では、配列の大きさはコンパイラにまかせて、 そこに "hello" を入れる。配列の大きさは自動的に 5 + 1 = 6 となる。
a[ ] には文字列がはいっていないから、6 行を使って、代入している。
a, b, c の三つとも、 格納されている文字列は同じ "hello" である。
文字列の出力には、三つの関数が使える。
まずは puts()。かっこの中には、文字列の先頭アドレスを書く。 この関数は、指定された文字列を出力したあと、改行も出力する。
次に、前から使っていた printf()。 文字列を出力する際は %s と書く。自動的に改行はしない。
三つ目は、fputs() である。これは二つの引数をとる。 初めのは文字列の指定。あとのは、いまは stdout と書くと覚えよう。 これは標準出力のことで、リダイレクトやパイプを用いていなければ画面のことである。 この関数も、自動的に改行はしない。
puts() 以外は、自分で改行するように書かないと改行しない。
#include <stdio.h>
#define MAXLINE 128
int main() {
char line[MAXLINE];
while (gets(line) != NULL) { /*【お勧めしません!】*/
puts(line);
}
}
puts() と対になる gets() で文字列の入力ができる。 この関数は、キーボードから一行に読み込み、行末の改行は文字列に含めず、 その代わり、そこに '\0' を置く。 入力が終わった場合、またその場合のみ、NULL を返す。 よって、上のプログラムは §6.1 のと同じように動く。 (NULL についてはあとで述べる。)
しかし、この関数は現在では勧められない。 理由は、ユーザーが入力する文字数に制限を設けられないため、 作者の予期せぬところを、入力された文字が“破壊”するかも知れないからである。
次のプログラムは、配列のサイズを 4 にしたもの。 手元の gcc でコンパイルしたところでは、 配列 line[ ] の直後に n がくるため、 ユーザーが 4 文字以上の長い文字列を入力すると変数 n が書き換わってしまう。
#include <stdio.h>
#define MAXLINE 4
int main() {
int n;
char line[MAXLINE];
printf("%p\n", &line[MAXLINE]);
printf("%p\n", &n);
n = 123;
while (gets(line) != NULL) { /*【お勧めしません!】*/
puts(line);
}
printf("n の値は %d です.\n", n);
}
特に危険なのは、将来、コンピュータの管理者になって、 一般ユーザーに使わせる、管理者権限で動くプログラムを書くときである。
関数 fgets() は三つの引数をとる。 一つめは入力された文字列の格納先、 二つめは格納先のサイズ('\0' の分も含む)、 三つめはいまのところは stdin である。 これは標準入力を意味する。 ただし、行末の '\n' までを文字列とみて配列に格納する。
fputs() は自動的に改行はしないから、 次のプログラムは §6.1 のと同じように動く。
また、一行の長さが MAXLINE 以上でも正しく動く。 その実験の際には、MAXLINE の値を小さくして実験すると楽である。 fput() の行の前に printf("###"); を挿入してみると、 どうして正しく動くのかわかるであろう。
#include <stdio.h>
#define MAXLINE 128
int main() {
char line[MAXLINE];
while (fgets(line, MAXLINE, stdin) != NULL) {
fputs(line, stdout);
}
}
ただし、'\n' が常に文字列の最後についてしまうのは扱いにくい。 よって、危険は理解したうえで、gets() を使ってゆこう。
次のプログラムは、入力行の最大の長さと、 その最大を与える行(の一つ)を出力する。 ただし、入力は空でないと仮定している。 入力リダイレクトを使えば、 ファイルの中で最大の長さをもつ行を知ることができる。
#include <stdio.h>
#include <string.h>
#define MAXLINE 128
int main() {
int len, maxlen;
char line[MAXLINE];
char longest[MAXLINE];
maxlen = 0;
while (gets(line) != NULL) { /*【お勧めしません!】*/
if ((len = strlen(line)) > maxlen) {
maxlen = len;
strcpy(longest, line);
}
}
printf("一番長い行は %d 文字あり、", maxlen);
printf("その行(の一つ)は「%s」です.\n", longest);
}
関数 strlen() は文字列を引数にとり、その文字列の長さを返す。 ただし、終端を意味する '\0' は数に含めない。 関数 strcpy() は文字列二つを引数にとり、第二のを第一に、 終端を意味する '\0' も込めてコピーする。 これらの関数を使うには string.h の #include が必要である。 また、strcpy() では第一の配列がコピー可能なだけの長さをもつと仮定している。 さらに、二つの配列の共通部分が空でない場合の動作は保証されない。
練習:strlen(), strcpy() は元々ある関数だが、 同じように動く関数を自作することが可能である。 mystrlen(), mystrcpy() という名前で書いてみよ。 main() は上のプログラムを利用すればよいだろう。
関数 strcmp() は二つの文字列を引数にとり、 辞書式順序でどちらが先かを返す。 負なら一つ目が先、0 なら等しい、正なら二つ目が先、である。 この関数を使うにも string.h の #include が必要である。
#include <stdio.h>
#include <string.h>
#define MAXLINE 128
int main() {
char line1[MAXLINE];
char line2[MAXLINE];
int r;
while (gets(line1) != NULL) { /*【お勧めしません!】*/
gets(line2); /*【お勧めしません!】*/
r = strcmp(line1, line2);
if (r < 0) {
printf("一つ目のほうが前.\n");
} else if (r == 0) {
printf("両者は等しい.\n");
} else {
printf("二つ目のほうが前.\n");
}
}
}
strcmp() は元々ある関数だが、 同じように動く関数を自作することが可能である。 mystrcmp() という名前で書いてみよ。 返り値は int 型とし、 二つの文字列を最初から比べていって '\0' まで等しければ 0 を返し、 そうでなければ、異なっている最初の文字の差を返せばよい。 次にプログラム例をあげるが、これでは等しいときに 0 を返すとは限らない。 直してみよ。
int mystrcmp(char *p, char *q) {
int i;
for (i = 0; p[i] == q[i]; i++) {
;
}
return p[i] - q[i];
}
main() は §9.8 のプログラムを利用すればよい。
レポートの本文冒頭に、氏名を、なるべく大学に届けるある通りの文字で書け。 次に、プログラムソースファイルを、添付ファイルではなく、本文に貼りつけよ。 それから、このプログラムを使った実験結果を貼れ。
件名は「??? kadai2」(←全て半角文字、小文字、 ??? は自分の履修者番号、その後ろに半角スペース一つ、 kadai と 2 の間にはスペースを入れない)とせよ。
いままで、変数や配列を使うときは必ず「宣言」をした。 そうではなく、プログラムが動き出してから、必要なメモリを取得することができる。。
次のプログラムは、入力行をすべて保持するものである。
char text[10000][10000]; などとして二重配列を宣言し、 そこに格納する方法もあるが、 常識的には一行は長くても 200 文字ぐらいなので、 これでは効率がよくない。
そのため、別のやり方を考える。 char *p[LINES]; として、 LINES 個の char へのポインタを宣言している。 こう書くとポインタの配列となる、という約束である。
関数 malloc() の引数は必要なメモリのサイズ、 返り値は割り当てられたメモリの先頭アドレスである。 請求するメモリのサイズは入力行の長さ + 1 とした。 この + 1 は、文字列の終わりの印 '\0' のためである。 そしてそこに入力行をコピーする。 関数 malloc() を使うには stdlib.h の #include が必要である。
関数 malloc() はメモリがとれない場合は NULL を返す。 NULL は、有効なメモリのアドレスにはならないアドレスである。 だから、厳密には、関数 malloc() を呼んだあとには、 帰ってきた値が NULL でないかのチェックが必要である。 (ほかに、入力行が長すぎた場合、入力行が多すぎる場合のことも考えねばならない。)
また、 「終わりの印」とコメントをつけた箇所では、 NULL は p[n] に代入することで、 その手前で入力が終わっていることを示すために使っている。
「割り当てられたメモリの先頭アドレスを出力」とコメントをつけたところでは、 割り当てられたメモリの先頭アドレスを出力している。 ここの gcc では、十六進で末尾が 0, すなわち、16 バイト単位で割り当てるようである。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLINE 1024
#define LINES 1024
int main() {
int len, n;
char line[MAXLINE];
char *p[LINES];
for (n = 0; gets(line) != NULL; n++) { /*【お勧めしません!】*/
len = strlen(line);
p[n] = malloc(len + 1); /* +1 は '\0' のため */
strcpy(p[n], line);
}
p[n] = NULL; /* 終わりの印 */
for (n = 0; p[n] != NULL; n++) { /* 割り当てられたメモリの先頭アドレスを出力 */
printf("%p.\n", p[n]);
}
for (n = 0; p[n] != NULL; n++) { /* 保持された行を出力 */
puts(p[n]);
}
}
「沢」の旧字体であるところの「澤」の文字コードが知りたくなったとする。
この文字だけを収めたテキストファイル x.txt を用意する。 このファイルは 2 バイトである。 別に、たとえば半角スペース 2 つだけからなるテキストファイル y.txt を用意する。
そして「fc /b x.txt y.txt」を実行する。 fc はファイル比較 (file compare) のプログラムであるが、 /b をつけるとバイナリファイルと見て比較する。よって
Z:\>fc /b x.txt y.txt ファイル x.txt と Y.TXT を比較しています 00000000: E0 20 00000001: 56 20
が画面に出て、「澤」のコードは E0, 56 と知れた。 その右の 20, 20 は半角スペース 2 つのコードである。