実は、配列名はその先頭要素のアドレスである。 よって次のプログラムは同じ答えを出す。
#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 つのコードである。