ブートキャンプ目次
(1)iPhone/iPadアプリケーションを作るには?
→無料開発者登録
(2)プログラミング言語とは何か?
(3)Xcodeを使ったC言語の学習
(4)C言語ソースの解析1/2
(5)C言語ソースの解析2/2
(6)自分で関数を作り、利用する1/2
(7)自分で関数を作り、利用する2/2
(1)iPhone/iPadアプリケーションを作るには?
→無料開発者登録
(2)プログラミング言語とは何か?
(3)Xcodeを使ったC言語の学習
(4)C言語ソースの解析1/2
(5)C言語ソースの解析2/2
(6)自分で関数を作り、利用する1/2
(7)自分で関数を作り、利用する2/2
パート4:文字列の加工
今度は文字列を加工してみます。与えられた文字列をアルファベットを3つずらす手法で暗号化します。例えば
CALL
なら暗号化後の文字列は
FDOO
となります。
シーザー暗号というローマ時代からある暗号法です。
配列変数
まず、暗号化した文字列を記憶するためのメモリ区画確保ですが、これには配列変数を使います。main関数の引数で説明した配列です。
変数宣言時に、変数名の後ろに[](ブラケット)で囲んだ要素数を付ける事で、その要素数分の配列が確保されます。
次に、const char*型の変数targetを宣言して、"HELLO WORLD"の先頭番地を代入します。
char result[100];
const char* target = "HELLO WORLD";
const char* target = "HELLO WORLD";
const char*型の変数と文字列リテラルを「=」で結ぶ事は、C文字列先頭番地の代入を意味します。
余談ですが、文字列リテラルの他に、「’」(シングルクォーテーション)で囲んだ1文字は文字コード表の対応する数値を意味するというルールもあります。
char a = 65; どちらもchar型変数aに文字Aを設定する事になるが、
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++; ← 次の文字にすすめる。
}
result[i] = *target; ← 1文字コピーする。
if (*target == 0) {
break; ← 文字は0だったので、ここでループを強制終了する。
}
target++; ← 次の文字にすすめる。
}
forループは前のパートで説明したとおりです。int型変数iを0から99になるまで繰り返すループになっています。
配列の要素の指定
ループ中でおこなっている 処理は、1文字のコピー処理です。
代入される側はresult配列のi番目の要素を指定しています。以前説明したように、[]内に添字を指定する事で、配列の要素を指定できるわけですが、添字には変数も使えます。
result[i]
ポインタ型変数の使い方
そして、右側のポインタ型変数targetの前に「*」(アスタリスク)を付ける事で、targetが指し示す番地の内容を読み取ることが出来ます。
*target
あとは = で代入する事で1文字コピーが完了です。
そして、ポインタ型にも四則演算や++、--が適用できます。
target++
この場合、番地が加算されたり、減算されたりするわけですが、int型と違い、ポインタ型はchar*型なら1バイト、int*型なら4バイトといったように、アスタリスクの付いた型の大きさ単位で変化します。
たえず、型の切れ目に移動するようになっているわけです。例えばint*型の変数targetに対し
*(target + 2)
と記述すると、targetが示す番地からint型の占有バイト数である4バイトの2個分で8バイトずれた番地の内容をint型の値として読み取れる事になります。
ループの強制終了
resultには最大100文字までコピーできるわけですが、C文字列の終端である0にぶつかれば、それ以上コピーする必要はありません。そのため
break
を使い、ループを強制的に終わらせています。この命令はdo~while、while、forループで利用できます。
これだけだと、ただの文字列コピーなので、resultにはそのまま代入せず、*targetに3を加えたものを代入します。
result[i] = *target + 3;
これで暗号化完了です。
厳格にシーザー暗号をまねるなら、最後のXYZはABCに戻る必要がありますが、ここでは単純に文字数値に +3 しています。
continue
breakの他に、continueという、記述された場所から後のループ内処理は省略し、ループ終了/継続決定処理まで進むというものもあります。
continueの場合、ループを続けるかどうかはチェックされる事に注意してください。
breakの他に、continueという、記述された場所から後のループ内処理は省略し、ループ終了/継続決定処理まで進むというものもあります。
continueの場合、ループを続けるかどうかはチェックされる事に注意してください。
実際のソースは以下のようになります。終端の0まで+3してしまっては、終端の意味が無くなるので、その対応も追加します。
サンプルプロジェクト:study-15-cipher.zip
#define
100のかわりにMAX_LENとしている事に注目してください。
以下の記述で、MAX_LENと書く事は100と書いたのと同じ事になります。
#define MAX_LEN 100 プリプロセス指令なので「;」(セミコロン)は付けない
#defineは以前説明した#includeと同じく、プリプロセス指令です。
このようにする事で、100とそのまま記述するより、目的が明確になりプログラムの意味が捉えやすくなります。
Runするとコンソールには
cipher =KHOOR#ZRUOG
Program ended with exit code: 0
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;
}
{
・・・ "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
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);
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文字、暗号を解く。
}
}
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;
このようにポインタ型は配列のようにも扱えます。
もちろんポインタ型本来の
*(ciphered_text + i) = *plain_text + 3;
という書き方でもかまいませんし、iを使わずに
*ciphered_text = *plain_text + 3;
if (*plain_text == 0) {
*ciphered_text = 0;
break;
}
plain_text++;
ciphered_text++;
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);
{
char result[1];
cipher("HELLO WORLD", result);
"HELLO WORLD"は11文字と0終端で、合計12個のchar型配列要素が必要なので、結果、resultでメモリに確保した配列エリアをはみ出した領域に、暗号化された文字列が書き出される事になります。
この場合、アプリケーションは最悪、機能停止(そのまま普通に実行される場合もあるので、なおやっかいです。実行できたとしても、それはけっして正常な状態ではありません)します。
そういう点では、この2つの関数は少し危険な部分が残っているわけです。
本来なら、これを防止するために、最大要素数を引数に受け取るなりして、配列の大きさをチェックしながら処理するべきですが、ここではC言語の学習が目的なので省略しています。
本来なら、これを防止するために、最大要素数を引数に受け取るなりして、配列の大きさをチェックしながら処理するべきですが、ここではC言語の学習が目的なので省略しています。
つづく