プログラミングと開発方法論と情報セキュリティ(主に初心者向け、言語はC#)、その他、ゲーム、思想等。

開発方法論萌え

C# 応用

C#応用編第1回 – クラスの前提事項

更新日:


基本編がまだ途中ですが、並行して実践編を始めます。

今回はオブジェクト指向の3大要素と言われるカプセル化についてです。

また、クラスの説明も兼ねることになります。

よくオブジェクト指向の機能として次の3つが挙げられます。一応、覚えておきましょう。

  • カプセル化
  • インヘリタンス(継承)
  • ポリモーフィズム(多様性)

今回、C#言語を使って解説しますが、おそらく現環境で実用されているほとんどの言語に当てはまるお話です。

当面の目標

ここ何回分か講義を使って、カプセル化の説明のために動的配列を自作することに挑戦してみます。

C#では配列のサイズを1度決めてしまうと変更できないのでした。

そこで、「動的配列」を使います。動的配列の場合、必要に応じて配列のサイズが拡張されてくれます。

サンプルを見てみます。

これは特に説明の必要はないと思います。


using System;
using System.Collections.Generic;

class Program
{
  static void Main()
  {
    List<int> list = new List<int>();
    Console.WriteLine("好きな数だけ数値を入力してください。");
    while (true)
    {
      string str = Console.ReadLine();
      if (str == "")
        break;

      list.Add(int.Parse(str));
    }

    int sum = 0;
    foreach (int a in list)
    {
      sum += a;
    }

    Console.WriteLine("合計は {0} になりました。", sum);
  }
}

実行結果は次の通りです。


好きな数だけ数値を入力してください。
1
2
3

合計は 6 になりました。

Fill() 関数を作る

次に、復習も兼ねて配列の要素を特定の値で埋めてしまう関数 Fill() を作ってみます。

class Program {...} の中にこのように書けば完成です。


static void Fill(int[] array, int a)
{
  for (int i = 0; i < array.Length; i++)
  {
    array[i] = a;
  }
}

ここで重要なのは、関数 Fill() は戻り値を返していないことです。

例えば次のように呼び出した場合の処理を考えてみます。


int[] hoge = new int[10];
Fill(hoge, 1);

呼び出し元の変数 hoge に int の配列の参照(C言語で言うところのポインタ)が代入されます。

次に Fill() を呼び出した時に呼び出し元の hoge(実引数)が関数側の array(仮引数)に代入されるのでした。

この関数を呼び出すときの引数の代入も、代入演算子(=)を使用した時の動作とまったく同じです。

例えば、次のようにすると、array と hoge はメモリ上の同じアドレス(座標)の配列を指すことになるので、array を通じて hoge を書き換えることができるのでした。


static void Main()
{
  int[] hoge = new int[10];
  int[] array = hoge;

  for (int i = 0; i < array.Length; i++)
  {
    array[i] = 1;
  }
}

これは関数をまたいだ場合でも全く同じで、関数側の array(仮引数)を通した操作は、hoge の差す先のアドレス(座標)にある配列に対する操作と同じものになります。

そのため、処理結果を戻り値として返さなくても、Fill() 関数が終わった後の、hoge の差す先のアドレス(座標)にある配列の要素には、全部 1 が代入されているのです。

やや冗長な説明になりました。非常にややこしく感じるかもしれませんが、この先に進むうえで必要なので、この動作は完璧に理解しましょう。

Add() 関数を作る

次に、配列に要素を追加する関数 Add() を作ってみます。

これはかなりややこしくなります。既に述べた通りC#では配列の要素数を拡張できません。

そのため、配列に要素を追加するのであれば、既存の要素数+1の要素数の配列を新規に作成し、既存の要素を全部複製した上で、新規の要素に値を代入する必要があります。

ここで、わざと誤りのコードを書いてみます。


static void Add(int[] array, int a)
{
  // 既にある配列+1の要素を持つ配列を作成する。
  int[] newArray = new int[array.Length + 1];

  // 既にある要素を複製する。
  for (int i = 0; i < array.Length; i++)
  {
    newArray[i] = array[i];
  }

  // 新規の要素を追加する。
  newArray[array.Length] = a;

  array = newArray;
}

何処が誤りか分かりますでしょうか?

これでは、Add() 関数の呼び出し元である hoge には何の影響も発生しません。

既に説明した通り、関数側の array(仮引数)を通した操作は、hoge の差す先のアドレス(座標)にある配列に対する操作と同じものになります。

array = newArray; により、array の差す先のアドレス(座標)が変更されています。

しかし、array の指す先のアドレスを変更してもこの影響は、呼び出し元には影響しないのです。

酷く矛盾しているようですが、ここが理解できないと先に進めないので、もし分からない方はコメントをください。その際は、手書きで図を付けます。

ここはC#以外の言語を使っていても付いて回る疑問点なので、完璧に理解しましょう。

正しいコードは次のようになります。戻り値として返せばよいのです。(赤字の部分に注目。)


static int[] Add(int[] array, int a)
{
  // 既にある配列+1の要素を持つ配列を作成する。
  int[] newArray = new int[array.Length + 1];

  // 既にある要素を複製する。
  for (int i = 0; i < array.Length; i++)
  {
    newArray[i] = array[i];
  }

  // 新規の要素を追加する。
  newArray[array.Length] = a;

  return array;
}

この関数を利用する側のコードは次のようになります。(赤字の部分に注目。)


static void Main(string[] args)
{
  int[] hoge = new int[0];
  Console.WriteLine("好きな数だけ数値を入力してください。");
  while (true)
  {
    string str = Console.ReadLine();
    if (str == "")
      break;

    hoge = Add(hoge, int.Parse(str));
  }

  int sum = 0;
  foreach (int a in hoge)
  {
    sum += a;
  }

  Console.WriteLine("合計は {0} になりました。", sum);
}

今回は以上です。

次回から、いよいよクラスを導入します。

既に、述べた通り、ここが分からなければ後々確実に響いてきます。完璧に理解しましょう。必要であれば図を書くのでコメントください。


管理人が学習に利用した書籍

管理人が学習に利用した書籍を紹介させていただきます。

私がお勧めしたいのは独習C#第3版です。独習C#新版と言うのもありますが、第3版とは異なる著者が書いた本です。

Amazon さん

楽天さん

 

-C#, 応用

Copyright© 開発方法論萌え , 2020 All Rights Reserved Powered by STINGER.