パート4:文字列の加工
 今度は文字列を加工してみます。与えられた文字列をアルファベットを3つずらす手法で暗号化します。例えば
CALL

 なら暗号化後の文字列は
FDOO

 となります。
テン*シー*シー-1
 シーザー暗号というローマ時代からある暗号法です。

配列変数
 まず、暗号化した文字列を記憶するためのメモリ区画確保ですが、これには配列変数を使います。main関数の引数で説明した配列です。
 変数宣言時に、変数名の後ろに[](ブラケット)で囲んだ要素数を付ける事で、その要素数分の配列が確保されます。
テン*シー*シー-2
 次に、const char*型の変数targetを宣言して、"HELLO WORLD"の先頭番地を代入します。
char result[100];
const char* target = "HELLO WORLD";

 const char*型の変数と文字列リテラルを「=」で結ぶ事は、C文字列先頭番地の代入を意味します。
 余談ですが、文字列リテラルの他に、「’」(シングルクォーテーション)で囲んだ1文字は文字コード表の対応する数値を意味するというルールもあります。
char a = 65;  どちらもchar型変数aに文字Aを設定する事になるが、
char a = 'A'; 後者の方が目的が明確になる。

 あとはtargetが示す番地の内容を1文字ずつ読み取り、resultの要素にコピーします。
for (int i = 0; i < 100; i++) {
result[i] = *target; ← 1文字コピーする。
if (*target == 0) {
break; ← 文字は0だったので、ここでループを強制終了する。
}
target++; ← 次の文字にすすめる。
}

 forループは前のパートで説明したとおりです。int型変数iを0から99になるまで繰り返すループになっています。

配列の要素の指定
 ループ中でおこなっている 処理は、1文字のコピー処理です。
 代入される側はresult配列のi番目の要素を指定しています。以前説明したように、[]内に添字を指定する事で、配列の要素を指定できるわけですが、添字には変数も使えます。
result[i]


ポインタ型変数の使い方
 そして、右側のポインタ型変数targetの前に「*」(アスタリスク)を付ける事で、targetが指し示す番地の内容を読み取ることが出来ます。
*target

 あとは = で代入する事で1文字コピーが完了です。
テン*シー*シー-3
 そして、ポインタ型にも四則演算や++、--が適用できます。
target++

 この場合、番地が加算されたり、減算されたりするわけですが、int型と違い、ポインタ型はchar*型なら1バイト、int*型なら4バイトといったように、アスタリスクの付いた型の大きさ単位で変化します。
テン*シー*シー-4
テン*シー*シー-5

 たえず、型の切れ目に移動するようになっているわけです。例えばint*型の変数targetに対し
*(target + 2)

 と記述すると、targetが示す番地からint型の占有バイト数である4バイトの2個分で8バイトずれた番地の内容をint型の値として読み取れる事になります。
テン*シー*シー-6
ループの強制終了
 resultには最大100文字までコピーできるわけですが、C文字列の終端である0にぶつかれば、それ以上コピーする必要はありません。そのため
break

 を使い、ループを強制的に終わらせています。この命令はdo~while、while、forループで利用できます。
テン*シー*シー-7
テン*シー*シー-8
 これだけだと、ただの文字列コピーなので、resultにはそのまま代入せず、*targetに3を加えたものを代入します。
result[i] = *target + 3;

 これで暗号化完了です。
 厳格にシーザー暗号をまねるなら、最後のXYZはABCに戻る必要がありますが、ここでは単純に文字数値に +3 しています。

continue
 breakの他に、continueという、記述された場所から後のループ内処理は省略し、ループ終了/継続決定処理まで進むというものもあります。
テン*シー*シー-9
 continueの場合、ループを続けるかどうかはチェックされる事に注意してください。

 実際のソースは以下のようになります。終端の0まで+3してしまっては、終端の意味が無くなるので、その対応も追加します。
テン*シー*シー-10
サンプルプロジェクト:study-15-cipher.zip

#define
 100のかわりにMAX_LENとしている事に注目してください。
 以下の記述で、MAX_LENと書く事は100と書いたのと同じ事になります。
#define MAX_LEN 100 プリプロセス指令なので「;」(セミコロン)は付けない

 #defineは以前説明した#includeと同じく、プリプロセス指令です。
テン*シー*シー-11
 このようにする事で、100とそのまま記述するより、目的が明確になりプログラムの意味が捉えやすくなります。
 Runするとコンソールには
cipher =KHOOR#ZRUOG
Program ended with exit code: 0

 と出力されます。
 "KHOOR#ZRUOG"が、正しく暗号化されたものなら、暗号を解いて"HELLO WORLD"に戻せるはずです。以下のようにして暗号化したresultを解いてみましょう。
int main (int argc, const char * argv[])
{
・・・ "HELLO WORLD"を暗号化してresultに保存。
↓暗号を解く

for (int i = 0; i < MAX_LEN; i++) {
if (result[i] == 0) {
break; ←文字は0だったので、ここでループを強制終了する。
}
result[i] = result[i] - 3; ←1文字、暗号を解く。
}
printf("decipher =%s\n", result);
return 0;
}

サンプルプロジェクト:study-16-cipher-2.zip

 Runするとコンソールには
cipher =KHOOR#ZRUOG
decipher =HELLO WORLD
Program ended with exit code: 0

 と出力されます。正しく暗号化できているようです。

暗号化、暗号解凍関数の用意
 今度は、暗号化、暗号解凍、それぞれの処理を関数にしてみます。
暗号化関数:受け取ったplain_text文字列を暗号化してciphered_textに入れる。
void cipher(const char * plain_text, char * ciphered_text);

暗号解凍関数:受け取ったciphered_text暗号を解凍してplain_textに入れる。
void decipher(const char * ciphered_text, char * plain_text);

 先のループ処理でわかるように、文字列の先頭番地を受け取りさえすれば、文字列全体を加工可能です。そのため関数の引数には
char*型

 を使うわけです。
 また、暗号化関数cipherでは、元にするplain_text文字列のメモリ区画は読み取りだけで変更はしません。参照だけして書き込んだりはしない事を意思表示するためにconstを付けています。
const char*型

 これで、関数を使う者に、この関数がplain_text引数に渡した文字列は変更される事はないと伝えられます。
#define MAX_LEN 100
void cipher(const char * plain_text, char* ciphered_text)
{
↓ 最大MAX_LEN文字(ただし終端は0)までの文字をコピーするループ
for (int i = 0; i < MAX_LEN; i++) {
ciphered_text[i] = *plain_text + 3; ← 1文字暗号化する。
if (*plain_text == 0) {
ciphered_text[i] = 0; ← 暗号化のせいで終端まで3になっているのを修正。
break; ← 文字は0だったので、ここでループを強制終了する。
}
plain_text++; ← 次の文字にすすめる。
}
}
void decipher(const char * ciphered_text, char * plain_text)
{
↓ 暗号を解く
for (int i = 0; i < MAX_LEN; i++) {
if (ciphered_text[i] == 0) {
plain_text[i] = 0; ← 終端は 0 のままでよい。
break; ← 文字は0だったので、ここでループを強制終了する。
}
plain_text[i] = ciphered_text[i] - 3; ← 1文字、暗号を解く。
}
}

サンプルプロジェクト:study-17-cipher-func.zip

 ポインタ型引数に対して、配列引数のような扱いをしている事に注目してください。
void cipher(const char * plain_text, char * ciphered_text)
・・・
ciphered_text[i] = *plain_text + 3;

 このようにポインタ型は配列のようにも扱えます。
 もちろんポインタ型本来の
*(ciphered_text + i) = *plain_text + 3;

 という書き方でもかまいませんし、iを使わずに
*ciphered_text = *plain_text + 3;
if (*plain_text == 0) {
*ciphered_text = 0;
break;
}
plain_text++;
ciphered_text++;

 と書いてもかまいません。
 いろいろな記述法があるので、わかりやすい記述法を選ぶといいでしょう。それぞれの記述法で多少の処理速度の違いも発生しますが、それよりはわかりやすい記述法をこころがけてください。

 逆に引数の方を以下のように書いても問題ありません。
void cipher(const char * plain_text, char ciphered_text[])

 main関数で体験済みですが、引数では[]の中の要素数を省略できます。
void cipher(const char * plain_text, char ciphered_text[MAX_LEN])

 と書いてもかまいませんが、XcodeのC言語コンパイラは要素数の違いを気にしないので、あまり意味がありません。以下のように要素数を1にしてcipher関数に渡してもコンパイラは注意してきません。
int main (int argc, const char * argv[])
{
char result[1];
cipher("HELLO WORLD", result);

 "HELLO WORLD"は11文字と0終端で、合計12個のchar型配列要素が必要なので、結果、resultでメモリに確保した配列エリアをはみ出した領域に、暗号化された文字列が書き出される事になります。
 この場合、アプリケーションは最悪、機能停止(そのまま普通に実行される場合もあるので、なおやっかいです。実行できたとしても、それはけっして正常な状態ではありません)します。
 そういう点では、この2つの関数は少し危険な部分が残っているわけです。
 本来なら、これを防止するために、最大要素数を引数に受け取るなりして、配列の大きさをチェックしながら処理するべきですが、ここではC言語の学習が目的なので省略しています。


つづく