テキスト編: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]); //インデクサ経由で参照
}
}
}