講義メモ ・p.223「第9章 継承」から 提出フォロー p.222 練習問題2 ヒント ex0802.cs
・p.217 indexer03.csをベースにすると良い ・コンストラクタで生徒数を入力する部分は、p.214 indexer02.csが参考になる ・生徒名を格納するstring型配列と、点数を格納するint型配列を宣言して用いると楽 ※ 生徒名と点数をメンバとするクラスオブジェクトの配列にできればさらに良い ・要素の生成はコンストラクタで行うと良い ・Mainメソッドで格納結果を表示すること作成例 //p.222 練習問題2 ex0802.cs using System; class MyIndexer { int[] point; //点数の配列の宣言 string[] name; //名前の配列の宣言 int num; //要素数(メソッドで設定) public int this[string Name] { //文字列をインデックスとするインデクサ① get { for (int i = 0; i < num; i++) { //配列の全要素について if (Name == name[i]) { //名前と一致? return point[i]; //点数を返す } } return -1; //一つも一致しなかったら-1を返す(0点だとあり得るので) } set { for (int i = 0; i < num; i++) { //配列の全要素について if (Name == name[i]) { //名前と一致? point[i] = value; //点数を格納 } } } } public string this[int i] { //添字をインデックスとするインデクサ② get { return name[i]; //名前[i]を返す } set { name[i] = value; //名前[i]に格納 } } public MyIndexer(int i) { //要素数を受け取るコンストラクタ point = new int[i]; //引数で受け取った要素数で点数の配列本体を生成 name = new string[i]; //引数で受け取った要素数で名前の配列本体を生成 num = i; //引数で受け取った要素数をデータメンバに格納 } } class indexer03 { public static void Main() { const int N = 3; //要素数 MyIndexer mi = new MyIndexer(N); //要素数Nで生成 mi[0] = "マチュ"; //インデクサ②のsetを呼ぶ mi[1] = "ニャアン"; //インデクサ②のsetを呼ぶ mi[2] = "ハロ"; //インデクサ②のsetを呼ぶ mi["マチュ"] = 60; //インデクサ①のsetを呼ぶ mi["ニャアン"] = 70; //インデクサ①のsetを呼ぶ mi["ハロ"] = 80; //インデクサ①のsetを呼ぶ for (int i = 0; i < N; i++) { //全要素について繰返す string name = mi[i]; //インデクサ②のgetを呼ぶ int point = mi[name]; //インデクサ①のgetを呼ぶ Console.WriteLine("{0} : {1}点", name, point); //インデクサ①②の組合せで名前と点数を得る } } } 第9章 継承 p.223 9.1 クラスの継承の基礎 ・クラスは乱立しやすく、同じことが複数のクラスに記述されてしまったり、適切なクラスを探しづらい ・そこで、クラスに親子関係を持たせるのが継承 ・親になるクラスを基本クラス、子になるクラスを派生クラスといい、派生クラスは基本クラスを継承するという ・派生クラスの書式: class 派生クラス名 : 基本クラス名 {…} ・例: class HoimiSlime : Slime {…} ・C#では派生クラスの基本クラスは1つのみで、基本クラスの派生クラスはいくつでもOK(単一継承という) ・基本クラスのpublicメンバ定義はすべて派生クラスに引き継がれる(継承される) ・基本クラスを書き換えると、自動的に反映するので「同じことが複数のクラスに記述される」ことが防止できる ・例: class Slime { public int hp; } ・例: class HoimiSlime : Slime { public int mp; } //hpは記述しなくてもSlimeから継承される ・継承のことをインヘリタンスともいう アレンジ演習:p.224 inheritance01.cs ・派生クラスの中に継承されている基本クラスのメンバをコメントで示そう 作成例 //アレンジ演習:p.224 inheritance01.cs using System; //基本クラス class MyBase { public int a = 10; //インスタンス変数 public void BaseMethod() { //インスタンスメソッド Console.WriteLine("ここは基本クラスです"); } } //派生クラス class MyDerived : MyBase { //MyBaseを基本クラスとする派生クラス //public int a = 10; //インスタンス変数が継承される public int b = 20; //インスタンス変数 public void DerivedMethod() { //インスタンスメソッド Console.WriteLine("ここは派生クラスです"); } // public void BaseMethod() { //インスタンスメソッドが継承される // Console.WriteLine("ここは基本クラスです"); // } } class inheritance01 { public static void Main() { MyDerived md = new MyDerived(); //派生クラスのオブジェクトを生成 md.BaseMethod(); //派生クラスが継承した基本クラスのメソッドを呼ぶ md.DerivedMethod(); //派生クラスのメソッドを呼ぶ Console.WriteLine("md.a = {0}", md.a); //派生クラスが継承した基本クラスのデータ Console.WriteLine("md.b = {0}", md.b); //派生クラスのデータ MyBase mb = new MyBase(); //基本クラス mb.BaseMethod(); //基本クラスのメソッドを呼ぶ Console.WriteLine("mb.a = {0}", mb.a); //基本クラスのデータ } } p.226 protectedによるデータの保護 ・基本クラスにおけるメンバの継承可をアクセス修飾子のpublicで行うと、外部から自由に利用できてしまい良くない ・そこで、アクセス権限はprivateだが、継承は可であるメンバはprotected指定にすると良い アレンジ演習:p.226 inheritance02.cs ・Mainで「md.x = 30;」とするとエラーになることを確認しよう 作成例 //アレンジ演習:p.226 inheritance02.cs using System; class Base { //基本クラス protected int x = 20; //外部からは直接利用不可だが、継承は可能 } class Derived : Base { //派生クラス //protected int x = 20; //ここにあるとみなす(外部からは直接利用不可だが、継承は可能) int y = 10; public void ShowXY() { // このクラスからはBaseクラスのxは見える Console.WriteLine("x = {0}, y = {1}", x, y); } } class inheritance02 { public static void Main() { Derived md = new Derived(); // md.x = 30; //protected指定なので外部からはアクセスできない md.ShowXY(); } } p.228 名前の隠蔽 ・派生クラスで基本クラスと同じ名前のデータメンバや、シグニチャ(名前と引数の型・数・並び)が同じメソッドを記述できる ・すると、名前の隠蔽が起こり、派生クラスにおける定義が優先される(見かけ上、上書きになる) ・名前の隠蔽が起こっていることを派生クラスのメンバの定義の先頭に「new」を記述することで明示できる(明示が推奨される) アレンジ演習:inheritance03.cs ・Mainメソッドで基本クラスのオブジェクトを生成し、名前の隠蔽がされているメンバが利用可能なことを確認しよう 作成例 //アレンジ演習:p.228 inheritance03.cs using System; class Base { //基本クラス public int x = 10; //継承により隠ぺいされるインスタンス変数 public void BaseMethod() { //継承により隠ぺいされるインスタンスメソッド Console.WriteLine("Baseクラスです"); } } class Derived : Base { // ここに「public int x = 10;」が継承されるが、次の定義で名前の隠ぺいになる new public int x = 20; //名前の隠ぺいになるインスタンス変数の定義 new public void BaseMethod() { //名前の隠ぺいになるメソッドの定義 Console.WriteLine("Derivedクラスです"); } } class inheritance03 { public static void Main() { Derived d = new Derived(); Console.WriteLine("x = {0}", d.x); //名前を隠ぺいしている側のインスタンス変数 d.BaseMethod(); //同上のインスタンスメソッド //【以下追加】 Base b = new Base(); //基本クラス Console.WriteLine("x = {0}", b.x); //名前を隠ぺいされている側のインスタンス変数 b.BaseMethod(); //同上のインスタンスメソッド } } p.229 名前の隠蔽:baseキーワード ・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができる アレンジ演習:p.228 inheritance04.cs ・「base.」を記述することで、隠蔽されたメンバを、派生クラスの中で利用することができるが、 隠蔽されたデータメンバの値を、派生クラスの中で書き換えるとどうなるか確認しよう 作成例 //アレンジ演習:p.229 inheritance04.cs using System; class Base { //基本クラス protected int x = 10; //継承可能なインスタンス変数(外部からの利用は不可) } class Derived : Base { // protected int x = 10; //インスタンス変数が継承されるが、直後に隠ぺいされる new int x = 20; //名前の隠ぺいになるインスタンス変数の定義 public void Show() { //派生クラスの自前のメソッド base.x = 30; //【追加】継承したデータメンバの値を書き換える Console.WriteLine("base.x = {0}, x = {1} ", base.x, x); //隠ぺいされた基本クラスの変数値 } } class inheritance04 { public static void Main() { Derived d = new Derived(); //派生クラスのインスタンスを生成 d.Show(); //派生クラスの自前のメソッドを呼ぶ(値の書き換えがされている) } } p.230 メソッドのオーバーライド ・メソッドにおいては名前の隠蔽とオーバーライドの2パターンから選ぶことができる ・オーバーライドを選ぶと、p.232で後述する多態性(ポリモフィズム)が実現し、オブジェクトの扱い方を効率化できるので、 一般にメソッドにおいては名前の隠蔽ではなくオーバーライドを用いることが多い ・ただし、オーバーライドにおいては、基本クラス側でオーバーライドを許可する必要がある ・基本クラス側でオーバーライドを許可するにはメソッドに「virtual」をつけて仮想メソッドにする ・派生クラス側でオーバーライドするメソッドには「override」をつけてオーバーライドメソッドにする アレンジ演習:p.213 override01.cs ・Mainメソッドで基本クラスのオブジェクトを生成し、オーバーライドされているvirtualメソッドが利用可能かどうか確認しよう 作成例 //アレンジ演習:p.231 override01.cs using System; class MyBase { //基本クラス public virtual void Method() { //オーバーライドを許可する仮想メソッド Console.WriteLine("MyBase"); } } class Derived1 : MyBase { //派生クラス① //ここに「public virtual void Method(){…}」があるが、下記にオーバーライドされる public override void Method() { //オーバーライドメソッド① Console.WriteLine("Derived1"); } } class Derived2 : MyBase { //派生クラス② //ここに「public virtual void Method(){…}」があるが、下記にオーバーライドされる public override void Method() { //オーバーライドメソッド② Console.WriteLine("Derived2"); } } class override01 { public static void Main() { Derived1 d1 = new Derived1(); //派生クラス① Derived2 d2 = new Derived2(); //派生クラス② d1.Method(); //オーバーライドメソッド①を呼ぶ d2.Method(); //オーバーライドメソッド②を呼ぶ //【以下追加】 MyBase m1 = new MyBase(); //基本クラス m1.Method(); //Virtualメソッドを呼ぶ } } p.232 多態性 ・クラスの継承は基本クラスを代表とする派生クラスのグルーピングにもなる ・例: Slimeクラスを継承するHoimiSlimeクラス、MetalSlimeクラスは「スライムの一族」 ・よって、派生クラスのオブジェクトは、基本クラスを型とする変数に代入できる ・例: HoimiSlimeクラスのホイミンは、Slimeクラスのホイミンとしても扱える(メンバを引き継いでいるので) ・このとき、メソッドにおいて名前の隠蔽が起こっていると、基本クラスを型とする変数でメソッドを呼ぶと、基本クラスのメソッドが実行される ・しかし、メソッドにおいてオーバーライドが起こっていると、基本クラスを型とする変数でメソッドを呼んでも、派生クラスのオーバーライドメソッドが実行される ・このことを多態性(ポリモフィズム)という ・例: Slimeクラスの仮想メソッドはHPのみ表示、継承するHoimiSlimeクラスのオーバライドメソッドはHPとMPを表示する場合: HoimiSlimeクラスのホイミンは、Slimeクラスのホイミンとして扱っても、メソッドを呼ぶとオーバライドメソッドが動作してHPとMPを表示 ・この「どのメソッドが実行されるかが実行時に決まる仕組み」を動的メソッドディスパッチャという ・例: class Slime { //基本クラス int hp; //基本クラスのインスタンス変数メンバ public virtual void show() { //仮想メソッド Console.Write(hp); } } class HoimiSlime : Slime { //派生クラス // ここに「int hp; //基本クラスのインスタンス変数メンバ」があるとみなされる int mp; // ここに「public virtual void show() {…} //基本クラスの仮想メソッド」があるがオーバーライドされる public override void show() { //オーバーライドメソッド Console.Write(hp + ", " + mp); } } HoimiSlime hoimin = new HoimiSlime(); //ホイミンをホイミスライムとして生成 Slime slime1 = hoimin; //ホイミンをスライムクラスのスライム1号として扱う slime1.show(); //スライム1号はSlimeだが、多態性により中身がホイミンと分かりオーバライドメソッドが呼ばれる アレンジ演習:p.233 override02.cs ・名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう ・Dogクラスに名前を隠ぺいするLeg()メソッドを追加し「return 0;」としてみよう 作成例 //アレンジ演習:p.233 override02.cs using System; class Mammal { //基本クラス「哺乳類」 protected const int LegNo = 4; //継承可能なインスタンス変数(定数) protected string Koe; //継承可能なインスタンス変数 public virtual string Nakigoe() { //オーバーライド可能な仮想メソッド Koe = "..."; //哺乳類の鳴き声は様々なので return Koe; } public int Leg() { //足の数を返す通常メソッド return LegNo; //定数値を返す } } class Cat : Mammal { //派生クラス「猫」 public override string Nakigoe() { //オーバライドメソッド Koe = "ニャー、ニャー"; //鳴き声が確定 return Koe; } } class Dog : Mammal { //派生クラス「犬」 public override string Nakigoe() { //オーバライドメソッド Koe = "ワン、ワン"; //鳴き声が確定 return Koe; } new public int Leg() { //【以下追加】足の数を返す通常メソッドの名前の隠ぺい return 0; //仮に0を返す ※多態性により用いられない } } class override02 { public static void Main() { Mammal m; //基本クラス「哺乳類」型の変数の宣言 Cat cat = new Cat(); //派生クラス「猫」のインスタンスを生成 Dog dog = new Dog(); //派生クラス「犬」のインスタンスを生成 m = cat; //「猫」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("猫の脚は{0}本で鳴き声は「{1}」です", m.Leg(), m.Nakigoe()); //鳴き声は多態性により「猫」になる m = dog; //「犬」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("犬の脚は{0}本で鳴き声は「{1}」です", m.Leg(), m.Nakigoe()); //鳴き声は多態性により「犬」になるが、足の数は変わらず4のまま } } p.235 プロパティのオーバーロード ・シグニチャ(プロパティ名)が同じプロパティを派生クラスに定義してオーバーライドすることが可能 ・多態性(ポリモフィズム)が発生する アレンジ演習:p.236 override03.cs ・プロパティにおいても、名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう ・MammalクラスのLeg()メソッドをプロパティ(getのみ)に変更する。 ・Dogクラスに名前を隠ぺいするLeg()プロパティ(getのみ)を追加し「return 0;」としてみよう 作成例 //アレンジ演習:p.236 override03.cs using System; class Mammal { //基本クラス「哺乳類」 protected const int LegNo = 4; //継承可能なインスタンス変数(定数) public virtual string Nakigoe { //オーバーライド可能な仮想プロパティ get { return "..."; } } public int Leg { //【変更】足の数を返す通常プロパティ get { //【追加】 return LegNo; //定数値を返す } } } class Cat : Mammal { //派生クラス「猫」 public override string Nakigoe { //オーバーライドプロパティ get { return "ニャー、ニャー"; } } } class Dog : Mammal { //派生クラス「犬」 public override string Nakigoe { //オーバーライドプロパティ get { return "ワン、ワン"; } } new public int Leg { //【以下追加】足の数を返すプロパティの名前の隠ぺい get { return 0; //定数値を返す ※多態性により用いられない } } } class override03 { public static void Main() { Mammal m; //基本クラス「哺乳類」型の変数の宣言 Cat cat = new Cat(); //派生クラス「猫」のインスタンスを生成 Dog dog = new Dog(); //派生クラス「犬」のインスタンスを生成 m = cat; //「猫」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("猫の脚の数は{0}本で、鳴き声は「{1}」です", m.Leg, m.Nakigoe); //【変更】鳴き声は多態性により「猫」になる m = dog; //「犬」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("犬の脚の数は{0}本で、鳴き声は「{1}」です", m.Leg, m.Nakigoe); //【変更】鳴き声は多態性により「犬」になる、足の数は変わらず4のまま //【以下参考までに追加】 Console.WriteLine("dog.Leg = {0}", dog.Leg); //名前の隠ぺいをするプロパティが呼ばれる } } p.237 インデクサのオーバーロード ・シグニチャ(引数の型、数、並び)が同じインデクサを派生クラスに定義してオーバーライドすることが可能 ・多態性(ポリモフィズム)が発生する アレンジ演習:p.237 override04.cs ・インデクサにおいても、名前の隠ぺいでは(オーバーライドは異なり)多態性が起こらないことを確認しよう ・MammalクラスのLeg()メソッドをthis[int]インデクサに変更する。 ・Dogクラスに名前を隠ぺいするthis[int]インデクサを追加し「return 0;」としてみよう ・なお、このプログラムのswitch構文では、必須なはずのbreakがreturnがあれば不要(記述不可)なことが分かる 作成例 //アレンジ演習:p.237 override04.cs using System; class Mammal { //基本クラス「哺乳類」 protected const int LegNo = 4; //継承可能なインスタンス変数(定数) protected string Tail, Gei, Food, Koe; //継承可能なインスタンス変数 public virtual string this[string index] { //仮想インデクサ[string] get { return "..."; } } public int this[int index] { //【変更】脚の数を返す通常インデクサ[int] get { //【追加】 return LegNo; //定数値を返す ※多態性により用いられない } } } class Cat : Mammal { //派生クラス「猫」 public override string this[string index] { //オーバライドインデクサ[string] get { switch (index) { case "尾": Tail = "1本"; return Tail; //retrunがあればbreakは不要 case "芸": Gei = "できない"; return Gei; //retrunがあればbreakは不要 case "鳴き声": Koe = "ニャー、ニャー"; return Koe; //retrunがあればbreakは不要 case "食べ物": Food = "キャットフード"; return Food; //retrunがあればbreakは不要 default: return ""; //retrunがあればbreakは不要 } } } } class Dog : Mammal{ //派生クラス「犬」 public override string this[string index] { //オーバライドインデクサ[string] get { switch (index) { case "尾": Tail = "1本"; return Tail; case "芸": Gei = "できる"; return Gei; case "鳴き声": Koe = "ワン、ワン"; return Koe; case "食べ物": Food = "ドッグフード"; return Food; default: return ""; } } } new public int this[int index] { //【以下追加】脚の数を返す通常インデクサ[int]の名前の隠ぺい get { return 0; } } } class override04 { public static void Main() { Mammal m; //基本クラス「哺乳類」型の変数の宣言 Cat cat = new Cat(); //派生クラス「猫」のインスタンスを生成 Dog dog = new Dog(); //派生クラス「犬」のインスタンスを生成 m = cat; //「猫」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("猫の脚は{0}本です。尾は{1}です。芸は{2}。食べ物は{3}。", m[0], m["尾"], m["芸"], m["食べ物"]); //【変更】脚以外は多態性により「猫」になる m = dog; //「犬」のインスタンスを「哺乳類」型の変数で扱う Console.WriteLine("犬の脚は{0}本です。尾は{1}です。芸は{2}。食べ物は{3}。", m[0], m["尾"], m["芸"], m["食べ物"]); //【変更】脚以外は多態性により「犬」になる //【以下参考までに追加】 Console.WriteLine("dog.Leg = {0}", dog[0]); //名前の隠ぺいをするインデクサが呼ばれる } } p.240 クラスの多層階層 ・派生クラスを基本クラスとして、さらに派生クラスを定義できる ・基本クラスのメンバは派生の派生クラスに継承される ・派生の派生クラスのオブジェクトも基本クラスの変数で扱える ・派生クラスで独自に記述したメソッドも、仮想メソッドにすることで、派生の派生クラスでオーバーライドできる ・派生クラスのオーバーライドメソッドを、そのまま、派生の派生クラスでオーバーライドできる ・つまり、オーバーライドメソッドの対象は上位のクラスの仮想メソッドまたはオーバーライドメソッド アレンジ演習:p.240 inheritance05.cs ・派生クラスのオーバーライドメソッドを、そのまま、派生の派生クラスでオーバーライドできることを確認しよう ・Derived2クラスを継承するDerived3クラスを定義して、Show()メソッドをオーバーライドし、Main()で呼び出てみよう 提出:アレンジ演習:p.240 inheritance05.cs 次回予告: ・p.243「クラスの継承とコンストラクタ」から