講義メモ

テキスト編:p.362 練習問題2(演算子のオーバーロードの複数化) から
ゲーム開発演習:自機と敵機の衝突判定とゲームオーバー画面 他

p.362 練習問題2(演算子のオーバーロードの複数化)

・p.358 Map05.cs:2項==演算子、2項!=演算子のオーバーロード (opov03.cs改)を用いよう
・「座標 * 実数」である2項*演算子のオーバーロードを加えよう
・X座標、Y座標のそれぞれを実数倍して整数化した座標を返すとする
・小数点以下切捨てで良い
・「座標 / 実数」である2項/演算子のオーバーロードを上記を利用して加えよう
・オーバーフローや、ゼロ除算は対処しない(例外が投げられるに任せて良い)
・「座標 - 座標」である2項-演算子のオーバーロードを他の演算子のオーバーロードを利用して加えよう
・ついでに文字列表現を返すstring ToString()メソッドのオーバーライドも記述し「(X座標, Y座標)」形式で戻すようにしよう

作成例

//p.362 練習問題2(演算子のオーバーロードの複数化)
using System;
class Map { //座標クラス
    int x; //X座標のデータメンバ 
    int y; //Y座標のデータメンバ 
    public int X { //X座標用のプロパティ
        get { return x; }
        set { x = value; }
    }
    public int Y { //Y座標用のプロパティ
        get { return y; }
        set { y = value; }
    }
    public Map() { //デフォルトコンストラクタ
        x = y = 0;
    }
    public Map(int x, int y) { //X座標、Y座標を指定するコンストラクタ
        this.x = x;
        this.y = y;
    }
    public static Map operator -(Map w) { //単項-演算子のオーバーロード
        return new Map(-w.x, -w.y); //X座標とY座標の符号を反転した結果を返す
    }
    public static double operator +(Map w) { //単項+演算子のオーバーロード
        return Math.Sqrt(w.x * w.x + w.y * w.y); //各座標の2乗の和の平方根を返す
    }
    public static Map operator +(Map a, Map b) { //2項+演算子のオーバーロード
        return new Map(a.x + b.x, a.y + b.y); //各座標の和をもつオブジェクトを返す
    }
    public static bool operator ==(Map a, Map b) { //2項==演算子のオーバーロード
        return a.x == b.x && a.y == b.y; //各座標の和をもつオブジェクトを返す
    }
    public static bool operator !=(Map a, Map b) { //2項!=演算子のオーバーロード
        return !(a == b); //2項==演算子のオーバーロードの結果を反転して返す
    }
    public static Map operator *(Map a, double d) { //2項*演算子のオーバーロード
        return new Map((int)(a.x * d), (int)(a.y * d)); //各座標の積をもつオブジェクトを返す
    }
    public static Map operator /(Map a, double d) { //2項/演算子のオーバーロード
        return a * (1 / d); //2項*演算子のオーバーロードを利用
    }
    public static Map operator -(Map a, Map b) { //2項-演算子のオーバーロード
        return a + (-b); //2項+演算子と単項-演算子のオーバーロードを利用
    }
    public override string ToString() { //文字列表現を返すオーバーライド
        return "(" + x + ", " + y + ")";
    }
}
class Map01 {
    public static void Main() {
        Map A = new Map(-3, -4); //座標Aを生成
        Map B = new Map(4, 2); //座標Bを生成
        Console.WriteLine("A{0} * 3 は {1}", A, A * 3.0);
        Console.WriteLine("B{0} / 2 は {1}", B, B / 2.0);
        Console.WriteLine("A{0} - B{1} は {2}", A, B, A - B);
    }
}

第15章 ジェネリック
p.363 ジェネリックとは

・ジェネリックは「総称」という意味で「型を変数で扱えば、型だけが異なり同一内容のクラスは1個あれば済む」ことを実現する仕組み
・よって、メソッドのオーバーロードや、引数の可変個化(p.201)などを更に進化させた位置づけ。

p.363 ジェネリッククラス

・利用時に型を指定できるクラス
・定義書式: class クラス名<型パラメータ> {…}
・C#が提供するジェネリッククラスも多数あり、それらの定義に合わせて、型パラメータは大文字1文字で表すことが多い(例:T,U,V,…)
・ジェネリッククラスを通常のクラスから利用するには、宣言と同時に型パラメータに型を指定する。
・宣言書式例: ジェネリッククラス名<型> 変数名;
・生成書式例: 変数名 = new ジェネリッククラス名<型>();
・宣言&生成: ジェネリッククラス名<型> 変数名 = new ジェネリッククラス名<型>();
・例: class Monster<T> {public T hp;} //変数hpの型は不定で宣言時に決まる
    class RPG { Monster<int> rimuru = new Monster<int>(); } //HPを整数で扱う
                Monster<double> gobuta = new Monster<double>(); } //HPを実数で扱う
・つまり、実行時に内部的に型パラメータ部分の解釈(埋め込み)が行われる

p.364 generic01.cs

//p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
    public T name; //宣言時に型が決まるデータメンバ
    public T GetVal() { //宣言時に戻り値が決まるデータメンバ
        return name; //戻り値の型は決まっていない
    }
}
class generic01 {
    public static void Main() {
        MyClass<int> mca = new MyClass<int>(); //型にintを指定してオブジェクト生成
        mca.name = 10; //よってint型になり整数の代入が可能
        Console.WriteLine(mca.GetVal()); //メソッドの戻り値型もint
        MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してオブジェクト生成
        mcb.name = "猫"; //よってstring型になり文字列の代入が可能
        Console.WriteLine(mcb.GetVal()); //メソッドの戻り値型もstring型
    }
}

アレンジ演習:p.364 generic01.cs

・mca.GetVal()の戻り値型はintなので、戻り値 + 10 とすると加算になることを確認しよう
・mcb.GetVal()の戻り値型はstringなので、戻り値 + 10 とすると連結になることを確認しよう

作成例

//アレンジ演習:p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
    public T name; //宣言時に型が決まるデータメンバ
    public T GetVal() { //宣言時に戻り値が決まるデータメンバ
        return name; //戻り値の型は決まっていない
    }
}
class generic01 {
    public static void Main() {
        MyClass<int> mca = new MyClass<int>(); //型にintを指定してオブジェクト生成
        mca.name = 10; //よってint型になり整数の代入が可能
        Console.WriteLine(mca.GetVal() + 10); //メソッドの戻り値型もintなので加算
        MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してオブジェクト生成
        mcb.name = "猫"; //よってstring型になり文字列の代入が可能
        Console.WriteLine(mcb.GetVal() + 10); //メソッドの戻り値型もstring型なので連結
    }
}

p.365(複数の型パラメータを持つジェネリッククラス)

・ジェネリッククラスの型パラメータの数には制限はない
・2つ以上を用いる場合はカンマで区切る
・定義書式例: class ジェネリッククラス名<型パラメータ①, 型パラメータ②> {…}
・複数の型パラメータを持つジェネリッククラスを通常のクラスから利用するには、宣言と同時に全ての型パラメータに型を指定する。
・宣言書式例: ジェネリッククラス名<型①, 型②> 変数名;
・生成書式例: 変数名 = new ジェネリッククラス名<型①, 型②>();
・宣言&生成: ジェネリッククラス名<型①, 型②> 変数名 = new ジェネリッククラス名<型①, 型②>();
・例: class Monster<T, U> {public T hp; public U mp;} //2変数の型は不定で宣言時に決まる
    class RPG { Monster<int, double> rimuru = new Monster<int, double>(); }

p.365(配列の型パラメータを持つジェネリッククラス)

・データメンバとして配列を持ち、その型をジェネリックで指定することも可能
・宣言例: 型パラメータ[] 配列名;
・生成例: 配列名 = new 型パラメータ[要素数];
・型が生成時に決まらないので、初期化はできないが、宣言+生成は可能。
 型パラメータ[] 配列名 = new 型パラメータ[要素数];
 例: T[] monsters = new T[3];

p.365 generic02.cs

//p.365 generic02.cs
using System;
class MyClass<T, U> { //2個の型パラメータTを持つジェネリッククラス
    public T[] x; //T型の配列xの宣言のみ
    public U[] y; //U型の配列yの宣言のみ
    public MyClass(int n) { //コンストラクタ(要素数)
        x = new T[n]; //要素数nのT型の配列を生成しxとする
        y = new U[n]; //要素数nのU型の配列を生成しyとする
    }
}
class generic02 {
    public static void Main() {
        int n; //配列の要素数
        Console.Write("n = ");
        string strN = Console.ReadLine();
        if (!Char.IsDigit(strN[0])) { //先頭文字が数字ではない?
            Console.WriteLine("入力が不適切です");
            return; //終了
        }
        n = int.Parse(strN);
        MyClass<int, string> mc = new MyClass<int, string>(n); //型指定で生成
        for (int i = 0; i < n; i++) { //全要素について繰返す
            Console.Write("番号--- ");
            string strNo = Console.ReadLine();
            if (!Char.IsDigit(strNo[0])) { //先頭文字が数字ではない?
                Console.WriteLine("不適切な番号です");
                break; //繰返し終了
            }
            mc.x[i] = int.Parse(strNo);
            Console.Write("氏名--- ");
            string strName = Console.ReadLine();
            mc.y[i] = strName;
        }
        Console.WriteLine(); //改行
        for (int i = 0; i < n; i++) { //全要素について繰返す
            Console.WriteLine("[{0}] {1}", mc.x[i], mc.y[i]);
        }
    }
}

【補足】ジェネリックプロパティ

・プロパティの戻り値型を型パラメータにできる
・例: public T HP { get { return hp; } set { hp = value; } }

アレンジ演習:p.364 generic01.cs

・GetValメソッドをジェネリックプロパティNAME(getのみ)にしよう

作成例

//アレンジ演習:p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
    public T name; //宣言時に型が決まるデータメンバ
    public T NAME { //宣言時に戻り値が決まるプロパティ
        get { return name; } //getのみ
    }
}
class generic01 {
    public static void Main() {
        MyClass<int> mca = new MyClass<int>(); //型にintを指定してオブジェクト生成
        mca.name = 10; //よってint型になり整数の代入が可能
        Console.WriteLine(mca.NAME); //プロパティの戻り値型もint型
        MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してオブジェクト生成
        mcb.name = "猫"; //よってstring型になり文字列の代入が可能
        Console.WriteLine(mcb.NAME); //プロパティの戻り値型もstring型
    }
}

【補足】ジェネリックインデクサ

・インデクサの型を型パラメータにできる
・例: public T this[int i] { get { return a[i]; } set { a[i] = value; } }

アレンジ演習:p.365 generic02.cs

・配列xをジェネリックインデクサで扱うようににしよう

作成例

//アレンジ演習:p.365 generic02.cs
using System;
class MyClass<T, U> { //2個の型パラメータTを持つジェネリッククラス
    public T[] x; //T型の配列xの宣言のみ
    public U[] y; //U型の配列yの宣言のみ
    public MyClass(int n) { //コンストラクタ(要素数)
        x = new T[n]; //要素数nのT型の配列を生成しxとする
        y = new U[n]; //要素数nのU型の配列を生成しyとする
    }
    public T this[int i] { //ジェネリックインデクサ(配列x用)
        get { return x[i]; }
        set { x[i] = value; }
    }
}
class generic02 {
    public static void Main() {
        int n; //配列の要素数
        Console.Write("n = ");
        string strN = Console.ReadLine();
        if (!Char.IsDigit(strN[0])) { //先頭文字が数字ではない?
            Console.WriteLine("入力が不適切です");
            return; //終了
        }
        n = int.Parse(strN);
        MyClass<int, string> mc = new MyClass<int, string>(n); //型指定で生成
        for (int i = 0; i < n; i++) { //全要素について繰返す
            Console.Write("番号--- ");
            string strNo = Console.ReadLine();
            if (!Char.IsDigit(strNo[0])) { //先頭文字が数字ではない?
                Console.WriteLine("不適切な番号です");
                break; //繰返し終了
            }
            mc[i] = int.Parse(strNo); //インデクサ経由で代入
            Console.Write("氏名--- ");
            string strName = Console.ReadLine();
            mc.y[i] = strName;
        }
        Console.WriteLine(); //改行
        for (int i = 0; i < n; i++) { //全要素について繰返す
            Console.WriteLine("[{0}] {1}", mc[i], mc.y[i]); //インデクサ経由で参照
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です