暗号化、暗号解凍関数の拡張
 引数にアルファベットのずらし量を指定できるようにすれば、3個ずらした暗号や、10個ずらした暗号なども容易に作れるようになります。
void cipher(const char * plain_text, char * ciphered_text, int shift)
{
・・・
ciphered_text[i] = *plain_text + shift;

void decipher(const char * ciphered_text, char * plain_text, int shift)
{
・・・
plain_text[i] = ciphered_text[i] - shift;

 ただ、この場合、どのずらし量で暗号化したかを暗号後の文字列とペアで管理する必要があり、複数の暗号文字列を扱うと、プログラムが理解しづらくなります。
int main (int argc, const char * argv[])
{
char cipher_a[MAX_LEN];
char cipher_b[MAX_LEN];
int shift_a; ←この時点ではshift_aやshift_bが何に使われるか想像しにくい。
int shift_b;
char plain[MAX_LEN];

shift_a = 3;
cipher("HELLO WORLD", cipher_a, shift_a); ←ここでshift_aの意味がハッキリする。
decipher(cipher_a, plain, shift_a);

shift_b = 4;
cipher("HELLO WORLD", cipher_b, shift_b);
decipher(cipher_a, plain, shift_b);
return 0;
}


構造体
 注釈を付けたりしていくのもいいのですが、それよりも関係のある変数同士を組み合わせて、一つの変数を作り上げる方が適切です。
 このような時に利用されるのが構造体です。以下はずらし量をint型のshiftという要素、暗号化された文字列をchar型配列のtextという要素として持つ構造体の変数aの宣言です。
struct {
int shift;
char text[MAX_LEN];
} a;


テン*シー*シー-12

 各要素には、変数名と要素名を「.」(ドット)でつなぐ事でアクセスできます。
a.shift

 構造体を使う事で、データの関連性が明確に表現できます。
int main (int argc, const char * argv[])
{
↓暗号化のずらし量shiftと暗号テキストのtextが構造体で一体になっている。
struct {
int shift;
char text[MAX_LEN];
} a, b;  ←「,」で区切って複数の変数を宣言できる。
char plain[MAX_LEN];

a.shift = 3;
cipher("HELLO WORLD", a.text, a.shift);
decipher(a.text, plain, a.shift);

b.shift = 4;
cipher("HELLO WORLD", b.text, b.shift);
decipher(b.text, plain, b.shift);
return 0;
}

サンプルプロジェクト:study-18-cipher-struct.zip

 a、b、2つの構造体変数のメモリ区画は、他のローカル変数同様スタック領域上に確保されます。

テン*シー*シー-13

 構造体を使うなら、cipher、decipher関数の引数も構造体にしましょう。
 その場合、引数として指定できるよう構造体を新しい型として定義するのが一般的です。

typedef
 C言語では、typedefを使い、既存の型から新しい型を定義できます。以下はchar型をsingned8bit型という新しい型として定義しています。
typedef char singned8bit;


テン*シー*シー-14

 この定義以降は、singned8bitという型が変数宣言に利用できるようになります。
typedef char singned8bit;

singned8bit add(singned8bit a, singned8bit b)
{
singned8bit x = a + b;
return x;
}

 singned8bit型はchar型と同じ特性の型という事になります。
 このtypedefは構造体にも利用でき、以下ではさきほどの構造体を新しい型、CipherText型として定義しています。
typedef struct {
int shift;
char text[MAX_LEN];
} CipherText;


cipher関数の構造体対応版
 cipher関数を、文字列とずらし量を受け取り、暗号化した文字列とずらし量をCipherText型引数に設定する関数に変更してみます。
#define MAX_LEN 100
typedef struct {
int shift;
char text[MAX_LEN];
} CipherText;

void cipher(const char * plain_text, int shift, CipherText * cipher);
・・・
void cipher(const char * plain_text, int shift, CipherText * ioCipher)
↑宣言部と違い引数名に関数名が使えない
そのため、cipherではなくioCipherとした。

{
ioCipher->shift = shift;
for (int i = 0; i < cipher->max_length; i++) {
ioCipher->text[i] = *plain_text + ioCipher->shift;
if (*plain_text == 0) {
ioCipher->text[i] = 0;
break;
}
plain_text++;
}
}
・・・
int main (int argc, const char * argv[])
{
CipherText a, b;
char plain[MAX_LEN];

cipher("HELLO WORLD", 3, &a); ← CipherText構造体をポインタ型で渡している。
・・・

 CipherText構造体をポインタ型で渡している事に注意してください。
void cipher(const char * plain_text, int shift, CipherText cipher);

 というように、CipherText構造体そのものを渡すようにした場合、スタック領域には、呼び出し側とは異なるCipherText構造体用のメモリ区画が確保されてしまう事になります。

テン*シー*シー-15

 ポインタ型での構造体の要素へのアクセスは、以下のようにint*型やchar*型のようにアスタリスクを付ける方法もありますが
(*cipher).shift

 それよりは
cipher->shift

 というように表記する方が一般的です。
 また、既存の変数の番地を、ポインタ型の変数に入れたい場合は変数名の前に「&」(アンパサンド)を付けて代入します。

int x;
int* x_p = &x;
char c;
char* p = &c;

 配列の場合は
int mat[100];
int* p = mat;

 とすれば、配列の先頭番地が
int mat[100];
int* p = &mat[5];

 とすれば、添字で指定される要素の番地が入ります。


 今回のcipher関数呼び出しなら以下のように記述します。
cipher("HELLO WORLD", 3, &a);

 これで、aに、"HELLO WORLD"を3つずらした暗号文字列と、ずらし量が入る事になります。
 decipher関数もCipherText構造体に対応させたものを次ページに示しておきます。

 次のパートでは文字列を、100文字といわず、1万文字でも暗号化できるようにしてみましょう。
#include

#define MAX_LEN 100
typedef struct {
int shift;
char text[MAX_LEN];
} CipherText;

void cipher(const char * plain_text, int shift, CipherText * cipher);
void decipher(const CipherText * cipher, char * plain_text);

// 暗号化する
void cipher(const char * plain_text, int shift, CipherText * ioCipher)
{
ioCipher->shift = shift;
for (int i = 0; i < MAX_LEN; i++) {
ioCipher->text[i] = *plain_text + ioCipher->shift; // 1文字暗号化する。
if (*plain_text == 0) {
ioCipher->text[i] = 0; // 暗号化のせいで終端まで3になっているのを修正。
break; // 文字は0だったので、ここでループを強制終了する。
}
plain_text++; // 次の文字にすすめる。
}
}

// 暗号を解く
void decipher(const CipherText * cipher, char * plain_text)
{
for (int i = 0; i < MAX_LEN; i++) {
if (cipher->text[i] == 0) {
plain_text[i] = 0; // 終端は 0 のままでよい。
break; // 文字は0だったので、ここでループを強制終了する。
}
plain_text[i] = cipher->text[i] - cipher->shift; // 1文字、暗号を解く。
}
}

int main (int argc, const char * argv[])
{
CipherText a, b;
char plain[MAX_LEN];

cipher("HELLO WORLD", 3, &a); // CipherText構造体をポインタ型で渡している。
decipher(&a, plain);
printf("%s = %s\n", plain, a.text);

cipher("HELLO WORLD", 4, &b); // CipherText構造体をポインタ型で渡している。
decipher(&b, plain);
printf("%s = %s\n", plain, b.text);
return 0;
}
注意)ウエブ上ではバックスラッシュが円マークで表示されています。
サンプルプロジェクト:study-19-cipher-struct-2.zip