6-1. C言語の関数ポインタ

下記のプログラムを見て下さい。

例6-1


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  char* name;
  int point;
} SEISEKI;

int compPoint(const void *a, const void *b){
  return ((SEISEKI*) a)->point - ((SEISEKI*) b)->point;
}
int compName(const void *a, const void *b){
  return strcmp(((SEISEKI*) a)->name, ((SEISEKI*) b)->name);
}

void printSeiseki(SEISEKI* p){
  if(p->name == NULL) {
    printf("--------------------------\n");
    return;
  }
  printf("name:%s, point: %d\n",p->name, p->point);
  printSeiseki(p+1);
}
int main(void){
  SEISEKI s[]={{"Charie",100},{"Bob",80},{"Alice",90},{NULL,0}};

  printSeiseki(s);
  qsort(s, sizeof(s)/sizeof(s[0])-1, sizeof(SEISEKI), compPoint);
  printSeiseki(s);
  qsort(s, sizeof(s)/sizeof(s[0])-1, sizeof(SEISEKI), compName);
  printSeiseki(s);
  
  return 0;
}

このプログラムでは構造体の配列について、2種類の並べ方(点数の降順、 名前の昇順)を指定しています。 順序を指定するとは、比較方法を与えることです。 つまり、処理方法をパラメータとして与える技術が必要となります。

C言語では関数ポインターという技術でこれを行います。

6-2. Comparator

一般のオブジェ クト指向言語ではそのような処理を行うオブジェクトを作る必要がありま す。 このように、後から処理するプログラムを差し替えるようにするデザイン パターンをストラテジデザインパターン と呼びます。 これを実現するには、メソッドがひとつだけ定義されたクラスのオブジェ クトをメソッドが受けとり、そのメソッドを実行するようにします。

Java で、比較方法を取り替えるには java.util.Comparator インターフェ イスを継承したクラスを作ります。

次のクラスを考えます。

例6-2


public class Seiseki {
	public String name;
	public int point;
	public Seiseki(String string, int i) {
		name = string;
		point = i;
	}
	@Override
	public String toString() {
		return "Seiseki [name=" + name + ", point=" + point + "]";
	}
}

このクラスを比較するクラスを次のように作ります。

CompPoint


import java.util.Comparator;    
public class CompPoint implements Comparator<Seiseki> {
  @Override
  public int compare(Seiseki o1, Seiseki o2){
    return o1.point - o2.point;
  }
}

CompName


import java.util.Comparator;    
public class CompName implements Comparator<Seiseki> {
  @Override
  public int compare(Seiseki o1, Seiseki o2){
    return o1.name.comapareTo(o2.name);
  }
}

このように Comparotor を作ることで、次のように並べ替えを行います。


import java.util.Arrays;
public class Main {
	private static void printArray(Object[] list) {
		for(Object s : list) {
			System.out.println(s);
		}
		System.out.println("---------------");
	}
	public static void main(String[] args) {
		Seiseki[] s={new Seiseki("Charie",100),
				new Seiseki("Bob",80),
				new Seiseki("Alice",90)};
		printArray(s);
		Arrays.sort(s, new CompPoint());
		printArray(s);
		Arrays.sort(s, new CompName());
		printArray(s);
	}

}

なお、point や name が private で宣言されている場合、Comparator を 外部クラスで作ることはできません。 その場合、Comparator を実装したクラスを Seiseki クラス内部に作ります (Inner class)。

内部クラスを作った時、インスタンスを作るには s[0].new CompPoint() とオブジェクトに new 演算子を付けます。

6-3. ラムダ式

ところが、Java8 からラムダ式という関数オブジェクトのリテラルが作れ る構文が定められ、Comparator を実装したクラスを作らずに、以下のよ うに比較方法を 与えることができるようになりました。


import java.util.Arrays;
public class Main {
	private static void printArray(Object[] list) {
		for(Object s : list) {
			System.out.println(s);
		}
		System.out.println("---------------");
	}
	public static void main(String[] args) {
		Seiseki[] s={new Seiseki("Charie",100),
				new Seiseki("Bob",80),
				new Seiseki("Alice",90)};
		printArray(s);
		Arrays.sort(s, (x,y)->x.point-y.point);
		printArray(s);
		Arrays.sort(s, (x,y)->x.name.compareTo(y.name));
		printArray(s);
	}

}

これは、 Java7 まででは次の無名クラスという構文が対応します。

  
Arrays.sort(s, (x,y)->x.point-y.point);

Arrays.sort(s,
  new Comparable<Seiseki>{
    @Override
    public int compare(Seiseki o1, Seiseki o2){
      return o1.point-o2.point);
  }
);

この大幅な簡約記法を実現するのは、型推論と、FunctionalInterface と いう概念です。

FunctionalInterface は interface のうち、一つだけ実装されていない メソッドを含むものです。 引数に FunctionalInterface のインスタンスを要求するメソッドに対して、 ラムダ式を与えることができます。

例6-3


@FunctionalInterface
interface A1 {
    int perform();
}
@FunctionalInterface
interface A2 {
    int perform(int y);
}
class Main {
    private static void print1(A1 a){
	System.out.println(a.perform());
    }
    private static void print2(A2 a){
	System.out.println(a.perform(3));
    }
    public static void main(String[] arg){
	print1(()->5);
	print2((x)->x+1);
    }
}

但し、このようなよく使われる FunctionalInterface は既に java.util.function パッケージ内に定義されています。 したがって、それらを使って、上記のプログラムを書き直すと以下のよう になります。

例6-4


import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
class Main {
    private static void print1(IntSupplier a){
	System.out.println(a.getAsInt());
    }
    private static void print2(IntUnaryOperator a){
	System.out.println(a.applyAsInt(3));
    }
    public static void main(String[] arg){
	print1(()->5);
	print2((x)->x+1);
    }
}

6-4. 演習

第2,3回で作成した A に誕生日を返す Calender birthday() メソッドを 追加します。 git://edu.net.c.dendai.ac.jp/git/spro/2/3

追加した birthday に従って並ぶようにプログラムを作成しなさい。

なお、順序の付け方は 2000/3/1 < 1999/4/1 < 2001/5/1 でも、 1999/4/1 < 2000/3/1 < 2001/5/1 のどちらでも良い。

6-5. 参考: Windows での C言語の実行環境 mingw

  1. http://www.mingw.org/にアク セス
  2. Download ページで mingw-get-setup.exe をダウンロードする
  3. Download した exe ファイルを実行する
    1. 表紙の画面で Install ボタンを押す
    2. Step1 の画面はそのまま continue(インストールする場所を変えた い場合はここで指定)
    3. Step2 の画面でインストールが始まり、終わると continue が表示される
    4. continue を押すと終了する
  4. MinGW Instalation Manager が起動する
  5. 以下をチェックする
    • mingw32-base-bin
    • mingw32-gcc-g++-bin
  6. Installation-Apply Changes を選び、Apply ボタンを押す
  7. インストールが終わると close ボタンが出るので、押し、 メイン画面で Install-Quit を選び終了する

6-6. 参考: Eclipse で C/C++ の環境を追加する

  1. Eclipse を起動
  2. Help-Install Newを選ぶ
  3. Work with 欄に --All Avairable sites-- を選ぶとインストール可能 なパッケージリストの読み込みが始まる。 しばらくすると読み込みを終え、パッケージのリストが表示される (急ぎたい場合は、Eclipse のバージョン名photonが入っている URL を選ぶ)
  4. Programming Language--C/C++ Development Tools にチェックをし、 Next を押す
  5. Next, ライセンスをAccept などと手順を進める
  6. インストールが始まる。インストールが終わると Restart を促す画面が 出る。 Restart してインストール終了
  7. 起動した後、 Workbench に移動し、 New -- Other で選択画面を開き、 C/C++ -- C project を選び Next。 プロジェクト名を入れ Executable--Hellow World ANSI C Project を選び Finish を押すとプロジェクトが作られる (MinGW がインストールされていれば自動的に関連付けられる)。 C/C++ Perspective を開くか確認が来るので、許すと開発環境が準備され る。
  8. Project -- Build All の後、Run でプログラムが実行できる