プログラミング言語論’10 ＃ 1
久野靖∗

2010.4.8

はじめに• 第 3 回→オブジェクト指向のさまざまなバリエーション□ 本科目のテーマ: プログラミング言語 — 具体的なテー• 第 4 回〜 (考え中)(分量によってはこの手前までを
ゆっくりやるとそれで終わりになるかも知れません)

マとしては…

• プログラミング言語とは何か?
• プログラミング言語にはどのような構成要素がある

計算機言語とは…

1のか?

• プログラミング言語にはどのようなバリエーション □ まず計算機言語とは何かというお話から。
(選択肢) があるのか?
• では計算機とは何をするもの?
□ 期待される効果:□ プログラミング言語…計算機言語の一種。
• 適切なプログラミング言語の選択ができるようになる □ 計算機言語とはどういう言語? なぜ存在する?
• 選択したプログラミング言語をよりよく使いこなせるようになる

1.1• 新しいプログラミング言語の必要性を予見できるよ
うになる (?)

計算機と人間の情報伝達□ 計算機…情報を取り扱うための装置□ 人間と計算機はどうやって情報をやりとりするか?

□ オブジェクト指向に比較的重きを置いている• 今日のソフトウェア開発において主流となっている □ ハードウェア…入出力装置
パラダイム• 入力→キーボード、マウス、マイク、…

• 道具だて、言語仕様が複雑、込み入っている• 出力→ディスプレイ、プリンタ、スピーカ、…

• 「なぜそうなっているか」という理屈はちゃんとある□ 具体的にどのような形で情報をやりとり?

• 「どういう理由で」「どうなっている」を学ぶ→使い□ 計算機に自然言語 (日本語) で命令できたら嬉しいと思こなし方が分かる

うか?

(Yes/No)□ ソフトウェア開発は結局「自分の頭でどれだけ考えられるか」の限界までしか作れない

1.2

人工言語

• 同じことをするのに「考えやすい道具 (言語)」を使□ 人間どうしの情報交換→自然言語 (日本語、英語、…)
うと→• 同じ能力でより高度なものまで考える (作る) ことが可能になる• 計算機とのやりとりには不向き• あいまいさ、処理が複雑• ではどうすれば「考えやすい」のか→言語の設計思• 人間を相手にするのと計算機を相手にするのでは違っ想を学ぶて当然 (?)□ 構成:□ 簡単な規則に基づく人工言語→計算機の処理に向く• 第 1 回→プログラミング言語の諸概念〜抽象データ型• 計算機で取り扱うために作った人工言語→計算機言語• 第 2 回→オブジェクト指向言語の主要メカニズムと用法∗ 筑波大学大学院経営システム科学専攻

1

1.3

構文と意味

プログラミング言語の諸側面

2□ 構文→言語に現われる語の並び方の規則性□ プログラム→「人間が書くもの」だが、プログラム以前には無かったさまざまな特徴/特性がある□ 意味→どのような構文であれば何を意味するか
犬です。
犬なの？

意味
＝伝えたい内容• 個別のプログラミング言語についても、その特徴/特

犬です。
犬なの？

性についていろいろな選択肢がある→以下で検討

2.1
This is a pen.
dog.

プログラムが持つ特徴□ 簡単な C プログラムを題材に…

Is this a dog?
構文＝ことばの規則

int main() {int x, y;printf("input x> "); scanf("%d", &x);
printf("input y> "); scanf("%d", &y);if(x > y)printf("%d is larger.\n", x);
else
printf("%d is larger.\n", y);return 0;}□ 言語の定義→構文と意味のペア (計算機言語も同様)• ほかに語彙 (単語) も必要だけど。

1.4

プログラミング言語□ プログラムを書くための計算機言語• Q. このプログラムは何をするプログラムですか?

• プログラムとは?

• Q. このプログラムにはどんな問題がありますか?

□ プログラミング言語の用途?

• Q. あなたが認識した「問題」を解決する方法は?

• 計算機に読み込ませる□ 問題かどうかはあくまで目的 (仕様) に照らさなければ• 人間が読む (娯楽、仕事、…)

分からない• 自分が考えるための手段→でも動かした方が嬉しい• 仕様: 「x と y が等しければ何が出力されてもよい」

1.5

だったら?

その他の計算機言語• 仕様: 「x と y に等しい数を入れてはならない」だったら?

□ プログラミング言語でない計算機言語も多数ある□ 楽譜記述言語• 仕様: 「x と y に等しい数が入ることはあり得ない」
だったら?

□ 図形記述言語• プリンタ→ページ記述言語→ PostScript…プログ □ 動作はきっちり決まっている
ラミング言語でもある。

• 言語仕様としてきちんと決まっている部分□ 文書記述言語 (マークアップ言語)、データ記述言語• 言語仕様で決まっていなくても、動かせば分かる(???)• SGML → HTML → XML、XHTML□ それが「プログラマの意図」さらには「ソフトの仕様」□ アプリケーションを動かし画面で見れば十分?

と一致しているかどうかはまた全然別の問題• 「処理」したいときは言語になっている方がよい。

1.6□ 1 つの動作を行うプログラムは何通りも書ける/* A */
if(x > y)printf("%d is larger.\n", x);
else
printf("%d is larger.\n", y);

この節のまとめ□ 計算機言語とは、計算機で扱うための人工言語• でも人間が読み書きしてもよい。
/* B */
if(x > y) z = x; else z = y;printf("%d is larger.\n", z);• 人間が考えるための「手段」でもある。
□ 代表はプログラム言語だが、他の計算機言語もいろいろある。

2• 関数 (サブルーチン)/* C */z = (x > y) ? x : y;printf("%d is larger.\n", z);• ...
□ これらがどういう意味を持っているか考えて見よう…

/* D */
printf("%d is larger.\n", (x>y)?x:y));2.3/* E */
z = x;
if(y > z) z = y;printf("%d is larger.\n", z);• どれがいいと思うか?

実行順序□ 「文が順次実行される」という考え方は「当然」か?
x = y * z;y = 3.1416 + 1;
z = 2.7183 + 2;

それはなぜか?

□ もし 3 つの値 x、y、z の最大だったら?

• どういう順番で実行されると「感じられる」?

/* D’ */
printf("%d is larger.\n",(x>y)?((x>z)?x:z):((y>z)?y:z));□ 方程式であれば、「参照関係に従って」計算する。
□ 関数型言語などでも同様。
double x() { return y() + z(); }double y() { return 3.1416 + 1; }double z() { return 2.7183 + 2; }/* E’ */
max = x;if(y > max) max = y;if(z > max) max = z;printf("%d is larger.\n", max);□ 再帰と組み合わせることで複雑な計算でも可能
double fact(n) {return (n < 1) ? 1 : n * fact(n-1);}• こんどはどれがいいか?
□ 1 つの動作を行うプログラムは何通りも書ける□ ではなぜ現在の多くの言語は「順次実行」なのか?

• そのどれがいいかは様々な側面を総合して決めるしかない• 手続き型言語のモデル←現在の計算機のモデル• プログラミング言語の機能やデザインもまさにそう!

• 現在の CPU は「命令を順番に実行して行く」モデルに従っている (実際には全然順番に実行していない

2.2

ことが多いが…)

プログラミング言語が持つ「要素」「概念」「機能」• プログラム言語でもこれにならった方が自然 (?)□ また同じ C プログラムを題材に…

2.4
int main() {int x, y;printf("input x> "); scanf("%d", &x);
printf("input y> "); scanf("%d", &y);if(x > y)printf("%d is larger.\n", x);
else
printf("%d is larger.\n", y);return 0;}

制御構造□ 「枝分かれ」「繰り返し」などの*実行順序*を表す機構
if(x > y) {z = x; x = y; y = z;}while(n > 0) {fact = fact * n; n = n - 1;}

• 実はこのように「制御構造の中に文の集まりが入る」• これにどのような「要素」「概念」「機能」が含まれという考え方が一般的になったのは 1970 年代の「構ている?

造化プログラミング運動」の結果。
□ たとえば…

• それ以前はこんな便利な (?) ものはなかった。

• 順次実行 (A; B; C)□ Fotran 60 (JIS Fortran 5000/7000)• 制御構造 (if、while、…)IF(X .LE. Y) GOTO 10
Z = X
X = Y
Y = Z
10 CONTINUE• 変数• 型 (値の種別)• アドレス (ポインタ)
32.5

30 IF(N .LE. 0) GOTO 20
FACT = FACT * N
N = N * 1
GOTO 30
20 CONTINUE

変数□ 変数とは、何ですか?

(知らない人に説明するとした

ら?)□ 変数の 2 つのモデル (どっちがいい?)□ JIS 3000

• 説明 A: 値を入れておく「箱」のようなもの。

IF(X - Y) 20, 10, 10
Z = X
X = Y
Y = Z
10 CONTINUE
20• 説明 B: 値を表す名前 (値に名前をつけたもの)。
□ 「箱」のモデル…手続き型言語のモデル• 手続き型言語…Fotran、COBOL、Pascal、C、C++、
Java、…

□ 構造化プログラミング運動• GOTO はスパゲティプログラムになるからやめよう• 変数に代入して値を書き換えて行く• 構造化構文 (今の if や while) の入れ子を使おう• 計算機ハードウェア (メモリ) の自然な抽象化になっ□ しかし「入れ子」がいいかどうかも疑問はあるている• 副作用ベース→分かりにくいバグの原因となることがある

if(a[low] < a[high]) {for(i = 0; i < high-low; ++i)
for(j = low; j < high; ++j)if(a[j] > a[j+1]) {z = a[j]; a[j] = a[j+1]; a[j+1] = z;}
} else {for(i = 0; i < high-low; ++i)
for(j = high; j > low; --j)if(a[j-1] < a[j]) {z = a[j-1]; a[j-1] = a[j]; a[j] = z;}}while(i > 0) {fact = fact * i; i = i = 1;
}
箱モデル

x

1

y

2

z

3

＋

参照モデル

• このコードが何をしているか読めるか?
• 読めるようになったとすれば「どう考えた」から?

x:

1

y:

2

z:

□ 「入れ子」構造は、ある部分 (たとえば「文」が入る部

3＋分) に、複雑な構造であっても 1 つの単位として互換性があれば入れられる、という思想によっている。

□ 「値を表す名前」のモデル…数学に近い感じ• 計算機による処理は容易 (やり方が分っていれば)。

• 関数型/論理型言語…ML、Haskell、Prolog、…

• しかし人間の頭はそういう風にはできていない。

• 変数には値が「束縛」される• →プログラムの書き手としては、「人間に扱えるよう• 一度束縛された値が書き変わることはない (単一代入とも言う)• ただし変数は複数のインカーネーション (実体) を持
つことも…

に」書く方がよい。

• →具体的にはどうする?
□ 「if」と「switch」と「while/for」と「do-while」でいいのか?

もっとあった方がいいのか?

いいのか?

(yes/no)int fact(int n) {return (n < 1) ? 1 : n * fact(n-1);}

減らした方が□ 現在あるもので「一応」足りているが「まだ」あった方

fact(n:5)↓ 5 * fact(n:4)↓ 5 * 4 * fact(n:3)↓ 5 * 4 * 3 * fact(n:2)↓ 5 * 4 * 3 * 2 * fact(n:1)↓ 5 * 4 * 3 * 2 * 1 * fact(n:0)↓5 * 4 * 3 * 2 * 1 * 1がよいかも。

• 定理:

すべての (含むスパゲティ) プログラムは連接、分岐 (if)、反復 (while) の組合せに書き換えることができる。

• しかし「言語としての使いやすさ」が問題だからもっ□ 結局:とあってもよい。

どちらのモデルも言語として成り立つ• 優劣は一概に言えない (言語どうしで優劣が言えな• 後述する「例外」などは新しい制御構造のバリエー

いのと同様)

ション。

4

2.6

型 (= 値の種別)• 基本型の値を書く手段として、リテラル (定数) が用意されている。(基本型でない場合も一部用意されて□ 「int x」「double x」「char *x」

いることはある。)• 何のために「型」があるのか?

□ 複合型…基本型でない型すべて。複数の値を含む…かどうか?

□ さまざまな「型がある理由」(A)□ 配列型…もっとも古くからある複合型 (ベクトルや行列• 入れる「箱」の大きさが種別によって変わるから。

演算のため)• 種別ごとに演算 (演算命令) が違うから• →これらは言語の設計次第でどうにでもなる。

• 同じ型の値が連続して並んでいる• →ただし型がある方が効率はよい• 添字により「何番目」を指定してアクセスする• 型のない言語 (弱い型の言語) → Lisp、Smalltalk、
Perl、…

• 実現上は単にメモリ上に必要なだけの領域を並べるのが基本 (だった)a[0]□ さまざまな「型がある理由」(B)

a[1]

a[2]

a[N]

a:

• プログラマに「ここはどういう種類の値」という情• おかしなデータの使い型をチェックして教えたい• しかしそれでは不便だと分かってきたので…(どう不便?) →連想配列• →「よりよくプログラムを書くための道具」として• 型としては「T 型の配列」という型→ T は「パラメ

報を書かせたいタ」になっている。

の型• さらに要素数 (ないし、添字の上限/下限) も型のパラメタとする言語もある (Pascal とか)• 強 い 型 の 言 語 → Pascal、C、C++、Java、ML、
Haskell、…

• 型の明示 (変数宣言に必ず型を書く) と型推論 (「x □ レコード/可変レコード/ユニオン型= 10」なら x は int、のように使用関係に基づいて• こちらは COBOL が元祖。複数の「違う型の」値を組決定) とがある。
み合わせたもの□ その他の区分• 非常に基本的な型だった…はずが、オブジェクト指向のせいで影が薄く…

• 型には単純な型 (「整数」「実数」「文字」…基本型)• Java にはレコード型はない。C++ではクラスの特別なカタチ。

と込み入った型 (「レコード」「配列」…複合型) がある (後述)。

• 「標準で用意されている型」「ユーザ定義の型」という区別があることもある。

• 可変レコード…レコードで「途中からフィールドを• できるだけどんな型でも同じように区別なく使える• これを「取り換えずに並べるだけ」と「取り換える

取り換える」ようにした← Pascal
だけ」に分離→ユニオン型← C

方が望ましい。が。

• 例: C++の演算子定義→ユーザ定義の型でも演算子が使える。が、そのために言語的には複雑になって

2.8

いる。

ポインタ型

□ ポインタ型…とは?

2.7

C 言語の式「＆ｘ」の結果とは?

• 答 A: 変数 x の主記憶上の番地 (アドレス)

さまざまな型• 答 B: 変数 x を「指す」(参照する) 何らかの値□ 基本型…数値 (整数、実数)、論理値、文字値など。

• 答 C: その他…
• 定義? 「直接 CPU が演算できるような値の型」「マ□ 言語仕様的には「B」。
シンレジスタに載るような値の型」「それ以上分解できない値の型」(どれもよくない)• 言語仕様に「番地」は出てこない (実装に依存しす• 結局、言語仕様として「基本型に決めたから基本型」
みたいな…

ぎる)。

• int *p = …; … p + 1 … ←実際に足されるのは「1」ではない5

• それはいいが、なぜこういうもの (ポインタないし参照) が必要なのか?

int myfunc(int x) { return x*x; }int (*f)(int) = &myfunc;←○
int (*g)(int) = myfunc; ←これも○□ ポインタは何のために必要?

• C では型名や変数宣言の書き方がすごく分かりづらい…

• 変数に値を書き込んで戻してもらうため←言語に参照渡しがない場合□ 引数の個数と型、および関数が返す型も関数型の一部• 動的データ構造 (連結リスト、2 分木、グラフ、…)(パラメタ)• 大きなデータを持ち回る非効率を避けたい場合• データを複数個所で共有したい場合 (cf.

• 「proctype(int,int)returns(int)」← CLU 流の
記法

広域変数)

□ データの共有は副作用を可能にする→注意が必要• 「(int(*)(int,int))」← C のキャスト等で書く型
指定…分かりづらい□ 言語設計上の問題点• 任意の変数のポインタを取れるのは危険 (なくなった □ ただし C では「int (*f)()」のようにパラメタ部分を書かないことで「引数の個数と型を指定しない」ことが後も…)
可能→安全でない• C/C++→配列の添字式がポインタ演算 (「a[i]」は「*(a+i)」と同じ意味)• または引数のどこかから後を「...」と指定できる(可変引数)• a が配列、i が整数として「a[i]」でも「i[a]」でも同じって知ってます?

• 引数を「持たない」場合は「int (*f)(void)」と指• 非常に特異な言語設計、問題山積み (添字検査困難とか)。

定 ← 注記:C++ では「int (*f)()」でも同じ。C

では「int (*f)()」だと「パラメタは何でもよい」になってしまう。

□ 型という点では「T 型へのポインタ」→パラメタつき
の型。

2.10

• しかし値そのものは「レジスタに載るような値」

強い型と弱い型• 型 T と型*T を使い分けるのは混乱のもと (C++だと □ ここまでおもに「強い型の言語」(=コンパイル時にチェックする) について考えて来た。
さらに＆Ｔもある)• すべての値をポインタとするのも 1 つの選択肢 (Java• 弱い型の言語 (=実行時にチェックする) → Lisp、がこれに近い)Smalltalk、Perl、Ruby 他スクリプト言語• 弱い型の言語はチェックが抜けることはない (実行時に逐一チェックするから) →安全ではあるが、速度は□ 古い言語には「ポインタ型」が 1 種類しかない言語もあった (PL/I)。

犠牲になる• どんな型の変数でもアドレスを取ると同じ型→型安

• 型のエラーがあってもそこを実行してみるまで間違

全でない。

いがあることが分からない→製品を作る上では弱点• しかし C でもキャストすれば何型のポインタにでも• 強い型であれば、コンパイル時にチェックできる→実
行時には安全であることが保証される (ただし最近できる→安全でないことに変わりはない。ただ、キャストを書くことで「分かっていてやっている」といのオブジェクト指向言語では実行時のチェックにたよう意志表示にはなる。

2.9

る部分もある)。

関数型/関数へのポインタ型

2.11□ 関数を指すポインタ→複数の関数のどれを呼ぶか選択可

この節のまとめ□ プログラム言語/プログラムが持つ特徴

能に• 厳密だが「何が正しいか」は難しい (仕様の問題)• 関数 (サブルーチン) は呼ぶ以外の使い方はできない→わざわざ「〜のポインタ」としなくてもいい→そ• 1 つのことを何通りにも書ける

ういう言語もある。
□ プログラム言語が持つ多くの概念• C では「関数名を自動的にその関数を参照するポイ
ンタ値に変換」• 順次実行、制御構造、変数、型、…

6

□ 型→データの種別、対応する演算の種別□ 関数の弱点→かたまりになるのはあくまでも「コード」
だけという点→データの共有 (呼び側と呼ばれ側、呼ば• 型安全性→間違った操作が起こらない (突然死なない)

れ側どうし) は?

• 強い型→コンパイル時にチェックできるようにする• パラメタとして逐一渡す→繁雑、渡す側でもいじれてしまう

命令型からオブジェクト指向へ

3• グローバル変数として共有→どこでもいじれるから一層危険□ 命令型言語 (imperative language) → C のような、順• →結局、関数だけを「部品」として用いると複雑さ

次実行と変数の書き換えに基づくプログラミング言語に限界。← 1 レベルであること、データの共有問題• オブジェクト指向言語はその発展形と考えてよい• どのような経緯でオブジェクト指向言語ができたか?

モジュール

3.2

• 細かい話題は後まわしにして、ここでは全体的な概観□ モジュール以前の言語→手続きが単位

3.1

関数 (サブルーチン)• 手続きが単位だと「カタマリ」が小さすぎる• 複数の手続きが 1 つのデータ構造を共有する→よく□ 関数 (サブルーチン) とは、何ですか?

あるパターン□ 現象的には、「ある範囲のコードの先頭にジャンプし、そのコードを実行し、終わったら元の (ジャンプする前の)ところに戻って来る」

関数• 実装的には戻り番地 (ジャンプした命令の次の命令の番地) を保存しておいてそこへ間接ジャンプで戻る

関数• しかしそれが関数 (サブルーチン) だと言われても釈然としないでしょう?

関数

データ

□ そのデータ構造は「グローバル変数」じゃいけないの?

サブルーチン
先頭□ グローバルだと「よそからいじられる」可能性• データ構造→通常、「こういう性質を維持」という条件がある (整合性条件) →これが壊れるとそれを前提

戻り命令としているコードも破綻• グローバルだとそれを分からずにいじられてしまう□ プログラマから見ると→□ モジュール機構があれば、データ構造は特定のコードか• ひとまとまりのコードに名前をつけたもの

らだけしかいじれなくできる• その名前を指定して呼び出せる• 整合性条件の維持→そのデータをいじるコードが限• それぞれが完結した何らかの仕事をしてくれる (そ定されていると、そこだけきちんとすれば確実に大の細部は呼び側では知らなくてもよい)

丈夫□ 要するに「抽象化の手段」→最も重要な機能• さらに、外から直接呼ばれたくない関数も同様にして限定できるとよい。

• さっきの「入れ子をどうするか」の答えも第 1 義的にはここにある。

□ どうやって実装するか?

• 「いちど関数を書いてしまえば、言語にそういう命

言語にモジュールがあれば簡単だが、C 言語でも「ファイル内の static」を使ってあ令が増えたものとして扱える」

る程度できる□ 関数の利用が普及したのは構造化プログラミングの時期 □ 例:
以降 (1970〜)「図形描画できるウィンドウ」---GraphWin.c--#include <なんとか.h>static Window *win;
static struct { int x1,y1,x2,y2 } line[1000];• それ以前だと「1 万行のメインプログラムだけ」とか珍しくなかった7

---GraphWin.h--typedef void *GraphWin;
GraphWin create_gw();
int gw_addline(GraphWin gw, int x1, int y1, int x2, int y2);
int gw_update(GraphpWin gw);...

static int linecount;static int initialized = 0;
static int init() {win = ...
linecount = 0;}
int addline(int x1, int y1, int x2, int y2) {if(!initialized) init();line[lincount].x1 = x1; line[lincount].y1 = y1;line[lincount].x2 = x2; line[lincount].y2 = y2;++linecount;}
int update() {int i;
if(!initialized) init();for(i = 0; i < linecount; ++i) 線 i を描く;}...

---アプリケーション.c--include <GraphWin.h>
int main() {GraphWin gw = create_gw();...
gw_addline(gw, ...);...
gw_update(gw);...
}□ この「2 回ずつ言う」あたりを何とかしたのがオブジェ□ このコードでは「どういう整合条件」があると思うか?

クト指向の「メッセージ送信記法」と考えてもらえれば□ こうしておくと他にどういういいことがあるか?

よい。

□ 結局、モジュールの機能は「何もしなければアクセスできるものを隠す」こと→カプセル化 (encapsulation)

3.4• 「わざわざ不自由にすること」が実は重要□ いちいち上のようにヘッダファイルを使い分けたり、第3.3

1 引数でデータ構造のポインタを渡したりすると煩雑→
間違い→破綻。

抽象データ型□ これを言語の方で自動的にやってくれると嬉しい□ モジュールでは隠す対象は「データ一式」□ では窓を沢山作りたければどうする?

オブジェクト指向

class GraphWin {Window win;Line[] line = new Line[100];
int linecount;public GraphWin() {win = ...;
linecount = 0;}
public addLine(int x1, int y1, int x2, int y2) {line[linecount++] = new Line(x1,y1,x2,y2);}public upate() {for(int i = 0; i < linecount; ++i) 線を描く…
}...
class Line {int x1, y1, x2, y2;public Line(int px1, py1, px2, py2) {x1 = px1; y1 = py1; x2 = px2; y2 = py2;}
public int getX1() { return x1; }public int getY1() { return y1; }public int getX2() { return x2; }public int getY2() { return y2; }}}→抽象データ型---GraphWin.c--#include <なんとか.h>typedef struct {Window *win;
struct { int x1,y1,x2,y2 } line[1000];
int linecount; } GraphWin;
GrappWin* create_gw() {GraphWin *gw = (GraphWin*)malloc(siezof(GraphWin));gw->win = ...
gw->linecount = 0;
return gw;}
int gw_addline(GraphWin *gw,
int x1, int y1, int x2, int y2) {gw->line[lincount].x1 = x1;
gw->line[lincount].y1 = y1;
gw->line[lincount].x2 = x2;
gw->line[lincount].y2 = y2;++gw->linecount;}
int gw_update(GraphWin *gw) {int i;
for(i = 0; i < gw->linecount; ++i) 線 i を描く;}...

□ 使う側では次のようになる
GraphWin gw = new GraphWin();...
gw.addLine(...);...
gw.update();□ なぜこれが「抽象データ型」かというと…GraphWin 型というデータ型を新しく作っていると考えられる。たとえば利用側は:8• 初期設定とかポインタとかに煩わされなくなった□ ところで、上の例は C++ですか?
□ 答:□ 本講座では実際にオブジェクト指向言語を使ってレポー
トをやって頂きたい→具体的な言語の紹介も必要

Java ですか?

上の例は Java。

• 言語として「C++」「Java」の両方を取り上げる• C++では new はポインタを返すが、こういう場合は• 例題・課題ともできるだけ両方を対比させて用意する

ポインタを使う必要はない• C++では「その場に」(ローカル変数ならスタック上などに) オブジェクトが作れるように工夫されている• ただし題材によってはどちらか片方だけになるものもある

Line[] line = new Line[100]; //JavaLine line[100]; //C++、ただし vector<Line>とか
の方がよい

4.1

• C からスムーズに移行できるオブジェクト指向言語として普及• C に (ほぼ) 上位互換な言語。型検査とかは厳しくなっている

抽象データ型再訪• +αされているもの…「オブジェクト指向」「例外」「テンプレート」など□ 抽象データ型 (Abstract Data Types, ADT) とは…

• C の「裸のマシンがそのまま使える」に「よい構造化ができる」を追加• 「型」とそれが持つ「操作群」を定義する。
• その型の「内部構造」や「操作の実現」は外部から□ C++の設計思想…

は隠蔽• たとえば「＋」や「− 」などの演算を持つ「整数型」のようなもの (例:ベクトル型とか) を自前で定義できる。

• フルスピードで動く (C に負けない)• そのために足枷となるものは言語に導入しない• どのようなプログラミングスタイルでもサポートする

CLU、Alphard、…

• プログラマが分かっていてやることは禁止しない□ ADT の何が重要か?

• ユーザ定義型も組み込み型と同様な見た目で使える• 内部構造や実現を外部から見せない (カプセル化) → □ 例題:
モジュール間相互の依存関係を軽減。

2 つの数を読み込んで足す#include <iostream>using namespace std;

• オブジェクト指向言語も「単純な」クラス (上に挙げた例のようなもの) は単なる ADT を実現している。

ADT 的な考え方は重要 (それなりに慣れが必要)。

3.6に よって 、「C withclass」として始まった• 少し後で C++と Java を題材にもう少し具体的にやりますがその前に…

• 初期の ADT 言語:

C++入門□ C++ --- Bjane Stroustrup

GraphWin gw = new GraphWin(); //Java
GraphWin gw = GraphWin(); //C++

3.5

オブジェクト指向言語入門

4• メッセージ送信記法→「馬から落馬」がなくなった

この節のまとめ

int main(void) {int x; cout << "x? "; cin >> x;
int y; cout << "y? "; cin >> y;cout << "x+y = " << (x+y) << ’\n’;}• 「cin」「cout」は入出力ストリーム□ プログラム言語の重要な機能→「抽象化」• 「>>」「<<」(もともとはシフト演算子) を演算子定• ひらたくいえば「かたまり」の作り方

義により入出力演算として使っている• 関数 (サブルーチン) →データはかたまりにできない• 「>>」の結果はまたストリームなので連続して使えるのでいまいち• 多重定義により、「<<」を文字列用、整数用、実数用• モジュール→関数+データのパッケージ。より大きなと多数用意しているかたまり• 抽象データ型 (ADT) →データ「型」として沢山使える □ C++の処理系は…
• オブジェクト指向→ ADT+メッセージ送信記法+α• Windows 用 …Borland C++、MS Visulal C++な ど
色々ある9• 「オブジェクト指向」→プログラムが扱う対象をそれぞれ自律的な/完結した「もの」(オブジェクト) で• フリーなもの…GNU C++ (G++)。Unix (Linux) には普通ついている。Windows なら Cygwin を入れれあるとする「考え方」

ばそこに一緒にいれて動かせる (資料末尾も参照)

4.2• 「オブジェクト指向言語」→オブジェクト指向の考え方をサポートするようなプログラミング言語

Java 入門□ 抽象的でよく分からない?

□ Java --- 「C++よりも安全なオブジェクト指向言語」とoutstream ostream = ...;
outstream_println(ostream, "Hello, World.\n");↑関数名が長い↑操作対象も引数 : 従来のスタイル

して SunMicrosystems 社で開発された• 当初 Web ブラウザの上で動く「アプレット」のための言語として普及

outstream ostream = ...;
ostream.println("Hello, World.\n");↑オブジェクト. メソッド (引数…) : オブジェクト指向ぽい• この場合の「安全」…プログラムが悪さをできない
ような防壁がある (例: ファイルの読み書きができない、勝手にネットワーク接続ができない、など)• 同じことを 2 度言わなくて済む感じ• 現在では通常のソフトウェア開発用言語として使われている• C(や C++) とは制御構造の構文が似ているだけであとは別物• 同じことには同じ名前が使える• 名前空間の節約□ あと、とりあえず関連する用語を紹介しておく□ 「クラス」→オブジェクトの種類に対応する構文要素。

□ Java の設計思想…

• 新しい種類のオブジェクトを定義したければ、クラ• C++の危険さ、複雑さを減少させ単純さを求める

スを書く。

• C や C++との互換性は捨てたので言語仕様は単純化できた□ クラス定義に含まれるもの…

• 安全さを第一とし、危険なことはどうやってもできない• オブジェクトはどういう動作 (メソッド、メンバ関
数) を持っているか• すべてのオブジェクトはヒープ上に割り当て、ごみ• オブジェクトはどういう属性を持っているか←イン

集めを行う

スタンス変数、メンバ変数• Smalltalk 的、伝統的なオブジェクト指向言語ふう• そのほかクラスはモジュールとしての役割りも←ク
ラスメソッド変数、static メソッド/変数• これらの代償として見た目は長く、動作は遅くなり
がち
□ 例題:

まあそうだと思いますが…

2 つの数を読み込んで足す

import java.util.*;□ 「インスタンス」→クラス定義に基づいて作り出された「もの」(オブジェクト)□ 逆に見ると→「新しいモノ」を定義できるのがオブジェ

クト指向
public class Sample1 {public static void main(String[] args) throws Exception {• 世の中はさまざまなモノから成り立っている→それ
Scanner sc = new Scanner(System.in);
System.out.print("x? ");をそのまま言語に移せる (プログラムだから現実世
int x = sc.nextInt();
界よりは杓子定規だけど…) →考えやすい (考えやす
System.out.print("y? ");さは重要)
int y = sc.nextInt();
System.out.println("x+y = " + (x+y));}□ どういうモノを作るか考える→オブジェクト指向におけ
}
る設計

4.3

• (1) どんなモノを作るか

オブジェクト指向言語• (2) それぞれのモノはどういう操作/インタフェースを持つか□ オブジェクト指向言語とは?→いろいろな定義があると• (3) その実装はどうするか←ある意味どうでもいい/
別建て

思うがここでは一応次のように考える。

• (上記は ADT の考え方と言った方が正確← ADT は重要)
10

4.4

例:じゃんけんの手□ public:以下にメソッド (メンバ関数) のインタフェー
スを書く→これを (ヘッダファイルとして) 取り込むこ□ 「じゃんけん」のデータを多数扱うアプリがあるとする• 従来手法:とで型検査可能に

たとえば"Goo"、"Paa"、"Choki"の文• クラス名と同名のメソッド→「コンストラクタ」(初字列で扱う→何がよくない?

• 従来手法:期化用の特別なメソッド)。

たとえば 0、1、2 をグー、パー、チョキ• メソッドは同名のものが複数あっても引数の数と型で区別できれば OK(オーバーローディング、多重定義)に対応→何がよくない?

• じゃあどうするか?

• 「変更しない」ことを const で表す (型もメソッドも) 。

□ 「じゃんけんの手」をデータ型とする• 「operator △」で「△」という演算子を定義可能。

• 常に「3 つの手のどれか」であることが保証される• unknown hand というクラスは「例外の種別を表す」
ために使う。

• どういう操作を持たせるか?

□ C++のクラス:実装部分□ 必要な操作• 作る (文字列→手、ランダムな手)• 表示 (手→文字列)• 勝ち負け引き分けの判定

4.5

じゃんけんの手:C++版□ C++のクラス:インタフェース (ヘッダに入れる部分)
class RPS { // Rock Paper Scissors
int hand;enum { rock = 0, paper = 1, scissors = 2 };static const char* const names[3];static const int wl[3][3];public:RPS();
RPS(const char* const s);
const char* c_str() const;bool operator==(const RPS& r) const;bool operator!=(const RPS& r) const;bool operator<(const RPS& r) const;bool operator<=(const RPS& r) const;bool operator>(const RPS& r) const;bool operator>=(const RPS& r) const;class Unknown_hand { };};const char* const RPS::names[3] = {"Rock", "Paper", "Scissors"};
const int RPS::wl[3][3] = {{0,-1,1}, {1,0,-1}, {-1,1,0}};• 「class クラス名 { ...

};」がクラスを定義• public:より前は private 部分→クラス外からは見えない (実際にはコンパイラは見るが) →カプセル化、ADT の保護

• 変数:「この型のデータ (インスタンス) は何と何を組にしたものか」を表す• static は全インスタンスに共通のデータ (共有されRPS::RPS() {hand = int((double(rand()) / RAND_MAX)* 3);}
RPS::RPS(const char *s) {switch(*s) {case ’R’: case ’r’: case ’G’: case ’g’:
hand = rock; break;case ’P’: case ’p’:
hand = paper; break;case ’S’: case ’s’: case ’C’: case ’c’:
hand = scissors; break;default: throw Unknown_hand();}}const char* RPS::c_str() const {return names[hand];}bool RPS::operator<(const RPS& r) const {return wl[hand][r.hand] < 0;}bool RPS::operator<=(const RPS& r) const {return wl[hand][r.hand] <= 0;}bool RPS::operator>(const RPS& r) const {return wl[hand][r.hand] > 0;}bool RPS::operator>=(const RPS& r) const {return wl[hand][r.hand] >= 0;}bool RPS::operator==(const RPS& r) const {return hand == r.hand;}bool RPS::operator!=(const RPS& r) const {return hand != r.hand;}• 「クラス名::名前」でクラスに付属するもの (メンバ) の名前が指定できる。定義自体は class { ...
} とは別に書く。
• static な変数も定義を外の 1 個所に置く必要。
• メソッド (メンバ関数) の中は「クラスの中」だから
private なものも参照可能。

る表とか)• 文字列はここでは「C の文字列」を返す。string ク
ラスとか色々あるが。

11

□ C++のクラス:メイン

public static void main(String[] args)
throws Exception {final int n = 5;
RPS[] a = new RPS[n], b = new RPS[n];Scanner sc = new Scanner(System.in);for(int i = 0; i < 3; ++i) {a[i] = RPS.newHand(); b[i] = RPS.newHand();}for(int i = n-2; i < n; ++i) {System.out.print("a hand? ");a[i] = RPS.valueOf(sc.nextLine());
System.out.print("b hand? ");b[i] = RPS.valueOf(sc.nextLine());}
for(int i = 0; i < n; ++i) {String s = "draw";
if(a[i].winlose(b[i]) > 0) s = "a win";if(a[i].winlose(b[i]) < 0) s = "b win";
System.out.println(a[i] + ":" + b[i] +" --- " + s);}}#include <iostream>#include <string>#include <cstdlib>
using namespace std;//ここにヘッダ部分//ここに実装部分 (別ファイルにしてもよい)
int main(void) {const int n = 5;
RPS a[n], b[n];for(int i = 3; i < n; ++i) {char sa[n], sb[n];cout<<"a hand? "; cin>>sa; a[i] = RPS(sa);cout<<"b hand? "; cin>>sb; b[i] = RPS(sb);}for(int i = 0; i < n; ++i) {char *s = "draw";
if(a[i] > b[i]) s = "a win";if(a[i] < b[i]) s = "b win";cout << a[i].c_str() << ":" <<b[i].c_str() << " --- " << s << "\n";}}}• Java ではヘッダファイルがなく、前方参照も OK(コンパイラの作りがより新しい感じ)• 「配列オブジェクトを作る」ことと「そこに入れる• RPS 型 5 個の配列→コンストラクタにより初期化→

オブジェクトを作る」ことは常に別。(値の配列ならランダムな手が最初から入っている

0 が入るのだけど…)• 2 つだけ手を読み込む→文字列を読んでコンストラ• 構文上の細工はあまりなく、基本的にすべてメソッ
ド呼び出し形式だけで書く。

クタで RPS 型を作り代入。

• 最後にどちらが勝っているか/引き分けかを表示。
「> 」「<」等は定義された演算子を呼ぶ。
□ クラス RPS は…enum なので制約があるがインスタンス
変数やメソッドを持たせることができる

4.6

じゃんけんの手:Java 版

enum RPS {ROCK, PAPER, SCISSORS;
static final int[][] wl = {{0,-1,1}, {1,0,-1}, {-1,1,0}};public int winlose(RPS r) {return wl[ordinal()][r.ordinal()];}public static RPS newHand() {RPS[] hands = values();return hands[(int)(Math.random()*hands.length)];}}□ Java では JDK 1.5 以降で「Enum 型」を導入。Enum 型とは要するに C++で作ったようなクラスを「ひとことで」作る機能。
enum RPS { ROCK, PAPER, SCISSORS }• これにより以下のようなメソッドを持つクラスが作られる:• static RPS RPS.ROCK, RPS.PAPER,
RPS.SCISSORS --- 3 つ の 値 に 対 応 す る イ ン ス• static の意味は C++と同じ。

タンスを保持する final 変数 (定数)• winlose は勝ち負けに応じて「正負零を返す」よう• public static RPS[] values() --- 3 つの値を格納した配列にした。

• enum でなく普通のクラスの例→次の節で• public static valueOf(String s) --- 文 字 列から 3 つの値に変換• public int ordinal() --- 何番目の値かを返す

4.7

分数電卓:C++版□ enum はクラスなのでメソッドを追加したりできる→そ □ 分数をあらわすクラスを用意する。方針としては 2 つの数 a、b で分数 a/b を表す。まず宣言部。
れを利用したじゃんけんの手の main()class Rational { // a/b
int a, b;
int gcd(int x, int y);import java.util.*;public class Sample2 {12• 面倒な「約分」の計算はコンストラクタで。
public:Rational(int x);□ メイン部分。
Rational(int x, int y);
Rational operator+(const Rational& r) const;#include <iostream>
Rational operator-(const Rational& r) const;#include <string>
Rational operator*(const Rational& r) const;#include <cstdlib>
Rational operator/(const Rational& r) const;
friend ostream& operator<<(ostream& o, Rational& r); using namespace std;friend istream& operator>>(istream& i, Rational& r);// ここにヘッダ部分};// ここに実装部分• private 部分には変数と補助関数 GCD(最大公約数)。
• コンストラクタは値 1 つ (分母 1) と 2 つ。
• 四則は演算子。
• 分母 0 になる場合は NaN (Not a Number)。
• 入出力は単独関数として定義した演算子「>>」と「<<」。(なぜ単独関数かというと、クラス istream や
ostream に後からメソッドの追加はできないから。)
第 1 引数は入出力ストリーム。firend 宣言は「この関数は特別にこのクラス定義の中身 (private 部分)にアクセスできる」という意味。
□ 実装部分。

int main(void) {Rational r(1), x(0);while(true) {char c; cout << "? "; cin >> c;
if(c == ’q’) return 0;
switch(c) {case ’q’: return 0;case ’=’: cin>>x; r = x; break;case ’+’: cin>>x; r = r + x; break;case ’-’: cin>>x; r = r - x; break;case ’*’: cin>>x; r = r * x; break;case ’/’: cin>>x; r = r / x; break;default: continue;}
cout << r << ’\n’;}}

int Rational::gcd(int x, int y) {while(x != y) if(x > y) x -= y; else y -= x;• 1 文字読んでコマンド文字によって動作を切替え。例:
return x;}? = 1 3 ← 1/3 を入れる
Rational::Rational(int x) { a = x; b = 1; }1/3
←現在値
Rational::Rational(int x, int y) {? + 1 6 ← 1/6 を足す
if(y == 0) { a = b = 0; return; }1/2←現在値
if(x == 0) { a = 0; b = 1; return; }q←おしまい
if(y < 0) { x = -x; y = -y; }if(x == 0) {a = x; b = y;
4.8 分数:Java 版} else if(x > 0) {a = x / gcd(x,y); b = y / gcd(x,y);□ C++版と同じ方針で実装。こちらはメイン側から。
} else {a = x / gcd(-x,y); b = y / gcd(-x,y);
import java.util.*;}}public class Sample3 {Rational Rational::operator+(const Rational& r) const {public static void main(String[] args)
return Rational(a*r.b + r.a*b, r.b*b);throws Exception {}Scanner sc = new Scanner(System.in);
Rational Rational::operator-(const Rational& r) const {Rational r = new Rational(1);return Rational(a*r.b - r.a*b, r.b*b);while(true) {}System.out.print("? ");Rational Rational::operator*(const Rational& r) const {String s = sc.nextLine();return Rational(a*r.a, r.b*b);if(s.charAt(0) == ’q’) return;}
Rational x = new Rational(s.substring(1));
Rational Rational::operator/(const Rational& r) const {switch(s.charAt(0)) {return Rational(a*r.b, r.a*b);
case ’=’: r = x; break;}case ’+’: r = r.add(x); break;ostream& operator<<(ostream& o, Rational& r) {case ’-’: r = r.sub(x); break;if(r.b == 0) o << "NaN";
case ’*’: r = r.mul(x); break;else o << r.a << ’/’ << r.b;case ’/’: r = r.div(x); break;return o;default: continue;}}
istream& operator>>(istream& i, Rational& r) {System.out.println(": " + r);
int a, b; i >> a >> b;}
r = Rational(a, b); return i;
}
}
}

13

• 値〜数値、文字、論理値。

• こちらはコンストラクタに文字列を渡せるように 作った 。文 字 列 を 空 白 で 区 切って 扱 う ク ラ ス• オブジェクト→ C++でも Java でもクラスにより定義。

StreamTokenizer を活用。

• ポインタ型/参照型→ C++にのみ存在。Java ではオ
ブジェクト値は全部ポインタのようなもの。

• 動かし方は C++版と同じ。
□ Rational クラスは内容的にはほぼ同じ。

□ C++と Java で一番違うところは:

class Rational {• C++ではオブジェクトは「配列や実行スタック上に直int a = 0, b = 0; // b == 0 -> Not a Number
接その記憶域を配置できる。外に配置するときはポ
public Rational(String s) {try {インタを使う。
StringTokenizer tok = new StringTokenizer(s);• Java オブジェクトはすべてヒープ上のオブジェクト。
if(tok.countTokens() == 1) {オブジェクトはすべてポインタで扱う (Java の用語
a = Integer.parseInt(tok.nextToken()); b = 1;
} else if(tok.countTokens() == 2) {では「参照」) →最も重要な単純化。動的データ構造
a = Integer.parseInt(tok.nextToken());
や多態のためにはどのみち参照が必要。そのため、すb = Integer.parseInt(tok.nextToken());}
べてポインタに統一している。そのために遅い面も。
} catch(Exception ex) { }class point {}double x, y;... }public Rational(int x) { a = x; b = 1; }public Rational(int x, int y) {ppxxif(y == 0) { return; }C++
if(x == 0) { a = 0; b = 1; return; }yyJavaif(y < 0) { x = -x; y = -y; }if(x == 0) {C++class Rect {a = x; b = y;Point p1, p2;... }} else if(x > 0) {a = x / gcd(x,y); b = y / gcd(x,y);rrxx} else {C++
a = x / gcd(-x,y); b = y / gcd(-x,y);
y
y
Java
}
x
C++
}
x
public Rational add(Rational r) {yreturn new Rational(a*r.b + r.a*b, r.b*b);y}public Rational sub(Rational r) {return new Rational(a*r.b - r.a*b, r.b*b);• そのため、Java ではごみ集め (GC、後述) が必須。
}
C++ではごみ集めはオーバヘッドがあるので言語本体
public Rational mul(Rational r) {return new Rational(a*r.a, r.b*b);
としては提供しない→手動メモリ管理が前提 (うま}くやれば効率はよいが面倒、間違えるとメモリリー
public Rational div(Rational r) {クしたり落ちたりする)。
return new Rational(a*r.b, r.a*b);}□ C++はオブジェクトも書き方を値に近くできる。Java はpublic String toString() {if(b == 0) return "Nan"; else return a + "/" + b; それはしない。
}
private int gcd(int x, int y) {• C++では「==」などの演算子を定義できる→オブジェ
while(x != y) if(x > y) x -= y; else y -= x;
クトでも演算子を使って書く。
return x;}• Java では「値→演算子」「オブジェクト→メソッ}

ド」ときっぱり区分されている。
「v1 == v2」(値)、• 演算子定義はないので適当なメソッド名をつける。

「o1.equals(o2)」。もしオブジェクトに「o1 == o1」• 出力も演算子はないので toString() で文字列へのを使うと「ポインタ比較 (同一のオブジェクトかど
うかを調べる)」になる。

変換方法を定める (文字列が必要なときは自動的にこれが呼ばれる)。

4.9□ オブジェクトの初期設定と後始末は…

• C++ではコンストラクタとデストラクタ (名前が「〜

値とオブジェクト

クラス名」) を用意する。

□ C++も Java も「値」と「オブジェクト」を区別している。

14

• なぜかというと、Java では「オブジェクトでないと不便なこと」が色々ある。C++は (テンプレート機構• ローカル変数のオブジェクトはスコープに入った時にコンストラクタが呼ばれ、出るときにデストラクを使って) オブジェクトでも値でも変わり無く扱えタが呼ばれる。

るようにするのが普通なのでその必要がない。
• ヒープ上のものは「new クラス名 (…)」でメモリ割
り当て+コンストラクタ、「delete オブジェクト」で □ 分割コンパイル→ C++はやはりちょっと古い。Java はよデストラクタが呼ばれる。
りスマート。

• Java で は「new ク ラ ス 名 (…)」と メ ソッド• C++はクラス定義+各メンバの定義。クラス定義はデー

finalize()。ただし後者は GC が領域を回収するときに呼ばれるので、呼ばれない場合も。

タの定義+メソッドインタフェースの定義。ヘッダファ
イルに入れる (そうしないとオブジェクトの大きさが分からないので使う側がコンパイルできない)。テ

4.10

Java とごみ集め (GC)

ンプレートだとさらにそういう感じ。

• Java は 1 つのクラス定義に全部書く。インタフェー□ Java のコード→どんどん必要なオブジェクトを生成し、ス情報は.class ファイルに書き出され、分割コン

使わないものは GC で回収、というスタイル。

パイル時はコンパイラがこれを参照する。

• GC のオーバヘッドは織り込み済みという割り切りが必要。

• 自分で領域開放するよりずっとよい。自前でやると

4.12

例外処理

コードが複雑化し、また重大な誤りの原因になりや □ 最近の言語で加わった新しい制御構造 (Lisp などでは
古くからある)すい。
• GC は Lisp あたりでは古くから使われているが、普 □ もともとの問題: エラー検出はどうやるのがいいのか?
通の手続き型言語で標準としたのは Java がはじめて□ GC の原理:

status = some_func(...);
if(status != OK) {エラー処理…
}

変数から参照できるオブジェクト群を順次たどって行き、たどったという印をつける。印がつかな
かった領域はごみとして回収• どういう問題があるか?

• その他、使っているオブジェクトだけコピーする方 □ 返り値が使えなくなってしまう
式、オブジェクトがそれぞれ何個所から指されてい□ 広域変数にするとマルチスレッドとか問題
るか常に数えおく方式などもある。
□ エラー処理が各所に挿入されると処理の流れが見づらい• GC で自動的に回収するためには「不要なデータ構造• どこか 1 箇所で処理したいが goto はあまり使いたは指さないようにする」(指している所に null を入くない

れるなどしてつながりを切る) ことが必要• エラーに飛び出したために後始末が飛ばされたりす
ると困る• 「もう要らないからこれ回収して」とは言えない (なんで?)□ エラー処理のための制御構造を導入→「例外」処理

4.11

try {
... 何かあるかも知れない... 処理をいくらでも書く} catch(Exception ex) { ←エラー情報受け取り
ここでまとめて処理}

プログラム構造□ C++はクラスと単独関数 (と struct と union と) がある。Java はクラスのみ。

• C++では文字列→数値変換などは (C に由来する)atoi□ 例外を発生させる側は…
などの関数利用。
• Java で は そ の よ う な も の の 置 き 場 所 と し て「Integer」「Double」などのクラスがある。

throw new 例外クラス名 (...); //Java
throw 例外クラス名 (); //C++• これらのクラスは int や double の値を「オブジェ• ここまでの書き方は Java と C++でほとんど同じ。
Java ではオブジェクトは全部ポインタだからこのままクトにくるんで扱う」ためにも使われる。

でいいが、C++では受け取るところをコピーを避けるため参照で受け取るのが普通。「catch(Exception&

ex)」
15

• Java で受け止めないでもいいのは自分も同じ例外をthrows で明示している場合のみ。ただし、すべてのメ□ 「どの範囲のエラー」という取捨選択が可能→クラスの階層関係

ソッドは「throws Error, RuntimeException」は• Java では finally 句で「必ずやりたい後始末」が指定できる• C++は finally がない→ローカル変数のデストラク
タ等を活用
try {
...
try {
... ←この中で起きたエラーで} catch(NumberFormatException e1) {... ←数値書式エラーはここへ来る} finally {必ずやる後始末}...
} catch(Exception ex) {... ←他のエラーは直接ここへ来る}

はじめから指定されているものと見なされる

4.13

整数の集合: C++版

□ より複雑なものの例として「整数の集合」型を作ってみる。
static const int maxsize = 100;class Intset { // set of strings
int count;
int arr[maxsize];
void add1(int x); // internal use only
public:Intset();int size() const;bool is_in(const int i) const;
Intset operator+(const Intset &s) const;
Intset operator-(const Intset &s) const;
Intset operator*(const Intset &s) const;class Overflow { };friend ostream& operator<<(ostream& o, Intset& s);friend istream& operator>>(istream& i, Intset& s);};□ 受け止めなかった例外は?

• メソッド呼び出し側に返される (そこに try-catchがあればそこで受け止められる)• 最後まで受け止められなければプログラム実行を中止□ 例外種別の分類→階層構造• 実装の方針→配列を用いて整数の列を保持。とりあ
えず最大固定。

• Java で は す べ て の 例 外 が 階 層 構 造 に 統 一 →Throwable を受け止めれば必ずすべてが受け止められる
Throwable ←例外すべて
Error ←システム上の問題
OutofMemoryError...
Exception ←普通の例外
IOException
InterruptedException
RuntimeException ←どこでも起きるような例外
NumberFormatException
NullPointerException...

• インタフェースはとりあえず最低限:□ 実装:• C++では言語上は例外オブジェクトは何でもよい。す
べてを受け止めたい場合は「catch(...)」というヘンなものを使う。
□ メソッドがどのような例外を返すかはコンパイラがチェッ
ク可能。

• いずれもメソッド引数の後に、C++は「throw (名前,…)」Java では「throws 種別, ...」という形。

• C++では「書かないと何でもよい」「受け止めなくてもよい」• Java では「原則として自分が発生させる例外は明示」「自分が呼ぶメソッドで発生する例外は原則として受け止めて処理」

16

Intset::Intset() { count = 0; }void Intset::add1(int x) {if(count+1 >= maxsize) throw Overflow();arr[count++] = x;}
int Intset::size() const { return count; }bool Intset::is_in(const int i) const {for(int k = 0; k < count; ++k)if(arr[k] == i) return true;
return false;}
Intset Intset::operator+(const Intset &s) const {Intset r = s;for(int k = 0; k < count; ++k)if(!s.is_in(arr[k])) r.add1(arr[k]);return r;}
Intset Intset::operator-(const Intset &s) const {Intset r;for(int k = 0; k < count; ++k)if(!s.is_in(arr[k])) r.add1(arr[k]);return r;}
Intset Intset::operator*(const Intset &s) const {Intset r;for(int k = 0; k < count; ++k)if(s.is_in(arr[k])) r.add1(arr[k]);return r;}

ostream& operator<<(ostream& o, Intset& r) {switch(s.charAt(0)) {o << "{ ";case ’=’: r = x; break;for(int k = 0; k < r.count; ++k) o << r.arr[k] << ’ ’;case ’+’: r = r.add(x); break;case ’-’: r = r.sub(x); break;o << ’}’; return o;case ’*’: r = r.mul(x); break;}default: continue;
istream& operator>>(istream& i, Intset& r) {}r.count = 0;
System.out.println(": " + r);
while(i.peek() != ’\n’) {}int x; i >> x; r.add1(x);}}}i.get(); return i;}□ クラス側• 下請けメソッド add1() はあふれチェックして要素を追加する。

• 演算のメソッドは 2 つの集合に含まれているかどうかをそれぞれチェックしながら動作。
• 出力は書くだけ。入力は読み込みながら add1() を呼ぶ。
□ メイン部分は先の例とほとんど代わらない#include <iostream>#include <string>#include <cstdlib>
using namespace std;// ここにヘッダ部分// ここに実装部分
int main(void) {Intset r, x;
while(true) {char c; cout << "? "; cin >> c;
if(c == ’q’) return 0;
switch(c) {case ’q’: return 0;case ’=’: cin>>x; r = x; break;case ’+’: cin>>x; r = r + x; break;case ’-’: cin>>x; r = r - x; break;case ’*’: cin>>x; r = r * x; break;default: continue;}
cout << r << ’\n’;
}
}

4.14

整数の集合: Java 版□ main 側。これもこれまでと同様。
import java.util.*;public class Sample4 {public static void main(String[] args)
throws Exception {Scanner sc = new Scanner(System.in);
IntSet r = new IntSet();while(true) {System.out.print("? ");String s = sc.nextLine();if(s.charAt(0) == ’q’) return;
IntSet x = new IntSet(s.substring(1));

17class IntSet {int[] arr;
int count = 0;
public IntSet(String s) {try {StringTokenizer tok = new StringTokenizer(s);arr = new int[tok.countTokens()];for(int i = 0; i < arr.length; ++i) {int x = Integer.parseInt(tok.nextToken());
if(!is_in(x)) add1(x);}} catch(Exception ex) { }}public IntSet() { arr = new int[0]; }private IntSet(int c) { arr = new int[c]; }private void add1(int x) {if(count+1 >= arr.length) {int[] a = new int[arr.length*2 + 1];
for(int i = 0; i < count; ++i)a[i] = arr[i];arr = a;}arr[count++] = x;}public int size() { return count; }public boolean is_in(int x) {for(int i = 0; i < count; ++i)if(arr[i] == x) return true;
return false;}
public IntSet add(IntSet r) {IntSet s = new IntSet(size() + r.size());
for(int i = 0; i < count; ++i) s.add1(arr[i]);for(int i = 0; i < r.count; ++i)
if(!is_in(r.arr[i])) s.add1(r.arr[i]);return s;}
public IntSet sub(IntSet r) {IntSet s = new IntSet(Math.max(size(), r.size()));
for(int i = 0; i < count; ++i)if(!r.is_in(arr[i])) s.add1(arr[i]);return s;}
public IntSet mul(IntSet r) {IntSet s = new IntSet(Math.max(size(), r.size()));
for(int i = 0; i < count; ++i)
if(r.is_in(arr[i])) s.add1(arr[i]);return s;}
public String toString() {if(count == 0) return "{ }";String s = "";for(int i = 0; i < count; ++i)• add1() で配列が満杯になったら増やす (Java 版と同様)。

s += " " + arr[i];return "{" + s.substring(1) + "}";}

• あとの演算子等は変更不要。これで完成…と思いま}

すか?

• Java では配列は常に「別オブジェクトへの参照」になる• 必要な容量を見積もってその大きさで用意し、容量が足りなくなったらより大きいものを用意して差し□ ヘンなコンストラクタと演算子が増えていると思いませんでしたか?
Intset(const Intset &s);
Intset& operator=(const Intset& s);

替え。

• 使わなくなった古い配列はごみ集めにより回収• 「コピーコンストラクタ」と「代入演算子」。これらを用意しないとまずい (なぜか分かりますか?)

4.15

整数の集合: C++2 版□ 通常のコピー (引数渡し等)、代入動作→オブジェクト□ C++でも配列が足りなくなったら増やせるようにするには…(クラス Intset 部分だけ差し替え)のそれぞれのインスタンス変数をコピー。先のバージョンではこれでよかったが…

class Intset { // set of strings• 変数 arr をコピーする→ポインタのコピーであって
int count, limit;
配列はコピーされない (共有された状態になる) →そ
int *arr;
れでは困る (副作用とか)。
Intset(int c);void add1(int x);• このため、「コピーコンストラクタ」と「代入演算子」
static int min(int x, int y) { return (x<y)?x:y; }を作成して、その中で arr が指す配列をコピーする。
public:Intset();□ 具体的なコード (この種のものとしては典型的)
Intset(const Intset &s);
~Intset();Intset::Intset(const Intset &s) {Intset& operator=(const Intset& s);arr = new int[s.count];
int size() const;
limit = count = s.count;bool is_in(const int i) const;
for(int i = 0; i < count; ++i)
Intset operator+(const Intset &s) const;arr[i] = s.arr[i];Intset operator-(const Intset &s) const;}
Intset operator*(const Intset &s) const;
Intset& Intset::operator=(const Intset& s) {friend ostream& operator<<(ostream& o, Intset& s);if(this != &s) {friend istream& operator>>(istream& i, Intset& s);count = 0;};
for(int i = 0; i < s.count; ++i)
add1(s.arr[i]);• 変数 arr は配列ではなく整数へのポインタ (=配列を}return *this;指すポインタとして使う) に。
}• 例外 Overflow は不要に。

• 代入の場合は「自分への代入は何もしない」を入れておくのが通例。

□ コンストラクタで配列のサイズ指定。

Intset::Intset(int c) {count = 0; limit = c; arr = new int[c];}4.16書き換え可能/書き換え不能
Intset::Intset() { count = limit = 0; arr = new int[0];}Intset::~Intset() { delete[] arr; }□ 書き換え不能 (immutable) →オブジェクトの状態が変void Intset::add1(int x) {if(count+1 >= limit) {化しないことint *a = new int[limit*2+1];
for(int i = 0; i < count; ++i)
• Rational オブジェクトも Intset オブジェクトも内a[i] = arr[i];部的には変数を持っていて値が変化することは可能
delete[] arr; arr = a;だが、外からメソッド経由で使っているぶんには値
limit = limit*2+1;}を変化させることはできない。
arr[count++] = x;
• 書き換え不能だと副作用の心配がない}• その代わりちょっとでも変更したいときは新しいオ• デストラクタが必要に (このオブジェクトが不要にブジェクトを作ることになる

なったら指している配列も不要になるから)。

18□ 書き換え可能 (mutable) →オブジェクトの状態が変化• ヒ ン ト: C++で 時 間 計 測 を す る に は 標 準 関 数
clock() を利用するとよい。

すること#include <ctime>...
clock_t t1 = clock();
計測したい処理
clock_t t2 = clock();
double sec = double(t2-t1)/CLOCKS_PER_SEC;↑秒単位に換算• 例えば Rect で「moveTo(x,y) で位置移動」Intset
で「add1(x) で直接集合に文字列 s を追加」などとすると書き換え可能になる• 配列オブジェクトなどが書き換え可能なものの典型例• CLU など「書き換えられない配列」を持つ言語もあ• ヒント: Java の時間計測には System クラスのク
ラスメソッド System.currentTimeMillis() を利用するとよい。

った• 書き換え可能だと変更する時の効率はよい• その代わり思わぬ副作用が起きないか注意する必要

long t1 = System.currentTimeMillis();
計測したい処理
long t2 = System.currentTimeMillis();long dt = t2 - t1; ←所要時間 (msec)がある□ 自分がクラスを作る時はどちらにするか十分検討して考える□ (2) 例題の RPS クラス、Rational クラス、Intset ク
ラスと同様な抽象データ型を自分でも作り、
「電卓」部

4.17

分も合わせて直して動かせ (たとえば「復素数」「ベクト

この節のまとめル」「方位」「時刻」など自由に考えてよい)。どんな演□ C++は C を土台にオブジェクト指向機能を追加して作っ

算やメソッドを用意するかも自分でデザインすること。

た言語

なぜそうデザインしたかも報告する。
□ (3) 例題の Intset クラスはプログラム内で生成した整• ポリシーとして「C と同程度の実行効率」。
• このため「プログラマが責任を持つ」ことは多め。

数データをもとに集合を作る方法を提供していない。こ□ Java は C++のアンチテーゼとして「安全優先」で作らや積集合を計算するメソッドの実行時間との関係を、あれを追加せよ。次に、2 つの集合の大きさ M、N と和集合れた言語る程度大きな M、N で複数通り試してグラフに描け。この実行時間を高速化する方法を考えて実装できるとなお• このため「最初から危ないことはできない」言語設よい。

計。効率は犠牲に。

• 注記: 実験用に乱数が必要なら「じゃんけん」の例
題を参照。

□ ここまででは抽象データ型 (ADT) としてのクラスの利用に限定して説明した (これだけでも十分重要だし複雑)。

• ヒント:• 次回はオブジェクト指向ならではの機能を取り上げる。

5

配列 arr 内で整数が大小順に整列されるようにしておくと、和集合や積集合の計算が速くできる。

• ヒント: 別の方法として、配列に順に詰めるのではなくハッシュ表などを使ってもよい。API ドキュメン

演習問題

トで調べて Java 標準ライブラリに含まれるハッシュ□ 下記 (1)〜(4) のうちから 1 つ以上を選び実験を行い、表のクラスを活用してもよい。

結果をレポートせよ。やったことの説明や書いたコードだけでなく、計測の場合は方法まで説明し、きちんと考□ (4) 2 つの Intset どうしが同じ集合であるかどうかを察まで書くこと。言語は特に指定していない場合、C++判断するメソッドを実装せよ。また、その性能についてでも Java でもよい (両方でやって比べてみるとなおよ

前 2 問と同様に所要時間のモデルを考え、計測により確い)。期限はリーダから指定。

認し、さらに改良を試みよ。

□ (1) Rational オブジェクトや Intset オブジェクトの例
題を動かし、その「足し算」などの演算が int や doubleなどの数値の演算の何倍遅いか計測してみよ。計測する
前に予測し、予測がどれくらい合っていたか検討すること。(予測はあてずっぽうではなく、何らかの根拠が
必要)。

19

• 注記: 前 2 問で行なった改良が含まれた状態でやってもよい。
• ヒント: 集合に含まれる文字列群全体のハッシュ値を計算し、集合が異なればハッシュ値も異なるようにしておくと、異なる場合の判断はすごく速くできる。