Silverlight をインストールするには、ここをクリックします*
Japan変更|サイトマップ
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > Student > プログラミング チャレンジ スクエア > 第 7 章 暗号化プログラムを作成しよう C# 編

第 7 章 暗号化プログラムを作成しよう


サンプルプログラムのダウンロード (chap7_csharp.msi, 228 KB)

※ また、サンプルプログラムは、あらかじめ Visual C# 2005 Express Edition と同じパソコンにインストールしておいてください。
   設定方法は こちら


この章でやること
・ 簡単な暗号作成プログラムを完成させます。
・ 入力された文字列の切り取りや結合を行いながら、ループ文について体験します。

第 7 章 暗号化プログラムを作成しよう
暗号化の処理を行う部分を書いてみよう
解読の処理を記述しよう





第 6 章が終わってから、待ちに待っていた週末がやってきました。今日は、いよいよ健一さんが合流して、翔太君と美咲さんと一緒に第 7 章に挑戦する日です。


翔太:この入門マニュアルもあと少しだね。
美咲:そうね。なんだかさびしい気もするけど、ここまできたら最後までやり遂げないとね。


二人が話していると、健一さんがやってきました。


健一:こんにちは!
翔太
&
美咲
:健一さん!
健一:二人とも気合十分って顔つきだね。
翔太:第 6 章までをずっとおさらいしてきたんだけど、改めてプログラミングって面白いんだなって思って。
美咲:私も。でも、同時にだんだん難しくなってきてるのもわかるから、これから何が出てくるのか、ちょっと怖い気もする。
健一:プログラミングは決して怖いものではないんだけど、かといって、油断しすぎてもいけない。ちょっとした書き間違いが一つあるだけで、プログラム全体が動かなくなってしまうことも珍しくないんだ。でも、この入門マニュアルの範囲ならまだ大丈夫。あわてないで、ゆっくりやれば、プログラミングの楽しい部分がよくわかるはずだから。
翔太:終わりに近づいているのは名残惜しいけど、始めようよ。
健一:よし、それじゃさっそく始めようか。


プログラムメニューから Visual C# 2005 Express Edition を立ち上げて、サンプルファイルフォルダの中の Part7_C#\Part7_Base フォルダにある Part7.sln を開きます。そして、[ソリューション エクスプローラ] から、[Form1.cs] を開きます。





翔太:[暗号化] ボタンと [解読] ボタンがあるだけのシンプルなデザインだね。
美咲:入門マニュアルによると、一番左端のテキスト ボックスに入力した文字を暗号にしたり、解読したりできるらしいわ。
翔太:なるほど、きっと、暗号化された文字が中央のテキスト ボックスに表示されるんだな。で、右端は解読結果を表示するテキスト ボックスか。でも、暗号の基になったテキスト ボックスがあれば必要ないんじゃない?
美咲:中央のテキスト ボックスに直接暗号文を入力することもできるみたいよ。
翔太:なるほど、それを解読するための機能なのか。とりあえず、デバッグ ボタンを押してみよう。


デバッグ ボタンをクリックして、プログラムを実行します。どのボタンをクリックしても、特に何も起こりません。





翔太:適当に文字を入力してボタンを押しても何も起きないね。
健一:よし、それじゃデバッグ 画面を終了して、プログラムの中身を見ていくことにしよう。




<暗号化の処理を行う部分を書いてみよう>

健一:最初は、[暗号化] ボタンの処理からだ。
美咲:まず、ボタンのプロパティ ウィンドウを開くのね。
翔太:今回は、いきなりコードを書いたりしないんだな。
健一:なんのことだい?
美咲:第 6 章では、最初に自分でコード エディタを開いて、コードを書くところから始まったの。
健一:ああ、なるほど。いろんなやり方があるんだけど、その時々に応じてやりやすいやり方でやっていけばいいんだよ。今回の場合は、ボタンの処理が重要だから、そこから始めるんだ。プロパティ ウィンドウが開いたら、[イベント] アイコンをクリックしてイベントの一覧を表示する。そして、一覧にある [Click] の右にあるコンボ ボックスに「btnAngou_Click」と入力して [Enter] キーを押す。







翔太:コード エディタが開いたよ。
健一:リスト 7-1-1 のコードが追加されているかどうか確認してみて。


リスト 7-1-1 は、あとで読み返すときにわかりやすいようにコメント文を追加した形で紹介しています。実際に上記の手順でコードを追加した場合、コメントは追加されていませんので、必要に応じて自分で書き加えてください。以後のリストについても同様です。


リスト 7-1-1

        // [暗号化>] ボタンを押したときの処理
        private void btnAngou_Click(object sender, System.EventArgs e)
        {
        
        }


美咲:追加されたようよ。
健一:今度は、「private void btnAngou_Click〜」の次の行にある「{」と「}」の間の行に次のリスト 7-1-2 のコードを追加するんだ。


リスト 7-1-2

            // 原文を格納する変数を用意して
            // txtGenbun の Text プロパティ値で初期化する
            string Genbun = txtGenbun.Text;
            // 原文の長さを格納する変数を用意して
            // Genbun の Length プロパティ値で初期化する
            int Nagasa = Genbun.Length;
            // 暗号1 を格納する変数を用意して NULL 文字列で初期化する
            string Angou1 = "";
            // 暗号2 を格納する変数を用意して NULL 文字列で初期化する
            string Angou2 = "";


翔太:わ! なんだかたくさん同じようなのが並んでるな。
美咲:えーと、どれも変数の宣言ってことでいいのかしら。最初の 2 つの変数には何かのプロパティの値が代入されてるわ。
翔太:あとの 2 つの変数には、「""」が代入されてる。「""」は文字列の何もないということを表わすはずだから、この場合もこの Angou1 と Angou2 という変数には何も文字列が入らないという初期化が行われたって事だね。
健一:ずいぶん、見方がしっかりしてきたね。この調子なら、しばらく黙ってみてることにしようか。
翔太:え、そんなぁ。
健一:冗談、冗談。それじゃ上 2 つでやってることをもう少し詳しく説明してもらおうかな。
美咲:えーっと。1 行目は、Genbun という変数を string 型で宣言して、txtGenbun というテキスト ボックスの Text プロパティを代入してます。これは文字列の変数で、Angou1 や Angou2 と同じね。
翔太:2 行目は、Nagasa という文字列を int 型で宣言してる。int は整数を扱う型だから、これは数値の変数だね。それで、Genbun 変数の・・・、Length っていうプロパティを代入してる。Length って何のプロパティだろう?
健一:Length は長さを表わすプロパティなんだ。この場合は文字の長さ、つまり文字数を表わしている。プロパティやメソッド、クラス、コントロール、コンポーネントなどは、プログラマが自分でつける名前を除けば、基本的に英語で書かれていることがほとんどだ。わからない名前が出てきたら、英和辞典で調べてみるといい。既に知っている単語でも、意外な意味があったりするから面白いよ。


健一:さて、最初のコードは意味までばっちりつかめてるみたいだね。少し補足すると、Genbun は、左のテキスト ボックスに入力されたデータを入れる変数。文字通り、基になる原文を入れておくところだ。Nagasa は、その原文の文字列の長さ。Angou1 と Angou2 は、原文を基に暗号化する際に使用する変数なんだ。
翔太:なぜ 2 つもあるの?
健一:それはあとのお楽しみということで。ここまでわかったら、次に進もう。このコードに続けて、次のリスト 7-1-3 のコードを追加するんだ。


リスト 7-1-3

            // [暗号1 の文字位置] を表す変数を用意して 0 で初期化し
            // [暗号1 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index1 = 0; Index1 < Nagasa; Index1 = Index1 + 2)
            {
                // 原文から [暗号1 の文字位置] にある 1 文字を取出し
                // 暗号1 に結合させる
                Angou1 = Angou1 + Genbun.Substring(Index1, 1);
            }


美咲:新しい単語が出てきたわ。
翔太:for ?
美咲:これはいったい何をやってるの?
健一:これに似た感じの文を今まで見てこなかったかい?
翔太:似た感じ? ・・・うーん、なんだろう? for と書いてある横に何かの式が書いてあるね。・・・、って、もしかして if で始まるあの条件分岐って奴のこと?
健一:そうそう、それ。
美咲:え、じゃあ、これも条件式があって、それによって処理が分かれてるの?
健一:ある意味それも正解かな。それじゃ、順を追って説明しようか。


健一:まず、1 行目では、Index1 という変数を新しく int 型で宣言して、初期値を 0 に指定している。そして、「for」の後にある「{」と「}」の間にある処理を、「for」の後ろで指定した条件に達するまで繰り返すという命令なんだ。
翔太:指定した条件?
健一:「;」で区切られた 2 つ目の式が条件だよ。
翔太:「Index1 < Nagasa」? これどういう条件式なの?
健一:Nagasa というのは文字列の長さ、つまり文字数について入れておく変数だったよね。つまり、ここでは文字数よりも Index1 の数が少ない間はずっと 2 行目以降を繰り返す、という意味なんだ。
美咲:最後の「Index1 = Index1 + 2」は?
健一:繰り返す回数に関係しているんだ。for から始まるこの書き方は、ループ文と呼ばれている。
翔太:ループ? ループって・・・、ジェットコースターとかに出てくる奴?
健一:そういえばそういうのもあるね。ループというのは元々「輪」という意味があるんだ。ジェットコースターもぐるっと一回転する奴にループってついてるはずだし、今回のループ文の場合も、輪のように同じ範囲を回転して行ったり来たりするプログラムのことをいうんだよ。
翔太:つまり、指定した条件に達するまで同じことを繰り返すからループ、っていうことなのか。
健一:そういうこと。「回転」する回数だけど、もし「Index1 = Index1 + 2」というのがなければ、このサンプルの場合は、Index1 という変数が 0 から 1 ずつ増えていって、文字数より 1 つ少ない数に達するまで、という作業になる。ところが、「Index1 = Index1 + 2」という記述があると・・・
美咲:・・・もしかして、Index1 変数が 2 ずつ増えるの?
健一:そのとおり! 1 回ループする度に、Index1 に 2 を加えるから、1 ずつ増える場合に比べて倍の速さで処理が進む。それに Index1 の値は次の処理命令の中でも使われているから、そこの結果にも反映してくるんだ。
翔太:なるほどね。それで、問題の処理する命令の中身は・・・、Angou1 に何かを代入してるね。自分自身である Angou1 に「Genbun.Substring(Index1, 1)」を加えてる・・・
美咲:Genbun は原文が入った変数よね。Substring はメソッドかしら、プロパティかしら。
健一:この場合はメソッドだよ。Substiring メソッドは、() で指定した内容を基に、文字列を取出す機能があるんだ。
翔太:文字列を取出す・・・?
健一:例えば、一番最初にプログラムを動かした場合を考えてみよう。初めてこのループ文を動かすときには、Index1 の値は?
翔太:0 ?
健一:そう。だから、Substiring メソッドは「(0, 1)」の範囲の文字列を取出すことになる。この場合、最初の 0 は Genbun 変数内の文字列の何文字目から取出すのか、開始位置を表わす数字になる。0 というのは実は 1 文字目のことを表わしているんだ。そして、0 の隣にある 1 は、開始位置から何文字目までを取出すのかを表わしている。だからこの場合は、Genbun 変数内の 1 文字目から 1 文字まで、つまり 1 文字目だけを取出す、という命令になっている。
美咲:えーっと。文字列から文字を取出して、どうするの?
健一:まあまあ、あわてないで。さて、この作業が原文の文字列の文字数より 1 文字少ない数まで続けられる。それも、取出す文字の位置は 1 つ飛ばしで変わっていくんだ。
翔太:どうして 1 つ飛ばしなの?
美咲:「「Index1 = Index1 + 2」で 2 つずつ Index1 が増えるから? これだと、Index1 の値が 0、2、4、6 ・・・となるから 1 つ飛ばしになってるわ。
翔太:なるほど。あれ? でもそうすると、「Nagasa」と一致しない場合が出てくるよね。長さが奇数、例えば 5 だったら。Index1は 4 までいって、その次、6 になっちゃうよ?
健一:その場合は、条件を超えたものとみなして、やはりループは終了するんだよ。だからその場合、Index1 の値が 6 になったら、このループ文の最後の「}」の行の次の行以降が処理されるんだ。
翔太:そうなんだ。うまくできてるんだなぁ。
美咲:それで、肝心の文字列の取出しの方はどうなるの?
健一:それじゃ原文が 6 文字の文字列の場合を考えてみようか。この場合、Substirng メソッドが取出す文字列の開始位置は、1 文字目、3 文字目、5 文字目、ということになるんだ。何文字取出すかは 1 のままだから、つまり原文の 1 文字目、3 文字目、5 文字目がそれぞれ Genbun.Substring メソッドの値ということになる。それが、Angou1 に加えられて代入されるんだ。
翔太:だんだん混乱してきちゃった。えーっと、じゃあ、ABCDEF で考えてみようか。これの 1 文字目は A、3 文字目は C、5 文字目は E だよね。
美咲:うん。それで、1 回目のループでは、Angou1 には何も入っていないから、A がそのまま代入される形になるわね。
翔太:だから 2 回目のループのときは、Angou1 には A が入っていて、そこに今度は 3 文字目の C が結合されるのか。ということは、Angou1 は AC になるんだ。
健一:そう、そして同じことをまた繰り返して、今度は 5 文字目の E が結合されて、ACE になる。これはこのループ文が終了したときの Angou1 の値だね。
翔太:なるほど・・・。うん、なんとなく文字を取出すという流れはわかった気がする。
美咲:そうね。でも、これだと元の文字列の半分になっちゃったわ。
健一:あわてないでって言っただろう? 残りの半分はこれから作るんだよ。
美咲:残りを作る?
健一:それじゃ、リスト 7-1-3 の次にリスト 7-1-4 のコードを続けて書いてごらん。


リスト 7-1-4

            // [暗号2 の文字位置] を表す変数を用意して 1 で初期化し
            // [暗号2 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index2 = 1; Index2 < Nagasa; Index2 = Index2 + 2)
            {
                // 原文から [暗号2 の文字位置] にある 1 文字を取出し
                // 暗号2 に結合させる
                Angou2 = Angou2 + Genbun.Substring(Index2, 1);
            }


翔太:あ、今度は Index2 を使ってループ文が・・・
健一:さあ、ここでやってるのはどういうことか説明できるかな?
美咲:え? でも、変数名が 1 から 2 に変わっただけで、あとは全部一緒なんじゃ・・・ あ! Index2 の初期値が違ってる!
翔太:Index2 の初期値? ・・・たしかにこのループ文では 1 になってるね。さっきのループ文の Index1 は 0 だったのに、どうして違ってるの?
健一:それはこのループ文を使って取得する文字列を、さっきのループ文とは 1 つずらすためさ。
美咲:1 つずらす?
健一:そう、ループ文の Index の値が一度に 2 つ増えるのはどちらも同じだ。ただ、さっきのループ文が 0 から始まって、0、2、4、6 と増えていくのに対して、今度のループ文はそれが 1、3、5、7 と増えていく。ということは・・・?
翔太:Index の値が違ったら・・・、Substring メソッドの範囲指定が変わるってこと?
健一:正解。つまり、さっきのループ文では、文字を取出す開始位置が原文の文字列の 1 文字目から始まっていた。指定の上では 0 からだけどね。それで、それに対して今度のループ文では、指定の上では 1 から、そして実際には、開始位置が原文の文字列の 2 文字目から文字の取出しが始まることになる。さっきの例でそのまま考えてごらん?
翔太:えーっと。ABCDEF の 6 文字が原文の場合、Index2 の値は 1、3、5 の 3 パターン存在することがわかる。
美咲:7 がないのは、「Nagasa」を超えてしまうからね。
翔太:そう。だから、この場合、Substirng メソッドが取出す文字列の開始位置は、2 文字目、4 文字目、6 文字目、ということになる。何文字取出すかは最初のループ文と同じ 1 だから、つまり原文の 2 文字目、4 文字目、6 文字目がそれぞれ Genbun.Substring メソッドの値ということになる。それが、Angou2 に加えられて代入される、と。
美咲:となると、ABCDEF が原文の場合、これの 2 文字目は B 、4 文字目は D、6 文字目は F になって、1 回目のループでは、Angou2 には何も入っていないから、B がそのまま代入されることになるわ。
翔太:それで 2 回目のループのときは、Angou2 には B が入っていて、そこに今度は 4 文字目の D が結合される。だから、この時点で Angou2 は BD になるんだ。
健一:そして 3 回目のループで、今度は 6 文字目の F が結合されて、BDF になる。これが、このループ文が終了したときの Angou2 の値というわけだ。
翔太:すごい! なんかよくわかんないけど、すごいことをやってる気がしてきた!
健一:翔太君は、この手のプログラムが好きなタイプみたいだね。それじゃ、続きをいってみようか。それで、やっと全体が見えてくるから。美咲ちゃんも大丈夫かい?
美咲:なんとか、ついていってます。でも、よくこんなややこしいこと考えるわね、プログラマの人って。
健一:プログラミングって、何でもできるように見えて実はかなり制限があるんだ。
美咲:制限?
健一:そう、例えばパソコンの中で絵を動かしたい、って思っても、今でこそある程度までは簡単にできるようになったけど、それでもまだ考えなきゃいけないことはたくさんある。その制限の中で、どうやればやりたいことがやれるのか、知恵を絞るのがプログラマなんだよ。僕の先輩の一人は、毎日パズルを解いてるようなものだって言ってたくらいだから。
翔太:パズルか・・・。そんな仕事なら楽しそうだな。
健一:プログラマの仕事については、また別の機会に話すとして、とりあえず続きを見てみよう。リスト 7-1-5 を、さっきのリスト 7-1-4 のあとに続けて書き加えてほしい。


リスト 7-1-5

            // 暗号1 と暗号2 を結合したものを txtAngou の Text プロパティに設定する
            txtAngou.Text = Angou1 + Angou2;


美咲:これは・・・ Angou1 と Angou2 を結合させて 1 つの文字列にしてるみたいね。
翔太:それを、txtAngou というテキスト ボックスの Text プロパティに代入してる。これで、画面に表示させてるんだね。
健一:そう、これが中央のテキスト ボックスに反映されるんだ。それじゃ、早速デバッグ ボタンで動きを確認してみよう。


左端のテキスト ボックスに「ABCDEF」と入力して、[暗号化] ボタンを押してください。中央のテキスト ボックスに「ACEBDF」と表示されることを確認します。








健一:さっきの例のとおり「ABCDEF」と入力すると、ちゃんと Angou1 の「ACE」と、Angou2 の「BDF」が結合された結果、このように「ACEBDF」として表示されることになるんだ。


テストは、全角文字でも可能です。例えば、「あいうえお」と左端のテキスト ボックスに入力して [暗号化] ボタンをクリックすると、「あうおいえ」という文字列が中央のテキスト ボックスに表示されます。皆さんもいろいろな文字列で試してみてください。

なお、もしうまくいかない場合には、どこかでコードが間違っている可能性があります。問題なければ、ここまでのコードはリスト 7-1-6 のようになっているはずです。うまく動かない場合には、確認してみましょう。



リスト 7-1-6

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Part7
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // [暗号化>] ボタンを押したときの処理
        private void btnAngou_Click(object sender, System.EventArgs e)
        {
            // 原文を格納する変数を用意して
            // txtGenbun の Text プロパティ値で初期化する
            string Genbun = txtGenbun.Text;
            // 原文の長さを格納する変数を用意して
            // Genbun の Length プロパティ値で初期化する
            int Nagasa = Genbun.Length;
            // 暗号1 を格納する変数を用意して NULL 文字列で初期化する
            string Angou1 = "";
            // 暗号2 を格納する変数を用意して NULL 文字列で初期化する
            string Angou2 = "";

            // [暗号1 の文字位置] を表す変数を用意して 0 で初期化し
            // [暗号1 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index1 = 0; Index1 < Nagasa; Index1 = Index1 + 2)
            {
                // 原文から [暗号1 の文字位置] にある 1 文字を取出し
                // 暗号1 に結合させる
                Angou1 = Angou1 + Genbun.Substring(Index1, 1);
            }
            // [暗号2 の文字位置] を表す変数を用意して 1 で初期化し
            // [暗号2 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index2 = 1; Index2 < Nagasa; Index2 = Index2 + 2)
            {
                // 原文から [暗号2 の文字位置] にある 1 文字を取出し
                // 暗号2 に結合させる
                Angou2 = Angou2 + Genbun.Substring(Index2, 1);
            }

            // 暗号1 と暗号2 を結合したものを txtAngou の Text プロパティに設定する
            txtAngou.Text = Angou1 + Angou2;
        }
    }
}


健一:うまくできたみたいだね。
美咲:実際に動いているところを見ると、プログラムの中であんなことをやってるなんて想像もつかないわね。
翔太:しかも、あっという間に作業が終わってるし。プログラミングって本当にすごいことをしてるんだね。
健一:そのプログラミングを、一部とはいえ君たち自身がやってるんだから、それもまたすごいことだよね。
翔太:まだ、このサンプルは終わりじゃないよね。早く続きをやろうよ。
健一:よーし、次は解読の処理だ。






<解読の処理を記述しよう>

美咲:まず、デバッグ 画面をちゃんと終了させて、と。
健一:そう、それを忘れちゃいけない。ちゃんとデバッグ 画面を終了させないと、次のコードが書き込めないからね。
翔太:終了させたよ。それで今度はどうするの?
健一:さっきと途中までは同じことをやればいい。つまり、[解読] ボタンのプロパティ ウィンドウを開いて・・・


プロパティ ウィンドウの [イベント] アイコン をクリックしてイベントの一覧を表示します。一覧にある [Click] の右にあるコンボ ボックスに「btnKaidoku_Click」と入力して [Enter] キーを押してください。





翔太:コード 画面が開いた。
健一:リスト 7-2-1 のコードが追加されたことを確認して・・・


リスト 7-2-1

        // [解読>] ボタンを押したときの処理
        private void btnKaidoku_Click(object sender, System.EventArgs e)
        {

        }


健一:次は、「private void btnKaidoku_Click〜」の次の行にある「{」と「}」の間の行にリスト 7-2-2 のコードを追加するんだ。


リスト 7-2-2

            // 暗号文を格納する変数を用意して 
            // txtAngou の Text プロパティ値で初期化する
            string Angou = txtAngou.Text;
            // [暗号文の長さ] を格納する変数を用意して
            // Genbun の Length プロパティ値で初期化する
            int Nagasa = Angou.Length;
            // 原文を格納する変数を用意して NULL 文字列で初期化する
            string Genbun = "";

            // [暗号1 の長さ] を格納する変数を用意して
            // [暗号文の長さ] を半分にした長さを代入する
            int Nagasa1 = Nagasa - (int)(Nagasa / 2);
            // [暗号2 の長さ] を格納する変数を用意して
            // [暗号文の長さ] から [暗号1 の長さ] を引いた長さを代入する
            int Nagasa2 = Nagasa - Nagasa1;
            // 暗号1 を格納する変数を用意して
            // 暗号文の 1 文字目から Nagasa1 文字目までの文字列を代入する
            string Angou1 = Angou.Substring(0, Nagasa1);
            // 暗号2 を格納する変数を用意して
            // 暗号文の Nagasa1 文字目から Nagasa2 文字目までの文字列を代入する
            string Angou2 = Angou.Substring(Nagasa1, Nagasa2);


翔太:あれ? さっきの暗号化の処理とはまた微妙に違う気がするな。
美咲:違ってる部分もそうだけど、同じ変数名が出てきてるわ。これは問題ないのかしら。
健一:うん、さっきの暗号化処理の部分とは、変数を使う範囲が異なっているからね。この場合は問題ないんだ。そもそも、このプログラム自体が、暗号化と解読を同時にやるようにはできてないからね。
翔太:ふーん。
健一:とりあえず順に見て行こうか。まず 1 行目。これはさっきも出てきた中央のテキスト ボックスの Text プロパティの値を、新しく string 型で宣言した Angou という変数に代入している。
翔太:Angou に数字をつけなくてもいいの?
健一:変数名同士で区別がつけばいいから、数字がつくものとつかないものがあってもいいんだよ。それにこの場合、txtAngou.Text の中身は、Angou1 と Angou2 を結合したものだろう? だったら、暗号全体を表わす変数ということで、あえて番号をつけない Angou という変数名を使うことで区別する、という意味もあるんだよ。
美咲:なるほど。
翔太:2 行目と 3 行目はさっきとほとんど同じだね。Nagasa っていう変数に Angou の文字数を設定してるのと、原文にあたる Genbun 変数の初期値を何もない状態にしているんだ。
健一:この場合の Genbun 変数は少し注意が必要だね。原文ということで、解読する対象、つまり暗号文だと思いがちだけど、ここは元通りの意味で、暗号化される前の本来の文、として用意された変数なんだ。
翔太:じゃあ、解読結果をここに入れるってこと?
健一:そうなるね。さて、ここまでが前半だ。後半はちょっとややこしいぞ。まず、Nagasa1 は、暗号文全体の長さを半分にした長さを代入している。これは、暗号化の際に、最初のループ文で設定した暗号の文字列の長さと同じだ。なぜなら、原文から 1 つ飛ばしで 1 文字ずつ取り出した文字列だから。計算式では、暗号文全体の長さを 2 で割った整数の値を代入しているんだけど、この (int)() というのは割り算の整数の値を出す命令になる。
翔太:それじゃ、Nagasa2 はもう 1 つのループ文の暗号の長さってこと?
健一:そう。2 つの暗号文の長さを足したものが、全体の長さになるはずだから、ここでは全体から Nagasa1 の長さを引いた値を代入している。
美咲:ちょっと待って。そうすると、残りの Angou1 は、Substring メソッドで、開始位置 0 つまり 1 文字目から Nagasa1 の値分までを代入してるから、そのままさっきのループ文で作った Angou1 ってことになるんじゃない? そうして、Angou2 は・・・
翔太:Nagasa1 の文字数の値を開始位置、ってことはその 1 つ上の数字から始まって、Nagasa2 の値分までを代入してるから、これもさっきの後半のループ文で設定した Angou2 と同じだ。
健一:そう、ここでやっているのは、さっき苦労して大元の文字列からバラバラに取出して 1 つにまとめた文字列から、処理の途中の変数の値まで引き戻す作業なんだ。さっきの ACEBDF の例で考えてみよう。文字数、つまり Nagasa は 6 文字あるから、Nagasa1 と Nagasa2 はそれぞれ 3 だ。つまり、前半 3 文字と後半 3 文字ということになる。
翔太:ということは、ここまで処理した時点で、Angou1 には前半の文字列である ACE、Angou2 には後半の文字列である BDF がそれぞれ代入されていることになるんだね。
健一:その通り。ここまでの流れは大丈夫かな? ここからさらに複雑になるから、よくおさらいした上で、次のコードを入力してみよう。


リスト 7-2-2 のあとに続けて、次のリスト 7-2-3 のコードを入力してください。


リスト 7-2-3

            // [暗号2 の文字位置] を表す変数を用意して 0 で初期化する
            int Index2 = 0;
            // [暗号1 の文字位置] を表す変数を用意して 0 で初期化し
            // [暗号1 の文字位置] が [暗号1 の長さ] - 1 になるまで繰返し処理を行う
            for (int Index1 = 0; Index1 < Nagasa1; Index1++)
            {
                // 暗号1 から [暗号1 の文字位置] にある 1 文字を取出し
                // 原文に結合させる
                Genbun = Genbun + Angou1.Substring(Index1, 1);
                // [暗号2 の文字位置] が [暗号2 の長さ] を超えていないか判断する
                if (Nagasa2 > Index2)
                {
                    // [暗号2 の長さ] より [暗号2 の文字位置] が小さいときの処理

                    // 暗号2 から [暗号2 の文字位置] にある 1 文字を取出し
                    // 原文に結合させる
                    Genbun = Genbun + Angou2.Substring(Index2, 1);
                    // [暗号2 の文字位置] を 1 つ進める
                    Index2 = Index2 + 1;
                }
            }


翔太:・・・こ、これって、もしかしてループ文と条件分岐が一緒になってるんじゃ・・・
健一:そうなんだ。ちょっとややこしいから、よく考える必要がある。
翔太:うわ・・・。大変だな、これは。
美咲:・・・よし、やってみるわね。えーっと、最初に Index2 という変数を整数で宣言して、0 を代入してるわ。Index2 はさっきのループ文で使ってた変数とは別物ってことなのよね?
健一:美咲ちゃん、気合入ってるね。そう、この場合は、Substring メソッドの範囲指定の目印に過ぎない。ちなみに、Index2 は、後半の Angou2 の方の文字列の解読を始める位置に使われている。ループ文と関係してくるのは、2 行目に出てくる Index1 の方。こちらは、前半の Angou1 の方の文字列の解読を始める位置に使われている。
翔太:それじゃ、ループ文の頭から見て行こうか。最初に、Index1 変数が int 型で宣言され、初期値として 0 が代入されている。そして、Angou1 変数の長さでもある Nagasa1 変数の値と一致するまでループが繰り返される、となってるみたいだ。今回、Index1 の増え方はどうなってるの? Index1++ ってどういう意味?
健一:これは、Index1 に 1 を追加しているんだ。式を書くと長くなるから、こうやって省略して書く書き方もあるんだよ。
翔太:へえー。便利な書き方だね。なぜ今まで使われてないの?
翔太:いきなり ++ って出てきても分かりづらいからね。今だって、変数が自分に数値を加えた値を自分自身に代入する、というやり方を見てきてるから、すぐにそういうものだということが分かったんじゃないかな? それに、この本の方針として、最初のうちは基本の書き方に慣れて欲しい、っていうこともあったのかもしれないね。いずれにせよ、今回は 1 回のループで 1 つずつ Index1 が増えていくことになるのは間違いないよ。
美咲:そして、その次の処理では、早くもその Index1 の値を使って、文字列の取出しが行われてるわ。最初のループでは、開始位置 0 からだから、Angou1 の 1 文字目から 1 文字取出して、Genbun 変数に代入してることになるのね。
健一:そう、さっきの「ABCDEF」の例で考えてみよう。暗号化によって、「ACE」と「BDF」に分かれたあとで 2 つの文字列は結合して「ACEBDF」になったんだったね。今回は、それをまた 2 つに分けたところから始まってる。このループの最初の段階では、Angou1 には「ACE」が入っていて、その 1 文字目から 1 文字分だから、「A」が取出されて、Genbun に代入されている。続けて、条件分岐の中身を見ていこう。
翔太:条件分岐の 1 行目の条件式では、暗号文の後半の長さである Nagasa2 の値が、Index2 よりも大きければ、条件を満たすことになり、その次の行が処理されるように記述されている。つまり、一番最初のループでは、Index2 は 0 のままだから、当然この条件式は満たされるよね。そして、Angou1 と同じように、Angou2 も Substing メソッドで文字列を取出されて、Genbun に結合されてるのか。でも、これだと Index2 は 0 で Index1 と同じだから、開始位置も同じになっちゃうんじゃない? 暗号化のときは Index2 は 1 から始まってたよね。
美咲:それは、暗号化のときは元々の原文から文字列を取出してたからよ。今回は、あらかじめそこから半分取出した文字列をそれぞれ相手にしてるんだから、それぞれ開始位置が 0 からでもいい、ってことよね、健一さん?
健一:そのとおり! ここは同じ名前を使っているから勘違いしやすいんだけど、暗号化のときと、解読のときでは違うものだということをよく頭に入れておく必要があるんだ。
翔太:えーっと、ということはさっきの例で言うと、「BDF」の 1 文字目から 1 文字ずつ取っていくっていうことになるのかな。
健一:そうそう、その調子!
翔太:だから、1 回目のループでは、「BDF」の 1 文字目である「B」が取出されて、Genbun に結合され・・・、あれ? 待てよ。さっきの時点で、Genbun には「A」が代入されてたんだよな。そこに「B」が結合されると・・・
美咲:「AB」だわ。すごい! ちゃんと元の文字列に近づいてる!
健一:だんだんわかってきたようだね。ところで、Genbun に文字列を代入した次の行を忘れちゃだめだよ。
翔太:「Index2 = Index2 + 1」? これは、Index2 の値を 1 つ増やしてるんだね。
健一:そう、今回、Index2 は for で始まるループ文本体で使っているわけではないから、自動的に値が増えたりしないんだ。だから、こうやって命令の中で明確に値を増やしてあげないといけないんだよ。
翔太:こうやって変数の数を増やしていくのは、ラーメンタイマーや数当てゲームでもやってきたけど、あれがここでも使われてるんだね。
健一:そう、この書き方はとても重要な書き方だって、言ったとおりだろう? さて、このループ文と条件式は、Angou1 と Angou2 の文字数分だけそれぞれ処理し終わったら、終了するようになっている。そして、終了したところで、最後にこの行を処理するんだ。


リスト 7-2-3 の最後の「Next」の次の行に、リスト 7-2-4 のコードを追加します。


リスト 7-2-4

            // 解読した原文を txtKaidoku の Text プロパティに設定する
            txtKaidoku.Text = Genbun;


美咲:これは右端のテキスト ボックスに Genbun 変数の値を表示させてるのね。
健一:そう、ここまでの解読作業で、元通りの文字列が Genbun 変数に代入されているはずなんだ。早速デバッグ ボタンをクリックして、実際の動きを確認してみよう。








翔太:暗号化された文字列がちゃんと元に戻ってるよ!
美咲:すごい・・・。なんだかここまでやるとちょっとした達成感があるわね。


このサンプルプログラムは、中央のテキスト ボックスに最初に文字を入力して [解読] ボタンを押すこともできます。例えば「あいうえお」という文字を入力して、[解読] ボタンを押したら、どのような結果になるでしょうか。





このように「あえいおう」となるのが正解です。もしここまで紹介したような結果が得られない場合は、デバッグ 画面を終了してコード エディタを開いて、イベントの名前や変数の名前などが間違っていないかよく確認してみてください。コードが正しければ、次のリスト 7-2-5 のようになっているはずです。なお、ここまでやってきた作業には、すべて意味があります。ただ単にこのコードをそのままコピーして貼り付けても、正常に動作しない場合がありますので、わからなくなったら、最初からやり直してみてください。


リスト 7-2-5

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Part7
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // [暗号化>] ボタンを押したときの処理
        private void btnAngou_Click(object sender, System.EventArgs e)
        {
            // 原文を格納する変数を用意して
            // txtGenbun の Text プロパティ値で初期化する
            string Genbun = txtGenbun.Text;
            // 原文の長さを格納する変数を用意して
            // Genbun の Length プロパティ値で初期化する
            int Nagasa = Genbun.Length;
            // 暗号1 を格納する変数を用意して NULL 文字列で初期化する
            string Angou1 = "";
            // 暗号2 を格納する変数を用意して NULL 文字列で初期化する
            string Angou2 = "";

            // [暗号1 の文字位置] を表す変数を用意して 0 で初期化し
            // [暗号1 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index1 = 0; Index1 < Nagasa; Index1 = Index1 + 2)
            {
                // 原文から [暗号1 の文字位置] にある 1 文字を取出し
                // 暗号1 に結合させる
                Angou1 = Angou1 + Genbun.Substring(Index1, 1);
            }
            // [暗号2 の文字位置] を表す変数を用意して 1 で初期化し
            // [暗号2 の文字位置] が [原文の長さ] - 1 になるまで
            // 2 足して行き、繰返し処理を行う
            for (int Index2 = 1; Index2 < Nagasa; Index2 = Index2 + 2)
            {
                // 原文から [暗号2 の文字位置] にある 1 文字を取出し
                // 暗号2 に結合させる
                Angou2 = Angou2 + Genbun.Substring(Index2, 1);
            }

            // 暗号1 と暗号2 を結合したものを txtAngou の Text プロパティに設定する
            txtAngou.Text = Angou1 + Angou2;
        }

        // [解読>] ボタンを押したときの処理
        private void btnKaidoku_Click(object sender, System.EventArgs e)
        {
            // 暗号文を格納する変数を用意して 
            // txtAngou の Text プロパティ値で初期化する
            string Angou = txtAngou.Text;
            // [暗号文の長さ] を格納する変数を用意して
            // Genbun の Length プロパティ値で初期化する
            int Nagasa = Angou.Length;
            // 原文を格納する変数を用意して NULL 文字列で初期化する
            string Genbun = "";

            // [暗号1 の長さ] を格納する変数を用意して
            // [暗号文の長さ] を半分にした長さを代入する
            int Nagasa1 = Nagasa - (int)(Nagasa / 2);
            // [暗号2 の長さ] を格納する変数を用意して
            // [暗号文の長さ] から [暗号1 の長さ] を引いた長さを代入する
            int Nagasa2 = Nagasa - Nagasa1;
            // 暗号1 を格納する変数を用意して
            // 暗号文の 1 文字目から Nagasa1 文字目までの文字列を代入する
            string Angou1 = Angou.Substring(0, Nagasa1);
            // 暗号2 を格納する変数を用意して
            // 暗号文の Nagasa1 文字目から Nagasa2 文字目までの文字列を代入する
            string Angou2 = Angou.Substring(Nagasa1, Nagasa2);

            // [暗号2 の文字位置] を表す変数を用意して 0 で初期化する
            int Index2 = 0;
            // [暗号1 の文字位置] を表す変数を用意して 0 で初期化し
            // [暗号1 の文字位置] が [暗号1 の長さ] - 1 になるまで繰返し処理を行う
            for (int Index1 = 0; Index1 < Nagasa1; Index1++)
            {
                // 暗号1 から [暗号1 の文字位置] にある 1 文字を取出し
                // 原文に結合させる
                Genbun = Genbun + Angou1.Substring(Index1, 1);
                // [暗号2 の文字位置] が [暗号2 の長さ] を超えていないか判断する
                if (Nagasa2 > Index2)
                {
                    // [暗号2 の長さ] より [暗号2 の文字位置] が小さいときの処理

                    // 暗号2 から [暗号2 の文字位置] にある 1 文字を取出し
                    // 原文に結合させる
                    Genbun = Genbun + Angou2.Substring(Index2, 1);
                    // [暗号2 の文字位置] を 1 つ進める
                    Index2 = Index2 + 1;
                }
            }
            // 解読した原文を txtKaidoku の Text プロパティに設定する
            txtKaidoku.Text = Genbun;
        }
    }
}


健一:いやあ、どうやら無事に最後までたどりつけたね。
翔太:うん、やっぱり健一さんにそばにいてもらってよかったよ。でないと、大変なことになってたかもしれない。
健一:おいおい、僕もまだまだ勉強中の身だってことを忘れないでくれよ。この本はあくまで初心者向けの入門書にすぎないんだ。だから、僕でもこうして君たちにいろいろ教えることができたんだよ。実際、君たちと一緒に勉強を進めたことで、自分自身の復習にもなったしね。
美咲:でも、健一さんがいなかったら、わけもわからずにただコードを書いて終わってたと思うわ。
健一:それでも、サンプルがちゃんと動いてくれれば、それはそれでいいんだよ。この本の目的は、書いてある内容について知識を深めてもらうことじゃなくて、プロラグミングを体験してもらうことなんだから。その意味では、十分すぎるほど効果があったみたいだね。
翔太:うん、とても楽しかったよ。あとは第 8 章が残ってるけど、最後なのが名残惜しいなぁ。
健一:第 8 章は、今までの応用編になってる。だから、体験を基本にしてるのは変わりはないんだけど、今までよりコードを書く量が多いし、出てくる技術も広範囲にわたってる。その代わり、今までの中では一番しっかりしたプログラムができあがるのは間違いない。どうする? 続けてやるかい?
翔太:うーん、やりたい気持ちはあるんだけど、さすがにちょっと頭が一杯一杯になってきちゃったな。
美咲:それじゃ、一休みしてからにしましょうか。
健一:だったら、出張のお土産にお菓子を買ってきたから、それでお茶にしよう。
翔太:うん! それじゃまずは休憩ってことで。そのあとで、最後の第 8 章に挑戦することにしようよ。


いかがでしたか? 第 7 章は、ループ文という新しい命令方法を使って、文字列の組み換えを行ってみました。プログラム自体は、それほどたいしたことをやっているように見えないかもしれませんが、実はその裏側では大変な作業を行っていることがわかってもらえたでしょうか。

さあ、次はいよいよ最後の第 8 章です。第 8 章は、これまで見てきた内容を応用して、月着陸ゲームを作っていきます。健一さんが言っていたように、今までのサンプルの中では一番よくできたプログラムになりますので、皆さんも期待していてください。ただし、その分手間は今まで以上にかかるかもしれません。でも、ここまで読み進めてきた皆さんならきっと大丈夫でしょう。

それでは、翔太君たち同様、一休みしてから、第 8 章にチャレンジしてみてください。


Top of Page Top of Page Top Page プログラミング チャレンジ スクエアへ戻る


Microsoft