では前回に引き続き、文字列を操作する方法を学びましょう。

・char型の変数を大量に宣言し、1文字づつ代入していく方法 が最も柔軟なのでしたね。

しかしこれはソースコードが見づらくなる上、とても書きにくい手法でした。

ところでC言語は配列をサポートしています。
配列とは、同じ型の変数を大量に宣言する方法です。

char String[100];

と宣言すると、char型のStringという領域がメモリ上に100箇所用意されます。
そこに1文字づつ置いていけばよいのではないでしょうか。

73


各領域へアクセスする際は宣言の時に使用した[]を使います。[100]と宣言した場合、[0]~[99]までの100箇所の領域が確保されます。0から始まるのでご注意下さい。

補足:string[5]に代入している\0はNULL文字といって、ここがこの文字列の末尾であることを表します。printf関数がprintf("%s", String);で文字列を表示する際には、このNULL文字を見つけるまで文字列をシークしているのです。
また、NULL文字は整数で表すと0です。ポインタへ文字列定数を格納する時には自動的に作成されていたので、気に留める必要はなかったのですが、配列へ文字を1つずつ格納する際には自動生成はされないので、手動で付けてやる必要があります。


少し楽に書けましたが、それでもまだ原始的です。

実は配列も初期化することができます。

配列を初期化する場合、[0]から順次値が代入されていきます。
この特性を応用します。


74


配列を初期化する場合は{};を使います。}の末尾にセミコロンが付いているのをお忘れなく。

しかしこれもまだ使いにくいです。なんというか、単にStringへ文字を1つずつ代入したいだけなのに、,で区切ったり'で区切るのは機械的でつまらないですね。
ちなみにこのやり方だとNULL文字を明示的に代入する必要はないです。

ということでC言語には配列を初期化するシンタックスシュガーが用意されています。シンタックスシュガーとは、より簡単に仕事を行う方法という意味です。

char String[100] = "hello";
実はこのように宣言できます。こうすると左の文字から順次[0]~へ格納されていきます。

最初から教えて! と思うかもしれませんが、恐ろしいのはこの"hello"は文字列定数ではないのです。文字列なのです。

文字列定数:文字全体を定数として扱う、つまり文字全体が1文字になっているような状態。参照しかできない。
char* String = "hello";

文字列:文字が集まって列になったもの。各文字ずつ参照したり、指定した文字だけ変更することも可能。もちろん参照も可能。
char[100] = "hello";

この違いを理解して貰う為に、今まで回りくどい説明を重ねてきたのです。
文字列を初期化する際には、通常の方法とシンタックスシュガーの方法どちらを使ってもNULL文字は勝手に格納されます。

75


もちろん文字列定数の時と違い、1文字だけを変更することも可能です。

76


ところで、printfでは文字列を%sで表示できるのでしたよね?

ということは……このように書くこともできますね。

77


どうですか? 大分すっきりしてきましたね。理論を1つずつ積み重ねて正解に近づくと、感動を覚えますね。この感動を忘れないで下さい。

しかしまだ完全ではありません。なぜなら、helloはNULL文字を含めても6文字しかないからです。
なのに[100]と宣言するのはもったいないですよね。使わない領域が94箇所もあるのですから。

char String[6] = "hello";

これで正解だと思った方は鋭いです。しかしここにもまたシンタックスシュガーがあるのです。
実は……
char String[] = "hello";
こう書くことができるのです。
こう書くとコンパイラが適切な数の領域を確保してくれます。
文字の数を自分で数えて値を書くと、間違える可能性が高いので、文字列を扱う時はこのようにして下さい。これなら無駄な領域もないです。

どうです? 美しさすら覚えてきたでしょう。

問23:画面上にhelloと表示するプログラムを書きなさい。但し、printf関数へ直接文字列を渡してはいけません。(参照して表示して下さい)


問24:上のプログラムを書き換えて、helloのoを0へ変更して表示するプログラムを書きなさい。



文字列定数は使えないのかと問われますと、そんなことはありません。
変更ができないというのは、欠点にも思えますが、利点でもあるのです。
文字列定数の文字を変更しようとすると、C++ではコンパイルエラーになりますから、(Cでは実行時エラー)変更することがない文字列に使えば、誤って変更するプログラムを書いたとしてもエラーにしてくれます。これはデバッグを助けてくれます。逆に、変更する可能性がある時は文字列を使えばよいのです。道具は使いようです。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ユーザーから入力を受け付けて、その文字を文字配列へ格納するプログラムを作成してみましょう。

82


この場合は、残念ながら文字列の要素を多めに確保しておく他ないです。

……おや、ところでscanfへは格納先のアドレスを渡すのでしたよね。
ということはscanf("%s", &String); と書くのが正しい気がします。

しかし、よく考えてみて下さい。そもそも格納先のアドレスとはどこでしょう?
Stringはchar型の変数の集合体です。&Stringと指定した時、大量にあるchar型の配列の一体どこを指定すればよいのでしょうか。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
正解は、String[0]です。
入力された文字をString[0]から順次代入していくのが無駄のないプログラムです。
つまり、scanf("%s", &String[0]); と書くのが正しいのです。
scanfには変数のアドレスを指定する。

が、ここでもシンタックスシュガーがあります。
実は文字列や文字列定数の名前はその名前そのものが先頭文字のアドレスを表すのです。

つまり、Stringと&String[0]は全く同じことを意味しているのです。
どちらも先頭文字のアドレスを表します。

なのでscanf("%s", String); と書くことができるのです。

補足:シンタックスシュガーはなぜ存在するか? これは単にその言語のルールだからという言葉で説明できます。理屈ではscanf("%s", &String[0]); と書くのが正しいことは勿論理解できますし、このように書いても良いでしょう。しかしなんというか、あまり見栄えがよくないというか。要はユーザーからの入力を受け付けたいだけなのだから scanf("%s", String); と書くほうが人間(ジョブ)目線では直感的だと思うのです。


問25:次の実行結果となるプログラムを書きなさい。
83