目次
[目次]
データ構造の更新を容易にする抽象データ型と呼ばれる手法があります。データ構造を実装する具体的な変数は見せずに、そのデータ構造で可能な操作を関数で定義し、この操作のみを公開するという手法です。
外から見ると操作のみで定義されたデータ構造となり、内部の具体的データ構造の違いは見えなくなり、抽象化されます。
※たとえば複素数のデータを格納する構造体を作り、このデータの実部や虚部、あるいは絶対値や偏角を読み書きする関数を用意します。ここで、構造体のデータは非公開にして、読み書きの関数のみを公開する。
※C言語では、関数は名前、引数、戻り値のみが使う側に公開されています。内部の処理の手順など実装は非公開です。ところがデータの方は実装が公開になっています。リストや木構造のなどの実装の仕組みを使う人が理解して使うことが前提です。抽象データ型ではデータの実装を非公開にして、データを操作するために必要な全ての関数を公開にします。この結果、利用者はデータ構造の実装まで理解する必要が無くなります。関数が実行できれば、データの実装構造は変更されても問題を起こしません。
※比喩^^。 列車の切符購入窓口に置かれた、製本された時刻表を見て乗る列車を探したり、時刻表にペンでメモ書きしたりできます(落書きしてはいけませんが、防ぐのは難しい)。これは、利用者にデータを書き込んだ変数を見せている状況に似ています?。 一方で購入窓口の係員にこんな条件で乗れる列車は無いか尋るのはデータの抽象化になっているのでは?。時刻表の書式が大きく変わったら自分で時刻表を見ていた人はパニックですが、窓口で聞いていた人は係員が対応してくれれば、変化に気が付かないでしょう。
皆が係員に聞いていたら、長い行列ができてしまいます。抽象化できるのは利点ですが、処理速度は落ちます。
#include<stdio.h> #include<stdlib.h> /*データの実装 ここを非公開にしたいが、C言語では参照制限がやりにくい*/ typedef struct Node{ int data; struct Node * next; }node; node sentinel={0,NULL}; node *root=&sentinel; /*操作の実装 中の実装は非公開*/ void add(int a){ node *p=(node*)malloc(sizeof(node)); p->data=a; p->next=root; root=p; } void clear(void){ node *p; while(root!=&sentinel){ p=root; root=root->next; free(p); } } void printAll(void){ node *p=root; while(p!=&sentinel){ printf("%d\n",p->data); p=p->next; } } /*操作のみ利用 しかし、やろうと思えばrootの変更やnode型データの参照も可能*/ int main(void){ add(1);add(2);add(3); printAll(); clear(); add(4);add(5);add(6); printAll(); clear(); return 0; } /*実行結果 3 2 1 6 5 4 */
抽象データの実装は変数とその変数を操作する関数を合わせて1個の部品とすることで可能です。
部品外からの操作は関数の呼び出しで行い。データ構造を実装した具体的変数は外からは参照できないようにします。このようにすると変数の構造を更新しても影響は部品内の関数の更新だけに限定できます。
例えば、データ構造に新しい要素と、これを参照する関数を追加した場合、従来と同じ名前の関数が呼び出せれば、従来のデータを利用する側に変更の必要はありません。
※再利用を考えると,抽象データ型を使うだけでは不十分です。色々な違いを乗り越えるために、もっと抽象化する必要があります。
C言語で抽象データ型を実装する一つの手法は、1つのソースファイル(翻訳単位)にデータと関数を纏め、カプセル化のためにデータを格納する変数はstatic付きで定義しファイル・スコープとします。これで、他の翻訳単位から変数名で参照することは出来なくなります。関数はグローバル・スコープとし他の翻訳単位から関数名で参照することを許せば、この関数を通してデータを読み書きできます。ただし、この方法では抽象データ1個に対して1つの翻訳単位が必要です。
例えば次のようにデータ構造と操作の実装部分を纏めたソースファイルlist.cを作ります。
/*list.c*/ #include<stdio.h> #include<stdlib.h> /*データの実装 ソースファイルを分けて変数を非公開に*/ typedef struct Node{ int data; struct Node * next; }node; static node sentinel={0,NULL};/*staticを付けて外部から参照させない*/ static node *root=&sentinel;/*staticを付けて外部から参照させない*/ /*操作の実装 中の実装は非公開*/ void add(int a){ node *p=(node*)malloc(sizeof(node)); p->data=a; p->next=root; root=p; } void clear(void){ node *p; while(root!=&sentinel){ p=root; root=root->next; free(p); } } void printAll(void){ node *p=root; while(p!=&sentinel){ printf("%d\n",p->data); p=p->next; } }
公開する部分は次のヘッダーファイルlist.hを用意し
/*list.h*/ extern void add(int a); extern void clear(void); extern void printAll(void);
使う側は、このヘッダーファイルlist.hをインクルードすることで操作に使う関数を呼びだせるようにする。
#include "list.h" /*操作のみ利用 このソースファイルからはrootなどを参照できない*/ int main(void){ add(1);add(2);add(3); printAll(); clear(); add(4);add(5);add(6); printAll(); clear(); return 0; }
あるいは、具体的データを指すポインタをvoid型のポインタとして、具体的なデータ構造を隠す手法もあります。しかし、中身が分からないとはいえポインタを使ってデータを参照することは可能です。
※カプセル化せず、専用関数を使ってしかデータを参照しないルールが守れるのなら、データ型を隠す必要は無いかもしれません。しかし、大規模なソフトウエアを大勢で作る場合、ルールが守られることを保証する手段がありません?。
C言語を使った、もっと上手い実装が色々と考えられると思います。 しかし、そこで問題なのは部品として組み合わせることが可能な標準化された実装を強制できないことです。
※標準化しないと、他で作られたプログラムとの結合が難しく、再利用できない。
構造化ではプログラムをデータとデータを加工する関数に分けることでプログラムを設計しました。ですがこの手法では更新が難しくなります。
関係するデータと関数をまとめたモノをオブジェクト(object)と呼ぶことにします。 プログラムをオブジェクトに分け、データをカプセル化して非公開とします。プログラムはオブジェクトが互いの公開された関数を呼び合う形に作ります。この手法をオブジェクト指向(object-oriented)プログラミングと呼びます。プログラムをオブジェクト単位で記述するので更新がしやすくなりそうです。
オブジェクトは基本的にはデータとその関連する関数(読み書き以外も含む)をまとめたモノです。抽象データ型を発展させたモノになっています。
※関数だけをまとめたオブジェクトなども在りです。
※オブジェクト指向と構造化は対立概念ではありません。データと関数を別々に構造化しただけでは更新が難しくなるので、データと関数をまとめたオブジェクトをプログラム部品として使おうというのです。
オブジェクト指向プログラミングを容易にする仕組みを持つ言語をオブジェクト指向言語と呼びます。
ENIACが砲弾の弾道計算のために使われたように、昔から計算機はシミュレーションに使われてきました。ここで、構造化プログラミングで計算対象となる物を単に数値データの集まりとしてだけ捉え、物の振る舞いを関数として別に扱うと、これまで述べたように更新の難しいプログラムになってしまいます。
多数の同じ物を一度に扱うことが多いので、物の雛型を作って型から同じ物を沢山作る仕組みを用意します。型をクラス(class)、物をオブジェクト(object)と呼びます。
物にはたくさんの種類があり、これを個別に扱っていては切りがありません。そこでクラスの継承を行って関数や変数の追加を行ったオブジェクトのクラスを作る仕組みも用意します。生物進化の系統樹のようにクラスの継承階層を作ることで沢山のクラスを系統的に整理できます。
このような工夫を行ったのが、後から最初のオブジェクト指向言語と呼ばれることになるSimula(1962)です。
この仕組みが、大規模プログラムを作る上でも有効だと考えた人々がオブジェクト指向言語と呼ばれる言語を作ることになります。
※ オブジェクト指向言語 Smalltalk(1972)、C++(1979)、java(1995)
※オブジェクト指向という表現はSmalltalkにかかわったアラン・ケイが70年代に使い始めた言葉です
どんなオブジェクトを用意してプログラムを組立てるかが問題です。1つの仕事をするために、オブジェクトの関数を呼び出したら、それだけで仕事が完結せずに、他の関数を順番に呼ばなければいけないとなったら面倒です。オブジェクトに仕事を依頼したら、関連する仕事はオブジェクトが責任を持ってやってくれると面倒が無くて助かります。
オブジェクトの責務(自身の責任でやる範囲)を決めて、役割分担させるのが良さそうです。責務は具体的にはオブジェクトが外部に公開する関数群で定義します。オブジェクトは責務を果たすために内部データを記憶する変数だけでなく、自分に関係する他のオブジェクトの場所を記憶する変数も必要です。これらの変数は非公開とするのが良いでしょう。
※例えばボタン・オブジェクトの大きさを変えたい場合、ボタンの大きさを変える関数を呼んで、次にボタンが表示されているウインドウ・オブジェクトの再描画関数も呼ばないといけなのでは面倒です。ボタンの大きさを変更する関数が、自分を表示しているウインドウの再描画関数を呼んでくれるように作れば、ボタンの大きさを変えたいとき、1つの関数を呼びだすだけで済みます。
しかし、ウインドウの中で複数の部品の大きさを変えたいとき、全部品の大きさを変えてからまとめて1回の再描画の方が効率がいいでしょう。実際は、もう少し工夫が必要です。
※更新や再利用が容易な、上手な役割分担を行うには先人の知恵に学び、自らも試行錯誤することが必要。先人の知恵を集めて整理したものに「デザイン・パターン」が在ります。
オブジェクトは関数と変数をまとめたものです。 オブジェクト内のこれらの要素をメンバー(member)と呼び、java言語では変数をフィールド(field)、関数をメ ソッド(method)と呼びます。C++では、これらをメンバー変数やメンバー関数と呼ぶことが多いようです。
メンバには責務を果たすために外部に公開(public)されるものとオブジェクト内に使用を限定する非公開(private)のものがあります。メンバには外部から参照できるものと、できないものがあり、この性質を可視性と呼びます。 可視性をどうするかは重要です。 公開メンバは責務を担うものですから変更すれば他に影響します。しかし、非公開の部分は変更しても他に影響を与えません。
オブジェクトを旨く作ると中の複雑なものを見せずに公開されたメソッドのみで操作できる 更新や交換が可能なモジュール化された部品となります。例えばパソコンの画面に表示されるウインドウ、その上のボタンやテキストなどがオブジェクトで作られています。
※モジュール:まとまった機能を持つ部品で交換や置き換えが可能なもの。例えばパソコンに入れるメモリーモジュールやグラフィックスボードなど。再利用を容易にするための工夫として、ハードウエアの世界では様々な規格を作ってモジュール化された部品を組み合わせるようになりました。
[目次]
オブジェクト指向のプログラミングが容易にできるように作られた言語がオブジェクト指向言語です。しかし、オブジェクト指向の考え方をそのままプログラミング言語として実現できる訳ではありません。
沢山のオブジェクトを分類するものとしてクラスと言う言葉が使われます。
例えば、会社の受付業務では受付係りの花子さん太郎さん、、2012年版電話帳などの資料、受付を訪れる客の次郎君などがオブジェクトであり、会話したり電話帳を調べたりして受付業務は行われます。ここで受付係り、客、電話帳といった分類がクラスです。クラス分けは色々ありえます、例えば社員と部外者とか、男と女とか、人と電話帳とか、問題領域の考え方でクラス分けは様々です。
C++言語では関数を要素に含めた構造体を定義できるようにしました。この構造体は要素として変数(フィールド)と関数(メソッド)を持つので,構造体型の変数としてオブジェクトを実装できます。このようなオブジェクトは元になる構造体の型の違いで分類できるので、拡張された構造体をクラスと呼びます。
※C言語でも構造体の要素として関数へのポインターを持たせれば、データだけでなく関数もまとめたデータ構造を作ることが可能。
※初期のC++はC++のソースコードをCのソースコードに変換し、これをコンパイルして実行可能プログラムを作っていた。現在はこのようなことはしていない。
型と変数に当たる関係を強調してクラス(class)とインスタンス(instance)という呼び方も使われます。 辞書を引くとそれぞれ次のような意味の言葉です 。
クラス(class):
辞書的には 分類されたもの、類、部類 の意味、インスタンス(instance):
辞書的には 事例、例、(事実を例証するための例)
C++ではインスタンスがオブジェクトに対応し、構造を示す拡張された構造体(クラス)が分類のクラスに対応します。
本来はオブジェクトの分類を多様に考えることができるはずですが、 このようなクラス型の変数としてオブジェクトを実現しているために、多様な分類に対応するのは困難です。
※クラスを定義してインスタンスを生成するタイプの言語を、クラスベースのオブジェクト指向言語と言います
※C言語との関連でかなり複雑なので、C++でのクラスの具体的な話は省略します。
java言語でもオブジェクトを実装する仕組みはC++と同様です。java言語のプログラムでも クラスにインスタンスの設計図である変数(インスタンス・フィールド)や関数(インスタンス・メソッド)の構造を記述 します。この設計図に従って作られたインスタンスが、オブジェクトに対応します。
C++との大きな違いは、javaではクラスの外に変数や関数を書くのを止めたことです。
C++ではC言語と同様のmain関数をクラスとば別に書き、ここでクラスからインスタンスを生成して組み合わせて起動する部分を記述しています。java言語ではクラス自体が持つ変数(クラス・フィールド)や関数(クラス・メソッド)を使ってこれを行います。
※オブジェクト指向モデルでもクラスに所属するオブジェクトが供用する変数や関数はクラスのメンバとすることがあります。例えば、太郎や花子が分類される人間クラスに総人口 を格納するフィールド(変数)あるいは総人口を計算するメソッド(関数)を用意するような 場合です。このようなメンバをインスタンスではなくてクラスのメンバとして持つことは解り易いと思います。
java言語ではクラスのメソッドとして起動用のmain関数を書くことになっています。プログラムを実行するときに指定したクラスのmain関数が起動の処理を行います。
※java言語では、クラス自体がクラスのメンバを持つことでオブジェクトとしての特性を持ちます。このため、クラスにクラス・フィールドやクラス・メソッドのみを記述し、クラス自体をオブジェクトとしてプログラムをつくることも可能です。しかし、このようにクラスでオブジェクトを実装するのは例外的な使い方です。
例えば、初等関数などを実装するMathクラスでは、全ての関数をクラス・メソッドとして実装しています。Math.sin(1.5)とかMath.exp(2)といった使い方で三角関数や指数関数を利用します。
[目次]
BASICのプログラムは命令文がプログラムの最小単位でした。C言語プログラムでは関数が最小単位です。これに対してjava言語ではクラスが単位になります。
print "Hello World!"
と一行で書けます。BASICでは、実行できるプログラムの最小単位は命令文です
/*CのHelloWorld*/
#include<stdio.h>
int main(void){
printf("Hello World!\n");
return 0;
}
C言語の実行できるプログラムの最少単位は関数です。つまりHelloWorldを書き出す命令文単独ではプログラムとしての実行が許され ず、mainという名前の関数ブロックの中に入れてはじめて実行可能になります。
C言語ではプログラムに関数という構造を強制することで、複数の処理をまとめて実装内容への外部からの参照を禁止し、関数名、引き数、戻り値のみが公開されたプログラム部品とします。
/*javaのHelloWorld*/
public class Hello{
static public void main(String args[]){
System.out.println("Hello World!");
}
}
クラスが実行プログラムの最少単位となっています。オブジェクトは複数の関数と変数を纏めることが出来るのでC言語に比べて規模の大きな部品を作ることが可能です。
では実際にやってみましょう。
皆さんの前の計算機は既に環境設定済みです。自宅のPCでもやってみたいなら、
http://java.sun.com/ からJava のJDKをDownloadしてインストール
その後でPATHの設定を行ってjavaのコンパイラや仮想機械のコマンドがどこからでも使えるようにします
情報活用のページに手順を書いています。
メモ帳(notepad.exe)を起動し下記の様にソースコードを書いて下さい
※何らかのプログラミング言語で書かれたテキストプログラムを一般的にソースコードと言います。Java言語でも同様です
メニューの「ファイル/名前を付けて保存」を選択しZドライブにgengo2フォルダを作成し、その中に名前「Hello.java」で保存して下さい。
ファイル名の入力枠のすぐ下に「ファイルの種類」の欄が有りますが、ここは「すべてのファイル」に。「テキスト文書」にすると自動的に拡張子をtxtにされてしまいます。もし保存したファイル名がHello.java.txtになっている場合は、後でHello.javaに変更しましょう。
Java言語のコンパイラはソースコードを機械語ではなくて中間言語コードに翻訳します。この、中間言語コードはバイトコードと呼ばれています。
コマンドプロンプトを開いてソースコードのファイル「Hello.java」をコンパイルしバイトコードのファイル「Hello.class」を作ります。次の手順で行ってください。
※下記は私のPCで実行したときの画面なので皆さんの実行画面とはディレクトリが異なります、
皆さんはZドライブのgenngo2フォルダに移動して実行してください。
Hello.classを実行しましょう
javaがインタプリタ:Java仮想機械(JavaVM)のコマンドです。引数に指定するのはクラス名なので
コマンドプロンプト>java Hello
で実行してください。java
Hello.classではありません。実行が旨くいけば「HelloWorld!」が表示されます。
※C言語の静的リンクに相当する処理はjava言語では不要です。javaプログラムを実行するjavaVMが実行中に必要になれば、目的のclassファイルを探して取り込んでくれます。このようなリンクを動的リンクと言います。
上のHelloWorldプログラムではインスタンスの記述は何もありません。もしインスタンスにHelloと言わせたいのならクラスにインスタンスの設計図を記述し、クラスのmain関数でnewを使ってインスタンスを生成してから、インスタンスのメソッドを呼び出して仕事を依頼します。次のように記述します 。
インスタンスを使う利点は同じ形の複数のオブジェクトが用意できることなので英語と日本語で挨拶する2つのインスタンスを作ることにしました。
public class Hello { // クラスのメソッド public static void main(String args[]) { //まずはインスタンスを2つ生成 Hello a=new Hello("Hello"); Hello b=new Hello("こんにちは"); //次に各インスタンスのメソッド(関数)を呼ぶ a.greetings(); b.greetings(); } /*---以下はインスタンスの設計図 staticが付かないことで区別される---*/ //フィールド:個々のインスタンスが持つ変数の宣言 //インスタンスが持っている変数なのでインスタンスからしか参照できない //インスタンスが生成されるときにメモリーが確保される。 //インスタンスごとにもつ変数なので、インスタンスごとに値が違ってもいい String msg; //メソッド:個々のインスタンスが持つ関数の定義 //インスタンスが持っている関数なのでインスタンスからしか呼び出せない。 //インスタンスごとに同じ関数を持つのは無駄が多いので //クラス側に関数を置いて、インスタンスからこの関数を呼び出すことになっている public void greetings() { System.out.println(msg); } //コンストラクタ:クラスと同名で戻り値無しのメソッドはコンストラクタと呼ばれる //インスタンス生成時にインスタンスの初期化に使われる関数 // new Hello("こんにちは") のようにnewと組み合わせて呼び出す。 public Hello(String s) { msg=s; } /*---------------------------------------------------------*/ }
C:\Users\mizuno>notepad Hello.java C:\Users\mizuno>javac Hello.java C:\Users\mizuno>java Hello Hello こんにちは C:\Users\mizuno>
※ここでクラス名はなんでもかまわないが、publicなclassであることが必要、javaプログラムはクラスを指定して実行開始される為、クラスごとにmain関数を書くことが可能。
図の描き方はUMLと呼ばれるものがデファクトスタンダード(de facto standard:事実上の標準)になっています。ここでもできるだけUMLに従う書き方をすることにします。
インスタンスを使わないHelloクラスを表すクラス図。
Hello +main(args:String[ ]):void
クラスを四角形で表し、最上段にクラス名、中断にはフィールド、下段にはメソッドを記述します。下線の引かれたメソッドは、このメソッドがクラスのメンバーであることを示しています。また先頭の+記号は公開を意味する可視性の記号です。
※main(args:String[ ] ):voidのように 名前:型 となっていて、Pascal言語と似ています。表記法がCとは順序が逆ですが、これはアルゴリズム記述の標準的な書き方で、ALGOL(アルゴル)が基になっています。
インスタンスを使うHelloクラスのクラス図。
Hello ~msg: String +main(args:String[ ]):void
+Hello(s:String)
+greetings()
ここではインスタンスのフィールドmsgとメソッドHelloとgreetingsが増えています。インスタンスのメンバーなので下線が在りません。「~」チルダのマークは可視性がpackageに公開の意味です。
※可視性はpublic、protected、package、privateと4種類在りますが、後で説明します。
Helloクラスのクラス・メソッドmainの実行の流れは下の図のようになります。呼び出されたら、まずはHelloのインスタンスを2つ生成しコンストラクタで初期化します。 その後、それぞれのインスタンス・メソッドgreetingsを呼 び出します。
この図はシーケンス図(sequence diagram)といいオブジェクト間のメソッド呼び出しを時間軸で示すものです。時間は上から下へ流れています。
結果として実行中は下の図のようにHelloクラスと、そのインスタンスa,bの3つのオブジェクトがあることになります。
[目次]
C言語ではmain関数はプログラムに1個だけでした。java言語ではクラスごとにクラスのメソッドとしてmain関数を作ることができます。java仮想機械は指定されたクラスのmain関数からプログラムを開始します。
java言語のプログラムはクラス単位なので,まずはクラスが必要です。何もしないTestMainという名前のクラスを書いてみます。中に何処からでも使えるmain関数を書く予定なので,クラス自体も公開にしなければなりません。
ソースコードは次のようになります。公開(public)のクラス(class)でTestMainという名前にします。クラスの中身は{ }の中に書く予定です。
public class TestMain {
}
保存するファイルの名前をTestMain.javaとします。
java言語ではソースコードのファイル名は「クラス名.java」とする決まりです。これはjavaのバイトコード・コンパイラがコンパイルするファイルを探すときに好都合だからです。
※このままでもコンパイルはできますが,main関数が無いので実行はできません。
main関数はクラスの{ }の中に書きます。何処からでも使えるmain関数なのでpublicが付きます。次のstaticはクラスのメンバであることを示します。そのあとは形は戻り値や引数の型が決められている他はC言語と似ています。
public static void main( String args[ ] ){....}
ここで、publicとstaticの順番は逆でもかまいませんが、多くはpublicを先に書くようです。
static public void main( String args[ ] ){....}
仮引数の変数名は自由ですargsである必要はありません。
例:
public class TestMain {
public static void main(String args[]) {
System.out.println("Hello World");
}
}
コンパイルできますがこのクラスをアプリケーションとして実行しようとすると
この例はコンパイルできますがこのクラスをアプリケーションとして実行しようとすると
>notepad TestMain.java >javac TestMain.java >java TestMain Hello World >
引数の型を変えても,関数の記述として文法ミスがなければコンパイルはできます。しかし,そのmain関数をjava仮想機械からアプリケーションの起動関数として実行することはできません
例:
public class TestMain {
public static void main() {
for(int i=0;i<10;i++)
System.out.println(i);
}
}
この例はコンパイルできますがこのクラスをアプリケーションとして実行しようとすると
>notepad TestMain.java >javac TestMain.java >java TestMain Exception in thread "main" java.lang.NoSuchMethodError: main C:\Users\mizuno>
の様にmainが無いと言われて実行できません。
(javaVMの中にはこの形を実行できる処理系もあるようです)
※java言語のmain( )はC言語ならmain(void)と書くものに対応します。
C言語でmain( )と書くと引数並びの情報が無い可変個引数の関数の意味になる。java言語のmain(
)とは違うものです。
※java言語でもデータ型が決まった可変個引数なら作れます。しかし、Cのようなデータ型の情報まで無い可変個引数は作れません。
C言語では関数は名前で区別されました。しかし、javaでは関数は名前と引数並びによって区別されます。この結果として、同じ名前でも引数の異なる関数を作ることが可能です。例えば下のようなプログラムを作ることが許されます。
public class Test2
{
public static void main(String a[ ]){
for(int i=0;i<10;i++)main();
}
public static void main(){
System.out.println("main()");
}
}
この例では上の正規のmain関数は実行できるので,そこから下のmain関数を呼んでみました。
>notepad Test2.java >javac Test2.java >java Test2 main() main() main() main() main() main() main() main() main() main()
課題1初期ファイルをダウンロード。1年生のプログラミング序論の課題1と同じ内容をjava言語で作るだけです。
下記のよ言うに、学籍番号と氏名を2行に分けて出力する。
123456789 水野和生
ヒント System.out.println( ) の printlnは文字列を書いた後で改行します。
ちなみにSystem.out.print( )の printでは文字列を描きだした後の改行はしません。
レポートツールでレポートを提出する手順を確認してください。
※課題以外はレポートツールを使う必要はありません。コマンドラインで実行する方が勉強になります。
[目次]