クラスの文法
第1章では文法のみに焦点を絞ってどのような挙動をするかどうか見ていきます。
サンプルは必ず自分の手で打って入力して下さい。
但し、System.Console.WriteLineに関してはコピペしても構いません。
第1章はこれが何に役に立つか分からないと思いますので辛いですが、
第2章で具体例を通して作っていきますので我慢して下さい。
クラスとは
クラスとはオブジェクトのための「ひな形」です。
クラスを1つ作れば、そのひな形からいくつものオブジェクトが生成されます。
オブジェクトとは、1つの意味のある塊です。
前置きはいいので、早速実行していきます。
C#では次のようにしてクラスを作成します。
class クラスの名前{
}
この中にクラスの中身を記述していきます。
まずはクラスを使ってひな形を作るときに呼び出される、コンストラクタと呼ばれるものを作ってみましょう。
class クラスの名前{
public クラスの名前(){
プログラム内容
}
}
コンストラクタの名前はクラスメイト同じにする必要があります。
次にクラスを使ってオブジェクトを作成してみます。
クラスを使ってオブジェクトを作ることをインスタンス化と言います。
そのオブジェクトのことをクラスのインスタンスと言います。
クラス名 変数名 = new クラス名();
これでインスタンス化が出来ます。正確には、new クラス名()でインスタンス化します。
インスタンス化した結果を代入するために、型の名前としてクラス名を使って変数に代入します。
それではサンプルを実行してみましょう。
class Program{
public static void Main(){
A a = new A();
}
}
class A{
public A(){
System.Console.WriteLine("In A()");
}
}
インスタンス化したときにコンストラクタA()が呼び出されていることが分かると思います。
クラスA, Bを作り、クラスAのインスタンスを2つ、クラスBのインスタンスを1つ作成せよ。
但し、コンストラクタにどちらのクラスでインスタンス化されたかが分かるようにSystem.Console.WriteLineで出力せよ。
クラスのインスタンスの配列
配列を思い出してみましょう。
int[] a = new int[4];
クラスによって型作られたので、同じようにして次のように書けます。
A[] a = new A[4];
このとき、配列として4つのインスタンスの領域が確保されました。実際に実行してみましょう。
class Program {
public static void Main() {
A[] a = new A[4];
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
}
実行結果の通り、インスタンス化はされていません。
個々の要素にインスタンスを入れる必要があるのです。
class Program {
public static void Main() {
A[] a = new A[4];
a[0] = new A();
a[1] = new A();
a[2] = new A();
a[3] = new A();
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
}
前回使ったfor文を使うと次のように書けます。
for(int i = 0; i < 4; i++) {
a[i] = new A();
}
プログラム中で繰り返す処理は必ずfor文を使って書いて下さい。
(それは後でメンテナンスしやすいからです。
それはプログラムが見やすいからです。
それは入力する手間が省けるからです。
それは人間様のする作業ではないからです。)
デストラクタ
作ったインスタンスは使われなくなると破棄されます。
破棄されたタイミングで呼び出されるのがデストラクタです。これは次のように加えます。
class クラスの名前{
public クラスの名前(){
プログラム内容
}
~クラスの名前(){
プログラム内容
}
}
1.2.のサンプルにデストラクタを追加せよ。
プログラム内容は、デストラクタが実行されたことが分かるようにSystem.Console.WriteLineで表示せよ。
※ なお、今後はデストラクタは省略します。
なぜならば、書かなかった場合は自動的に内部で処理されるからです。
いつか必要になるときが来ますが、それまで頭の片隅に入れておいて下さい。
ところで、デストラクタについて次のようなサンプルを実行してみます。
class Program {
public static void Main() {
int x = 100;
if(x > 0){
int y = 100;
A a = new A();
}
A a2 = new A();
System.Console.WriteLine("y = {0}", y);
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
~A() {
System.Console.WriteLine("In ~A()");
}
}
ご覧の通りコンパイルできません。
これは、変数yがスコープの外にあるためです。
スコープとは簡単に言うと{から}の中です。
この中で宣言された変数は、この{から}のなかでのみ使用できます。
つまり、今回の例で言うとif文の中でしか参照できないのです。
同様に変数aもif文のなかでしか参照できません。
それにもかかわらず、System.Console.WriteLine("y = {0}", y);を抜かして実行すると、
In A()
In A()
In ~A()
In ~A()
となります。これは、Mainが終了するときにデストラクタが呼び出されたことになります。
プロパティ
インスタンスに値を持たせることが出来ます。この値をプロパティと呼びます。
同様にクラス自身にも持たせることが出来ますが、今回は割愛します。
プロパティを使うには次のように加えます。
class クラスの名前{
public 型 プロパティ名;
public クラスの名前(){
プログラム内容
}
~クラスの名前(){
プログラム内容
}
}
また、外部からインスタンスに対してプロパティを参照するには、
(インスタンス変数).(プロパティ名)
とします。ドットを打つだけですね。
それでは試しに使ってみましょう。次のようなサンプルを実行します。
class Program {
public static void Main() {
A a = new A();
System.Console.WriteLine("a.x = {0}", a.x);
}
}
class A {
public int x;
public A() {
x = 100;
System.Console.WriteLine("In A()");
}
}
このサンプルでは、コンストラクタでプロパティxに100を代入しています。
その後、Mainでa.xとして表示しています。
プロパティは、(今の範囲では)クラスの中で自由に読み書きできます。
プロパティとはインスタンス固有の値です。従ってインスタンスを複数生成した場合、
それぞれに値が保存されます。
次の例では、プロパティを100で初期化した後、Mainのなかでそれぞれ上書きしてしまいます。
注目するのは、aの持っているxと、a2のもっているxとは値が異なると言うことです!
class Program {
public static void Main() {
A a = new A(), a2 = new A();
a.x = 50;
a2.x = 300;
System.Console.WriteLine("a.x = {0}, a2.x = {0}", a.x, a2.x);
}
}
class A {
public int x;
public A() {
x = 100;
System.Console.WriteLine("In A()");
}
}
メソッド
メソッドとは、クラスのインスタンスがすべき処理のまとまりの1つをいいます。
メソッドを記述すると、そのメソッドを呼び出してプログラムを実行することが出来ます。
メソッドを追加するには次のように記述します。
class クラスの名前{
public 型 プロパティ名;
public クラスの名前(){
プログラム内容
}
public void メソッド名(){
プログラムの内容
}
}
※ デストラクタは今回は省略しています。
メソッドを実行するには、
(インスタンス名).(メソッド名)();
とします。
それではサンプルでメソッドを実行してみましょう。
class Program {
public static void Main() {
A a = new A();
a.test(); // インスタンスaのメソッドtestを実行
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
public void test() {
System.Console.WriteLine("In test()");
}
}
メソッドは値を返すことが出来ます。
返す値がないときはvoidを指定し、あるときはその型を書きます。
値を返すには、メソッド内でreturn 返す値;とします。
次の例ではメソッドを実行すると100を返します。
返した値は、実行した位置に直接置換したように利用できます。
例えば、x = a.test();と実行したら、x = 100;のように置き換わって処理されます。
class Program {
public static void Main() {
A a = new A();
int x = a.test();
System.Console.WriteLine("x = {0}", x);
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
public int test() {
System.Console.WriteLine("In test()");
return 100;
}
}
メソッドの返す値を戻り値と言います。
なお、メソッド内でreturnすると、それ以降の行は無視されます。
メソッドに対して何かを入力することが出来ます。
これは、
public 戻り値の型 メソッド名(引数1の型 引数1, 引数2の型 引数2, ...){
プログラムの内容
}
とします。サンプルを次に示します。サンプルでは、testメソッドに100を入れて2倍して返します。
class Program {
public static void Main() {
A a = new A();
System.Console.WriteLine("戻り値は{0}", a.test(100));
}
}
class A {
public A() {
System.Console.WriteLine("In A()");
}
public int test(int x) {
x *= 2; // xを2倍
System.Console.WriteLine("In test()");
return x;
}
}
publicとは?
今までpublicと指定してきましたが、これはクラスを使うときに指定します。
クラスの外側に対して公開する場合、つまり外部からメソッドを使ったり、
プロパティを参照したり、変更したりするには、publicを指定します。
逆に公開したり参照したり出来ないようにするにはprivateを指定します。
次の例ではプロパティxの中身をkakikomiメソッドを通じて変更します。
また、取得するときはyomikomiメソッドを通じて
class A {
private int x;
public A() {
System.Console.WriteLine("In A()");
}
public void kakikomi(int x) {
System.Console.WriteLine("In kakikomi()");
this.x = x;
}
public int yomikomi() {
System.Console.WriteLine("In yomikomi()");
return x;
}
}
class Program {
public static void Main() {
A a = new A();
a.kakikomi(10);
System.Console.WriteLine("privateなa.x = {0}", a.yomikomi());
}
}
privateなのでメインの中から、
class Program {
public static void Main() {
A a = new A();
System.Console.WriteLine("a.x = {0}", a.x);
}
}
としても出来ません。privateなプロパティは参照も出来ないのです。
プロパティをメソッドを使わずに変更させる
今までの例だと、privateなプロパティを変更するためには、或いは参照するためにはいちいちメソッドを用意しないといけないことになります。
これは不便なことが多く、C#ではプロパティの値自体を操作するのに適した文法があります。
次の例ではプロパティに対して読み込みのみ許可し、書き込みは禁止にします。
class A {
public int x {
private set;
get;
}
public A() {
x = 100; // クラスの中では書き込み可能
System.Console.WriteLine("In A()");
}
}
class Program {
public static void Main() {
A a = new A();
System.Console.WriteLine("a.x = {0}", a.x);
}
}
変更点は次の通りです。
public int x {
private set;
get;
}
を追加し、
public void set(int x) {
this.x = x;
}
を削除した。
public 型名 プロパティ名 {
private set;
get;
}
とすることで読み込みのみ許可します。一方書き込みのみ許可したければ
public 型名 プロパティ名 {
set;
private get;
}
とします。両方禁止したければ
private 型名 プロパティ名;
でいいですね。
C#ではプロパティに対して設定、取得を詳細に定めることが可能です。
プロパティに対して値を設定すると言うことは、C#の文法で
instance.property = value;
と書くことであり、取得するとは、
variable = instance.property;
と書くことです。これは今まで書いてきたメソッドyomikomiとkakikomiに対応しますね。
つまり、これらの動作は、
instance.kakikomi(value);
や
variable = instance.yomikomi();
に相当するわけですね。
このメソッドを書くような感覚で次のように書けます。
class A {
private int x;
public int X {
set {
// setの中ではvalueが代入される値に相当する
if(value % 2 == 1) {
x = value - 1;
}
else {
x = value;
}
}
get {
return x;
}
}
public A() {
System.Console.WriteLine("In A()");
}
}
class Program {
public static void Main() {
A a = new A();
a.X = 3;
System.Console.WriteLine("a.x = {0}", a.X);
}
}
最初の
private int x;
で外部から参照・変更ができないようにprivateで宣言します。
次に、参照・変更が出来るように別の名前(普通はprivateのプロパティの先頭を大文字にしておきます)を付けて、
public int X {
とします。publicにまず設定しておきます。
それでは、
set {
if(value % 2 == 1) {
x = value - 1;
}
else {
x = value;
}
}
を見てみましょう。valueは代入された値です。これは予約後で、いつでもvalueは代入された値です。
これは、代入されたときにvalue % 2 == 1で奇数と判断し、そのときは
xの値をvalue - 1の偶数に変換して代入します。
else {
x = value;
}
で偶数の場合はxの値を偶数にします。
次に
get {
return x;
}
を見てみましょう。returnで返す値を指定します。
今回の場合は、中身をそのまま表示するようにするためにreturn x;としています。
staticなメソッド・プロパティ
今まではクラスからインスタンスを生成して、それに対して何かすることを考えてきました。
ところが、クラスによっては、インスタンス化しなくても使えるようになると便利なメソッドを実装したり、
そのためのプロパティを設定したりしたくなることがあります。
次の例ではクラスにstaticな変数xを指定して、Mainのなかで変更して表示しています。
class A {
public static int x;
public A() {
System.Console.WriteLine("In A()");
}
}
class Program {
public static void Main() {
A.x = 100;
System.Console.WriteLine("A.x = {0}", A.x);
}
}
staticな変数は、クラスのインスタンスの内部からでも使用できます。
class A {
public static int x;
public A() {
System.Console.WriteLine("In A(), x = {0}", x);
}
}
class Program {
public static void Main() {
A.x = 100;
A a = new A();
System.Console.WriteLine("A.x = {0}", A.x);
}
}
この例では、クラスAのコンストラクタの中でstaticな変数xを参照しています。
同様に、メソッドの場合もstaticを付けることによってインスタンス化しなくてもメソッドが実行されます。
class A {
public A() {
System.Console.WriteLine("In A()");
}
public static void M() {
System.Console.WriteLine("In M()");
}
}
class Program {
public static void Main() {
A.M();
}
}
この例では、Main中からクラスAのstaticなメソッドMを実行しています。
ところで、次に様なプログラムはコンパイルできません。なぜでしょうか?
class A {
public int x;
public A() {
x = 10;
System.Console.WriteLine("In A()");
}
public static void M() {
x = 20;
System.Console.WriteLine("In M()");
}
}
class Program {
public static void Main() {
A.M();
}
}
引数を2つ指定して、足し算を行い、その結果を返すプログラムを作成せよ。
引数を1つ指定して、そのあたいをプロパティに保存するプログラムを作成せよ。
※ 補足
プロパティの名前と引数の名前が同じ時、引数の方が優先されます。
メソッドの中でプロパティの値を明示的に呼び出すには、this.プロパティ名とします。
つまり、引数をxで受け取り、プロパティxに設定するには、
this.x = x;
とします。
1からNまでの整数を足し算し、その結果を返すメソッドを作成せよ。
なお、Nは引数で与える。
あるプロパティが1つあり、値を取得するメソッドGetと、
値を引数に入れて変更するメソッドSetを作成し、実行せよ。
クラスA, Bがあり、クラスAのプロパティとしてクラスBを持つ。
また、クラスBはプロパティとして整数xを持つ。
クラスAにはメソッドsetがあり、クラスBのインスタンスのxの値を引数によって変更する。
クラスBにはメソッドgetがあり、クラスBのインスタンスのxの値を戻り値とする。
なお、クラスAのコンストラクタでクラスBをインスタンス化し、プロパティに格納する。
以上のプログラムを、デストラクタも含めて、
- 各メソッドが呼び出されたことが分かるようにSystem.Console.WriteLineで出力し、
- MainのなかでクラスAをインスタンス化し、
- そのインスタンスのsetメソッドを呼び出し、
- 最後にインスタンスのプロパティからクラスBのインスタンスのgetメソッドを呼び出し値を確認せよ。
クラスを使ってみよう
本を管理しよう
私が初めてオブジェクト指向に触れたのは、Rubyというプログラミング言語で、「楽しいRuby」という書籍によるものでした。この中ではクラスの説明に本が使われていて非常に納得したのでここでもそれに則って進めていきます。
本を管理する際に必要な情報は何でしょうか?
タイトル、著者、出版社、出版日時、版数、ページ数、ジャンル、目次……
いろいろあればあるだけ探しやすいと思います。
本を管理する際に必要な機能は何でしょうか?
検索、並び替え、追加、削除、所有者の指定、貸し出し
これらを全て実装するのはそれだけ時間がかかりますので今回は簡単な目標を立てましょう。
- 本の情報はタイトルと値段だけ
- 本の登録上限数は3冊とする。
- 本を「add 本のタイトル,値段」で追加できる。ただし同じ名前の本は登録できない。
- 本を「remove 本のタイトル」で削除できる。
- 「list」で本の一覧を表示できる。
このシステムでは、コンソール上に入力されたコマンドを取り扱い、そのコマンドに従って動作します。
本当なら「コマンド」もクラスにしたいのですが、今回は「本」クラスだけに焦点を当てていきます。
まずはじめに「本」のクラスを作ります。
本は1冊の本のことです。本の名前は変えられることがないと思われます。
値段は変えられる可能性があります。ただしマイナスになることはないでしょう。
class Book { // クラスの先頭は大文字です
private string name;
private int price;
public Book(string name, int price) {
this.name = name;
if(price > 0) {
this.price = price;
}
else {
this.price = 0;
}
}
public string Name { // プロパティの先頭が大文字になってます
get {
return name;
}
private set {
// 名前は変更不可
}
}
public int Price {
get {
return price;
}
set {
// 負の値は無視します
if(value > 0) {
price = value;
}
}
}
}
続いてこの本を管理するクラス「Booklist」クラスを作ります。
3冊までしか登録できないようにしています。また、現状では追加して登録しかできません。
foreachでbook != nullとチェックしているのは、配列を初期化したときにnullになっているためです。
nullはBookのインスタンスではないため、Book.Nameなどとするとエラーになります。
class Booklist {
Book[] books; // 本のデータを格納するプロパティ
int n; // 登録数
public Booklist() {
books = new Book[3]; // Bookの配列の領域を作成
n = 0; // 初期数を0に
}
public void Add(Book book) {
if(n >= 3) {
// 上限を超えている場合はなにもしない
return;
}
books[n] = book; // 現在の位置に追加
n++;
}
public void List() {
foreach(Book book in books) {
if(book != null) {
System.Console.WriteLine("{0}({1}円)", book.Name, book.Price);
}
}
}
}
最後にMainの中でテストしてみましょう。
class Program {
public static void Main() {
Booklist bl = new Booklist();
bl.Add(new Book("らき☆すた1", 798));
bl.Add(new Book("キノの旅", 557));
bl.List();
}
}
実際に本のリストを作る側は直感的に、
本リストを作って
new Booklist();
本を作って
new Book("らき☆すた1", 798)
それを追加して
bl.Add(それ)
表示する
bl.List();
と操作できていることが分かるでしょう。
※ クラスを作るときは、そのクラスを使う人のことを第一に考えます。 ※
そのクラスを使う人(自分でも、未来の自分でも、他人でも……)が
内部の構造を知らなくても、メソッド或いはプロパティを通して操作できるようにする必要があります。
今回はBooklistについては内部の構造は分かりませんがAddで追加できるように、
表示の仕方については分かりませんが、とりあえずListで表示できるようになりました。
更にBooklistに登録するときにAdd(name, price)としなかったのは、
もしかしたら今後Add(name, price, author)となる可能性もあったり、
2冊以上続けて登録できるようになったり、と将来を考えて分離したかったからです。
※ つまり、本を追加する際に「名前とタイトルを与えて追加」するのではなく「本を追加」を
直接表しています。 ※
検索機能を付けよう :
中にあるデータと本のタイトル名を直接比較し、一致したときその本のインスタンスを返します。
もちろん現実的には、部分一致やもっと速いアルゴリズムを使って本を検索する必要があります。
public Book Search(string name) {
foreach(Book book in books) {
if(book != null && book.Name == name) {
return book;
}
}
return null; // 何も見付からない場合
}
削除機能を付けよう :
削除する際に、その本のインスタンスを指定して直接削除できるようにしてみました。
即ち、検索した結果を用いて削除できるようにしました。
こうすることで名前を指定して削除するメソッドを作るよりも拡張性が上がります。
つまり、検索するデータを「ID」にしたり「著者」にしたりいろいろできるようになるからです。
今回は削除というか、配列の値をnullにして消したことにしました。
C/C++とかやってる人が見たらnewしたメモリはどうなるの?
って気持ち悪くなるかもしれません。
でも大丈夫です、必要がなくなった段階で自動的に削除されます。
削除されるタイミングはnullにしたときではないことに注意して下さい。
public void Remove(Book book) {
for(int i = 0; i < n; i++) {
if(book != null && books[i] == book) {
books[i] = null;
}
}
}
ここまできたら後は文字列を解析して追加・削除をすればいいのですが、このコマンドの解析は大変かもしれません。
コマンドなど文字列の解析に興味があれば「正規表現」「構文解析」とかでググってみて下さい。
入力文字列を1文字ずつ探します。
今回はadd, remove, listの3種類のコマンドがあり、その後にスペースが数回あり、そして本の名前、スペースが数回あり、値段があることが期待されます。ただしremoveでは名前だけ、listでは何もありません。
文字列のi番目の文字を取得するにはどうしたらいいでしょう?
実は配列のように簡単で、変数名[index]とするだけなのです。
string str = "にほんご";
System.Console.WriteLine(str[2]);
とすれば「ん」が表示されます。
もちろんforeachも使えます。
1文字ずつ比較する例を次に示します。
string str = "にほんご";
foreach(char c in str) {
System.Console.WriteLine(c == 'ほ' || c == 'ん');
}
char型はこの入門シリーズでは初め出てきましたが、文字列ではなく1文字だけを入れるための方です。
charは1文字ですが、Unicode用に16bit領域がありますので、日本語の1文字も入ります。
また、ToStringで1文字の文字列に変換されますので、文字列の末尾に連結したいとき、
string_data += charactor;
とできます。
さて話をコマンドの解析に戻します。
有限オートマトンを愚直に
プログラミングしてみます。
有限オートマトンとは、ある状態があってその状態に入力を与えたら他の状態に移る計算モデルです。
初期状態0があって、これに何か入力(例えば'a'とか)を与えたら状態が遷移し、状態1となります。
この状態1のときに更に'd'が入力されれば次に状態4に移ります……
これを続けることで入力内容を解析できます。
実際に解析する状態を全て列挙してみましょう。
初期状態は0である
状態0 :
- aが来たら状態を1にする
- rが来たら状態を2にする
- lが来たら状態を3にする。
状態1 :
- dが来たら状態4にする
状態4 :
- dが来たら状態5にする
状態5 :
- スペースが来たら状態6にする
状態6 :
- スペースが来たら状態6のままにする
- 他の文字が来たら状態7にする
状態7 :
- スペースが来たら状態8にする
- スペース以外の文字が来たら状態7にする(ここが本の名前に当たりますね)
状態8 :
- スペースが来たら状態8のまま
- それ以外なら状態9へ
状態9 :
- 終端に来たら受理する
- 数字が来たら状態9のままにする
状態2 :
- rが来たら……
試しにaddコマンドだけ実装してみました。
class Program {
public static void Main() {
string str = "add らき☆すた 798";
int state = 0;
string title = null;
string price = null;
foreach(char c in str) {
if(state == 0) {
if(c == 'a') {
state = 1;
}
}
else if(state == 1) {
if(c == 'd') {
state = 4;
}
}
else if(state == 4) {
if(c == 'd') {
state = 5;
}
}
else if(state == 5) {
if(c == ' ') {
state = 6;
}
}
else if(state == 6) {
if(c == ' ') {
state = 6;
}
else {
title = "";
title += c;
state = 7;
}
}
else if(state == 7) {
if(c == ' ') {
state = 8;
}
else {
title += c;
state = 7;
}
}
else if(state == 8) {
if(c == ' ') {
state = 8;
}
else {
price = "";
price += c;
state = 9;
}
}
else if(state == 9) {
if(c >= '0' && c <= '9') {
price += c;
}
else {
state = 0;
}
}
}
// 受理状態
if(state == 9) {
System.Console.WriteLine("title = {0}, price = {1}", title, price);
}
else {
System.Console.WriteLine("受理できませんでした。");
}
}
}
解析する文字に「add (スペースは何回でも可能)タイトル (スペースは何回でも可能)値段(ただし整数)」という書式を入れると解析できます。但し、これ以外の書式を入力すると「受理できませんでした」となります。
正規表現は上で実装したオートマトンと等価な言語を表せます。
即ち有限オートマトンでできることは正規表現でも同等なことができます。
(言語学的に言う正規表現ではなくC#の機能としての正規表現のことです)
正規表現はパターンマッチングを行うのに適しています。
例えば(数字)-(数字)-(数字)という電話番号とマッチするかどうか調べる正規表現は、
([0-9]+)-([0-9]+)-([0-9]+)
で表され、これを次のようにして解析できます。
class Program {
public static void Main() {
System.Text.RegularExpressions.MatchCollection mc = System.Text.RegularExpressions.Regex.Matches("090-5454-1925", "([0-9]+)-([0-9]+)-([0-9]+)");
foreach(System.Text.RegularExpressions.Match m in mc) {
foreach(System.Text.RegularExpressions.Group g in m.Groups) {
System.Console.WriteLine("マッチ : {0}", g);
}
}
}
}
結果は、
マッチ : 090-5454-1925
マッチ : 090
マッチ : 5454
マッチ : 1925
System.Text.RegularExpressions.MatchCollectionは長いですが方の名前です。intとかと一緒。
System.Text.RegularExpressions.Regex.Matches(マッチングを行いたい文字列, 正規表現)でマッチを行い、
System.Text.RegularExpressions.MatchCollection型の結果を返します。
この結果をforeachでマッチした数だけループします。
今回は電話番号っぽいものが1回しか現れないので初めのループは1回だけ回されます。
次に
foreach(System.Text.RegularExpressions.Group g in m.Groups) {
System.Console.WriteLine("マッチ : {0}", g);
}
で、マッチした結果の1つめのグループを取得します。グループとは(から)のなかを1つとして、括弧の対応の数だけグループが増えます。
従って今回は、マッチした0番目の中のグループの0番から2番まで表示しているわけです。
※ なお、
gはSystem.Console.WriteLineメソッドの中で暗黙的にToStringメソッドが呼ばれており、
g.ToString()と書いても結果は同じです。
※ 1回しかマッチしないことが分かっているなら、
foreach(System.Text.RegularExpressions.Group g in mc[0].Groups) {
System.Console.WriteLine("マッチ : {0}", g);
}
と書いても同じです。
なお、正規表現についてはググって下さい。
これらのことを駆使して頑張って書いてみて下さい。
オセロを作ろう
コンソール上でオセロを遊べるように作ってみましょう。
インターフェースはキーボードのみです。
1
●○2
3 ○●
4
1
2 ○
●○●
3 ○●
4
このように打てる箇所に番号を振り、その番号を入力することでその場所に打つことができます。
この場面で5を入力したら再び番号を聞き返して、正しく打つまで繰り返します。
打てるところがない場合はスキップする前に、
※ 打てる箇所がありません。
と表示して適当な入力を促し、コンピュータの番にします。
プログラム作成までのヒント :
1. まずはオセロ板を確保します
8×8=64個固定のフィールドです。
更に、これらのデータはルール上「空白」「白」「黒」の3種類しかありません。
従って8bitのデータ型であるbyteが適しているでしょう。
byte[] board = new byte[64];
2. 次に(x, y)に石を置いてみましょう。
例えば、board[y * 8 + x]とすれば、上からy番目、左からx番目の位置を表せます。
public void Put(int x, int y, int value) {
board[y * 8 + x] = value;
}
でその位置に値を書き込めますね。
3. そしてひっくり返す処理を考えましょう
自分が白だとします。今★に打てるかどうか考えます。
するとまず、この★の周囲8カ所に黒がいないとひっくり返せませんよね?
下の図で言うと■の位置をまず探索しなければなりません。
■■■
■★■
■■■
次に例えば右の■が黒だとしましょう。
この右の先に黒が連続していれば更にそれらもひっくり返す対象になりますよね?
下の図で言うと■は検索対象となります。
★●■■■■
そして、■の内初めて白が来たところまでがひっくり返す対象となります。
★●●●○
は
○○○○○
となります。
白が来る前に空白が来てしまった場合はひっくり返せません。
これを8方向に渡って探します。
CPUが打てるようにしよう
オセロの処理としては上記だけです。次に、CPUが打つ場所を探す処理を書きましょう。
これは1つの手法ですが、オセロの現在の盤面を見てどちらが優位かを数値で表現できたとします。
例えばより大きい値ならCPUが優勢としましょう。
一番簡単なのが静的な評価です。これは相手の石の置いてある場所に関係なく、
現在CPUが置いた石の位置によって評価する方法です。
つまり、角を取れば有利で、角の付近に置くのは不利とかそういう短絡的な発想を数値化するということです。
最も、オセロ初心者に対して奈良十分強いプログラムとなりますが。
もっと強い評価のために次のサイトを紹介しておきます。
http://hp.vector.co.jp/authors/VA015468/platina/algo/3_1.htm
最終更新:2013年11月21日 17:58