(前頁)<<      >>(次頁)          (Up)

2章 準備勉強
2.1 FORTRANプログラムとC#プログラムの相違 (その2)



 前頁で作成したプログラムを使用し、次のような処理を行なってみます。

処理@  値:2で初期化 → 1.0を10回 加算する。
処理A  値:5で初期化 → 1.0を3回 加算する。

 通常に考えると 処理@を行った後に 処理Aを行います。

       // メインプログラム
        static void Main(string[] args)
        {
            Sub01(2.0);  // 値2で初期化
            Sub02(10);  // 10回加算
            Sub03();  // 結果を表示する

            Sub01(5.0);  // 値5で初期化
            Sub02(3);  // 3回加算
            Sub03();  // 結果を表示する
        }


 計算結果は下記の通りになります。
 


 2つの処理@、Aを行うためには、処理@が終了した後に処理Aを行う必要があります。

 しかし、処理@、Aを同時に解くことはできないでしょうか? 例えば、初期化を続けて2回行い、次に加算を続けて2回行うとどうなるでしょうか?

       // メインプログラム
        static void Main(string[] args)
        {
            Sub01(2.0);  // 値2で初期化
            Sub01(5.0);  // 値5で初期化
            Sub02(10);  // 10回加算
            Sub02(3);  // 3回加算
            Sub03();  // 結果を表示する
            Sub03();  // 結果を表示する
        }


 計算結果は当然のことながら下記のように 最初の処理とは大きく異なります。
 
 

 当たり前と思われますが、結果が異なることを敢えて考えてみます。

     
 サブルーチン Sub01, Sub02, Sub03 は 同じCommonData内の変数:Xにアクセスしています。
 このため、Sub01を2回コールすると 変数:Xは 最初に設定された値が後から指定した値に上書きされてしまいます。
 それでは、どのようにすれば、2つの処理を同時に行えるようになるでしょうか?

 
 真っ先に思い浮かべられるのは 変数Xを配列化してしまうことです。
 すなわち
// 改修前
    class CommonData
    {
        public double X;
    }

  ↓

// 改修後
    class CommonData
    {
        public double X[2];    // DIMENSION  X(2) と同じ
    }
とすることです。

    

 このように変数を複数化した後 Sub01, Sub02, Sub03 を 配列に対応した処理に改良します。



 ところで、前頁で 意味あるデータを纏めて クラス「CommonData」を制作してきました。
 そこで、第2の方法として 下記のようにクラス単位で メモリー領域を2つ確保します。
    


   // CommonData という領域の構造を定義する
    class CommonData
    {
        public double X;
    }

    class Program
    {
        // CommonDataというメモリー領域を確保する
        CommonData CmnDt1 = new CommonData();
        CommonData CmnDt2 = new CommonData();
      .........................


 次に Sub01, Sub02, Sub03 を 複数のCommonDataに対応した処理に改良します。
 しかしながら、この方法でも Sub01, Sub02, Sub03 を修正することには変わりありません。
 


 ところが、オブジェクト指向の考え方を取り入れると、もっと簡単な方法で変更ができます。

 前節では、データの纏まりを 「クラス」として宣言しました。 しかし、クラスにはデータを纏める以外に、自分の中に持っているデータを扱う関数を定義できるという性質があります。(この関数を 「メンバー関数」、または「メソッド」と呼びます) そこで、クラス CommonDataを扱うメンバー関数 Sub01, Sub02, Sub03 を クラス「CommonData」内部に実装します。

    // CommonData という領域の構造を定義する
    class CommonData
    {
        public double X;
       
        // サブルーチン01= メソッド
        public void Sub01(double dX)
        {
            X = dX;
        }
        // サブルーチン02= メソッド
        public void Sub02(long LoopNum)
        {
            long lCnt;
            for (lCnt = 0; lCnt < LoopNum; lCnt = lCnt + 1)
            {
                X = X + 1.0;
            }
        }
        // サブルーチン03= メソッド
        public void Sub03()
        {
            Console.WriteLine(X);
        }
    }

 メンバー関数 Sub01, Sub02, Sub03 は 同じクラス内部の変数:X(メンバー変数)に直接アクセスできます。

 そして、メンバー関数をコールすることで、内部のメンバー変数に所定の処理を行います。

        // メインプログラム
        static void Main(string[] args)
        {
            // CommonDataというメモリー領域を確保する
            CommonData CmnDt1 = new CommonData();
            CommonData CmnDt2 = new CommonData();

            // 各領域に対してそれぞれ処理を行う。(メンバー関数のコール)
            CmnDt1.Sub01(2.0);  // 値2で初期化(領域1)
            CmnDt2.Sub01(5.0);  // 値5で初期化(領域2)
            CmnDt1.Sub02(10);   // 10回加算(領域1)
            CmnDt2.Sub02(3);    // 3回加算(領域2)
            CmnDt1.Sub03();     // 結果を表示する(領域1)
            CmnDt2.Sub03();     // 結果を表示する(領域2)
        }


 この様に、クラスにメンバー関数を定義すると、メンバー関数を利用して それぞれ確保し領域に独立に処理を行うことが可能となります。 確保した領域を 「インスタンス」と呼びます。
 一度クラスを作成すると、 インスタンスが 3つに増えても クラスのメンバー関数を改造する必要はなくなります。
           // CommonDataというメモリー領域を確保する
            CommonData CmnDt3 = new CommonData();      

            // 領域3に対してそれぞれ処理を行う。
            CmnDt3.Sub01(3.0);  // 値3で初期化(領域3)
            CmnDt3.Sub02(2);   // 2回加算(領域3)
            CmnDt3.Sub03();     // 結果を表示する(領域3)

 
  これを、プログラムのメモリー管理の状態から考えてみます。

      

 FORTRANの場合には 記憶領域と処理は独立しているので、各処理(サブルーチン)はどの領域を参照しているかを認識した処理としなければなりません。 この為、記憶領域の構造や個数等が変化すると それに関連するすべてのサブルーチンに変更が発生します。



 一方、C#(オブジェクト指向)の場合には、記憶領域と一体化したメンバー関数が定義されています。 
       

  従って記憶領域の個数が変化しても、メンバー関数自身の変更は必要ありません。
 
      

 メインプログラムからメンバー関数(メソッド)をコールする時には、そのメンバー関数が処理を行うべき対象の記憶領域(インスタンス)が確保されていなければなりません。 

  CommonData CmnDt3 = new CommonData();  // 領域確保

とコーディングして、記憶領域:「CmnDt3」 を確保(インスタンスを生成)してから、その記憶領域「CmnDt3」に対して

  CmnDt3.Sub01(3.0);     // 値3で初期化(領域3)

の様にメンバー関数(メソッド)をコールします。
          (注意) // は それ以後の記述内容がコメントである事を示します(1行単位)



 ところで、Sub01, Sub02, Sub03 を クラス:CommonData のメンバー関数(メソッド)とする時に static という宣言が無くなりました。 メソッドに付与した static 宣言は特殊な宣言で、 処理を行う対象領域が確保(インスタンスが生成)されていなくても呼び出すことができるという事を示しています。 static 宣言されたメソッド内で何らかの記憶領域を扱う必要が有る場合には、その記憶領域は FORTRANの場合と同じように プログラム開始時に確保されていなければなりません。このため、前節で示したサンプルプログラムでは

      static CommonData CmnDt = new CommonData();

の様に 記憶領域にも、static 宣言を行なっていました。

 しかしながら、クラス(記憶領域)の「メンバー関数」を定義した事で、その「メンバー関数」は必ず「生成されたインスタンス(記憶領域)」に対して処理を行う事になった為、 static の宣言は外します。




 ここで、これまで説明を後回しにしてきた namespace, class, public,private 等の用語を整理します。 class(クラス)が データの一纏まりであることは説明しました。 クラスは「メンバー変数」と「メンバー関数」を持ちます。
 public 宣言した「メンバー変数」、「メンバー関数」には他のプログラムやクラスからアクセスできますが、private 宣言した「メンバー変数」、「メンバー関数」は、アクセスできません。(コンパイルでエラーになります)
 例えば、下図の様な ”兄”というクラスを作成した時、private なメンバー変数 ”日記”、メンバー関数 ”寝る()” は アクセスすることができません。 しかしながら、public 宣言した”模型”、”小説” 及び ”食べる()”、”働く()”は アクセスすることができます。

       

 次に namespace ですが、下のような喩えで説明します。
 上図の”兄”クラスを 偶々、複数の人が作成したとします。 同じクラス名の存在は許されませんが、namespace が別名であれば、同じクラス名でも区別ができます。つまり、namespace は 名字(姓)の様なものです。 例えば、名字付きで ”山田家の兄”、”川口家の兄”とすれば、それを呼び出す側で区別できます。

     

 namespace 内のクラスには、 public , internal の宣言ができます。(宣言省略時には internal 宣言と同じ)
 public 宣言されたクラスは、別のDLL内のプログラムからアクセス可能ですが、internal 宣言されているクラスは別のDLL からは(例え同じnamespaceであっても)アクセスできません。

 
       
     ((注) namespace 説明用プロジェクト:NSTestMain1.lzh   解凍後 NSTestMain1\NSTestMain1.slnを開いて下さい。)
 




 通常、クラスのメンバー変数はpublic にせずに、public にしたメンバー関数(メソッド)を通じて間接的にアクセスします。 つまり他人はpublic にされたメンバー関数を通さなければ、クラスのメンバー変数を変更できません。 このようにすると、クラス内部のメンバー変数に変更があった場合でも外部からはその変更を認識する必要がありません。 つまり、クラスがメンバー関数内の処理を変更するだけで 他のプログラムには影響を与えないようにできます。(変更範囲の局所化)
 これは、オブジェクト指向の大きな概念で「情報の隠蔽」と言います。

 「情報(メンバー変数)」と その情報の「処理(メンバー関数)」を纏めたものが「オブジェクト」です。    

(オブジェクト)=(情報)+(処理)


 オブジェクト指向とは プログラム全体を上記の思想で制作しようというものです。

 プログラムを作成する場合には、何を一つのオブジェクトと見なすか(オブジェクトの発見)が重要で オブジェクトの定義の方法によって プログラム構造は大きく変化します。 
 class でオブジェクトを定義し、new を用いて実体化(メモリー領域を確保=インスタンスを生成)することで、処理を行うことができます。



 FORTRANのサンプルプログラムをC#でオブジェクト指向化したプログラムを下記に示します。

namespace TEST
{
    // CommonData という記憶領域の構造を定義する
    class CommonData
    {
        // メンバー変数
        public double X;
       
        // サブルーチン01= メンバー関数(メソッド)
        public void Sub01(double dX)
        {
            X = dX;
        }
        // サブルーチン02= メンバー関数(メソッド)
        public void Sub02(long LoopNum)
        {
            long lCnt;
            for (lCnt = 0; lCnt < LoopNum; lCnt = lCnt + 1)
            {
                X = X + 1.0;
            }
        }
        // サブルーチン03= メンバー関数(メソッド)
        public void Sub03()
        {
            Console.WriteLine(X);
        }
    }

    class Program
    {
        // メインプログラム
        static void Main(string[] args)
        {
            // CommonDataというメモリー領域を2箇所 確保する
            CommonData CmnDt1 = new CommonData();
            CommonData CmnDt2 = new CommonData();

            // 各領域に対してそれぞれ処理を行う。(メンバー関数をコール)
            CmnDt1.Sub01(2.0);  // 値2で初期化(領域1に対して)
            CmnDt2.Sub01(5.0);  // 値5で初期化(領域2に対して)
            CmnDt1.Sub02(10);   // 10回加算(領域1に対して)
            CmnDt2.Sub02(3);    // 3回加算(領域2に対して)
            CmnDt1.Sub03();     // 結果を表示する(領域1に対して)
            CmnDt2.Sub03();     // 結果を表示する(領域2に対して)
        }

    }
}

    プロジェクト: CSSTest03.lzh

 サンプルでは、Microsoft Visual C# で最初にプロジェクトを生成した時に class Program が自動で生成されました。 Program も class 宣言してあり 一つのオブジェクトです。
 namespace TEST に属する Program は、同じ namespace 「TEST」 に属する クラス:CommonDataの public メソッドにアクセス可能です。


 ところで、クラス:CommonData は Program.cs ファイル内に記述されていますが、別なファイルに記述することも可能です。 そこで、CommonDataを別なファイルへ移動させます。 
 先ず、プロジェクトを右クリックし、ポップアップメニューの「追加」−「クラス」を選択します。

  

 現れたダイアローグで「CommonData」を追加します。


     

 すると、CommonDataクラスが自動で追加されます。

      

 ここで、クラス  CommonData 内に処理を実装します。
 同一ネームスペース内に同じ名前のクラスは許されないので、これまでファイル Program.cs 内に記述されていた クラス CommonData の内容をこのファイルに カット&ペーストします。(Program.cs ファイル内に記述されていた class CommonData は完全に削除します)


 通常 1クラスに1ファイルを割り当てます。
 制作したプロジェクトを CSSTest04.lzh  に置きます。     

 オブジェクト指向のプログラムに少し慣れた所で 次頁からは水理水文のFORTRANモデルをC#に移植してみます。


(前頁)<<      >>(次頁)

初版:2013/06/12 | 修正第2版:2013/07/26