ファッションブランドコミュニティ「プーペガール」
よく来たな~、新兵ども!
$テン*シー*シー-bootcamp
 というわけで、いよいよブートキャンプ開幕っす。
 目標は、iPhoneやiPadのアプリケーションを作るための知識を一から積み上げる事。どんな機材が必要か、どんな知識が必要か、から初めて、C言語の学習までやりますよん。

目次
(1)iPhone/iPadアプリケーションを作るには?
  →無料開発者登録
(2)プログラミング言語とは何か?
(3)Xcodeを使ったC言語の学習
(4)C言語ソースの解析1/2
(5)C言語ソースの解析2/2
(6)自分で関数を作り、利用する1/2
(7)自分で関数を作り、利用する2/2
(8)文字列の加工 1/2
(9)文字列の加工 2/2
 で、C言語の学習の後は~、そうだね、ドリル本だね。まだ出ていないけどな~、ハッハー。

T-POINTで東日本大震災支援
 T-POINTで募金できるみたいっす。
Tポイントでつなぐ東日本大震災 子ども支援

 お~、なんかT-POINTの期限が近づいてるのに欲しい本とかビデオがねーのよ、な人はポッチとしましょう。

「iOSデバッグ&最適化技法 for iPad/iPhone.」のお知らせ
 本の内容を試す時はARCをOFFでよろしく!

Xcode 4リリース補完
1 | 2 | 3 | 4 | 5 |最初 次ページ >>
2012年02月15日(水) 00時29分34秒

ブートキャンプ(9)文字列の加工 2/2

テーマ:ブートキャンプ

暗号化、暗号解凍関数の拡張
 引数にアルファベットのずらし量を指定できるようにすれば、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
最近の画像つき記事
画像一覧へ ]
2012年02月14日(火) 23時32分15秒

ラン10.0km

テーマ:ランニング
 ラン10.0km/60分。

現在
 体重:61.3kg 体脂肪:20.3%

目標
 体重:59.5kg 体脂肪:15.1%
2012年02月09日(木) 09時00分04秒

ブートキャンプ(8)文字列の加工 1/2

テーマ:ブートキャンプ

パート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言語の学習が目的なので省略しています。


つづく

Amebaおすすめキーワード

    1 | 2 | 3 | 4 | 5 |最初 次ページ >>
    アメーバに会員登録して、ブログをつくろう! powered by Ameba (アメーバ)|ブログを中心とした登録無料サイト