第2回 C++・オブジェクト指向の基礎(オブジェクト・クラス)


オブジェクト指向プログラミングの広まりの歴史

オブジェクト指向言語の歴史は意外と古く、 1960年代にシミュレーション用の言語として作られたSimulaという言語が起源と言われています。 ただ、この言語やその処理系を知っている人はほとんどおらず、有名になったのは、1970年代に生まれた言語Smalltalkの登場以降だと思います。 当時非常に高価でしたが、Xerox Parcなどのマシンの上で1980年代にSmalltalk-80が実装されて使えるようになってから、一部の富裕層のプログラマーがオブジェクト指向に触れることができるようになりました。 今も定義としてよく引用される、「メッセージを送りあうオブジェクト」という概念は、Smalltalkで使われていたことから広まったとされています。 ただし、「メッセージを送りあう」という言葉は、C++言語やJava言語のような目下代表的なオブジェクト指向言語と言われる言語では、あまり使われなくなっています。

歴史的な話に戻ると、プログラム言語を学習する立場のユーザが、オブジェクト指向言語をもっとも身近な存在であると感じ始めたのは、80年代後半から90年代前半にC++言語の処理系がBorland社やマイクロソフト社のソフトウェアとして、いわゆるPC(パーソナルコンピュータ)で実装されて動かせるようになってからです。 また、ほぼ同時期にサンマイクロシステムズ社のワークステーション上で、Sun Solarisオペレーティングシステム(ほぼUNIXと同じ)のもと、C++コンパイラがサポートされていった時期とも並行します。いまでは、我々はGNUの良質なISOに準拠したg++コンパイラを無料で使用できますが、当時(1990年ごろ)は、g++の品質がまだ悪く不安定だったため、開発用のユーザインタフェースが付随していた有料のBorland社等のC++コンパイラを求めて、米国の大学に留学中、大学生協(売店)で買い求めたのを覚えています。ちなみに当時のPCには16ビットOSであるWindows3.1が主流でした。

1995年にWindows 95が発表され、インターネットも世界的に利用できるようになり、同年Java言語が登場したころまでには、g++の品質も向上し、また、新たなオブジェクト指向言語としてのJava言語に触れることができるようになってやっと、大学でも企業でも、演習やOn-the-Jobトレーニング(OJT)の一環で、オブジェクト指向言語に普通に触れることができるようになりました。 とはいえ、多くの大学や企業でC++言語やJava言語を教えるようになったのは、2000年以降かと思います。

オブジェクト指向プログラミングの真髄

オブジェクト指向言語がC言語、FORTRAN等の通常の手続き型言語と違う点は、いうまでもなく、 オブジェクト を生成し、これを使って一連の仕事(プログラミング)をする、という考え方にあります。 そもそも、「オブジェクト」って何でしょうか? このよさが理解できるのは、おそらく、ちょっとした大き目のプログラム(だいたい数千行以上)を作成する必要性に迫られると感じてきます。 数千行以上の規模になると、まずは、オブジェクト指向を使わないにしても、プログラムを細かい単位に分割する、「構造化プログラミング」あるいは、「モジュール型プログラミング」が必須となります。なぜなら、 細かい単位にファイルを分割しておかないと、メンテナンスが大変になるからです。 このような「構造化プログラミング」では、必然的に「分割コンパイル」が必要となり、機能別に分割するのならいっそのこと、機能ごとファイルに分けて管理したくなります。

C言語には「構造体」があります。 ある構造体を使う一連の関数を特定のファイルにまとめて管理することがありますが、これは暗黙的にオブジェクト指向的なプログラミングに通ずるものがあります、 また、「アルゴリズムとデータ構造」でたとえると、ひとつの「データ構造」を実現する単位として「構造体」的な考えが使えることがわかりはじめます。ただし、「データ構造」は定義しても、それを使う「アルゴリズム」も必要になります。 これは一般に「関数」として実現しますが、その関数に通常、「データ構造」に相当する「構造体」(あるいはそのポインタ)を引数にもたせることになります。 このようなプログラミングを繰り返していくと、あることに気づきます。 それは、データ構造をちょっと拡張したり、関数をちょっと手直ししようとしたりすると、その「構造体」を使っていた、あちこちのコードを修正しなくてはならなくなり、結局、ほとんどプログラムを一からやり直すのと同じくらい手間がかかることです。

では、オブジェクト指向言語にすると、どこが改善されるでしょうか? まず注意しておかねばなりませんが、 オブジェクト指向言語を使うだけで、何かが特別によくなるわけではありません。 使い方を間違えば、性能の悪いコード(スピードの低下やメモリ使用率の上昇)になることがしばしばありえます。 しかし、賢く利用すれば、高性能・高機能で、拡張性、安全性、メンテナンス性に優れたコードを作成することが可能です。 C言語の「素朴な」構造体で何が問題だったかといえば、たとえば、構造体をアクセスする人が、構造体の内部を全部知っていないと使えないこと、また、構造体は変数しかもてず、構造体を用いる関数がどこにあり、どんな名前だったかをプログラマが知っていないといけない、ということです。 この構造体は、実はオブジェクト指向のオブジェクト、特にクラスの概念に、かなり近いものです。 C言語の構造体(struct)は、C++言語では、すべての変数がpublicなクラスと解釈されます。

こころがけることは、以下のような2ステップです。

  1. まず、「データ構造」に対応する構造体の変数と、「アルゴリズム」に対応する構造体を使った関数群をまとめ、これらに名前を与えることです。 これが、クラスの定義に対応します。
  2. 次に、「クラス」を定義する際、データメンバー あるいはメンバー変数 と呼ばれる変数群と、メソッドあるいはメンバー関数(またはメンバ関数のプロトタイプ群)と呼ばれる関数群にまとめ、これを「クラスファイル」(C++では通常、各クラスを定義するヘッダーファイル)とし、関数の実体は、そのクラスの関数群ごとに、グルーピングして(ファイルとして)管理します。 こうすることで、「クラス」に変更があった場合は、そのクラスを定義するファイルの修正だけで済むようになります。
もっともシンプルなクラスの書式は以下の図のようになります。



classではじまるクラス自体の定義の中で、C++言語では、以下のようなアクセス制御子、 すなわち、 public: あるいは private: などでアクセス制御(データ保護)を行うことができます。 何もラベルがない場合、structではじまる構造体に似た全公開型のクラスと違い、すべてのデータメンバーやメンバ関数は非公開すなわち、暗黙にprivateであると解釈されます。 アクセス制御の宣言はほかにも protected: がありますが、これらのアクセス制御ラベルを書いた場合は、次に別のラベルが現れるまでは、同じアクセス制御が続きます。

C言語からC++言語を修得を目指す人の「推奨されること」と「あまり推奨されない」こと

C++言語の開発者のBjarne Stroustrupさんは、彼のC++の著書の中で、 C言語のプログラマが、C++言語を学ぶときの"Do"と"Don't"に関して指針を述べています。 以下のようにまとめられます。

C++言語のISOでの動き(特にC++11)

C++言語は、1998年にISOの国際標準 ISO/IEC 14882:1998 Programming languages -- C++になりました。 その後、C++11として知られる改訂が施されたバージョンが 2011年に ISO/IEC 14882:2011 Programming languages -- C++になりました。 さらに、2014年にいわゆるC++14ISO/IEC 14882:2014 Programming languages -- C++ として出版されています。 C++11で、もっとも重視されたのは、並列プログラミング(concurrent programming) をタイプセーフ(type-safe)で可搬(portable)にすることでした。 また、システムプログラミングやライブラリ作成の容易さ、 ならびにC++言語を教育現場でより教えやすくすることが主目的で改訂されました。 結果として、たとえば、Javaがもともと持っていたThreadクラスに近づいた threadライブラリが生まれました。 C++11になって導入された主な機能は以下の通りです。

C++言語のクラス定義例 (Part 1)

以下では、幾つかの例を通して、オブジェクトとは何か、 クラスとは何かなど、C++言語でのオブジェクト指向型プログラミングを少しずつ見ていきます。

Listクラスを一般化してみよう

まずは、C言語でもよく扱われる線形リストを考えてみます。 STLにも<list>というライブラリはありますが、 それくらい、基本的でかつ重要なクラスであることを意味しています。 例として、2次元図形(三角形)が要素であるような線形リストを考えます。 たとえば図形(三角形)の編集ソフトを作るを考えます。 この場合、線形リストとしては、 図形に関するデータ(例:三角形へのポインタ)と、 線形リストを互いにリンクするためのデータ(例:線形リストへのポインタ)があれば最小限のことができます。 一方、関数群としては、たとえばリストへ新しいデータを追加する関数既存のデータを削除する関数リストの要素を数える関数などが考えられます。

以下のプログラム(ヘッダーファイル)で例示します。 なお、プログラム2-1では、いろいろなポインタ型にキャスト可能なvoid *型が出てきます。 これは比較的大きなシステムの内部の実装では、時々使いますが、 C++言語を最後まで覚えたあとは、C++言語の作成者も言っているように、 できるだけvoid *型は避け、templateを使い、 任意のデータ型に対応させるほうがスマートな書き方であることを頭の片隅で覚えておいてください。

myList.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* プログラム2-1: myList.h 線形リストクラス定義 */
#ifndef MY_LIST
#define MY_LIST

#include <iostream>
using namespace std;

class myList {/* 線形リストクラス */
  private: /* 外からはこれらのデータに直接アクセスさせない */
	void *data;/* 線形リストのデータ */
	myList *next;/* nextポインタ(次要素) */
	
  private:/* 外からアクセスできない関数 */
	void deleteList(myList *head){/* 再帰的な線形リスト削除 */
		if (head){
			deleteList(head->next);
			delete head;/* オブジェクトの削除 */
		}
	}

  public:/* 以降はpublicで公開する関数(メソッド) */
	myList(){}/* コンストラクタ */
	myList ( void *data ){/* コンストラクタ:多重定義 */
		this->data = data;
		this->next = nullptr; // C++11で追加された機能
	}
	/* 以下、型を持つ関数群のプロトタイプ */
	void myDelete(myList *head){ deleteList(head);}
	myList *insert ( void *data ){/* オブジェクトの生成 */
		this->next = new myList( data );/*リストの末尾に挿入 */
		return(this->next);
	}
	void *getData () { return data;}/* 線形リストの要素を返す関数 */
	void setData ( void *data ){ this->data = data; }/* 線形リストの要素をセットする関数 */
	myList *getNext() { return next; }/* 線形リストの次要素を返す関数 */
};
#endif /* myList.h */

プログラム2-1は、線形リストクラスで、 前回のプログラム1-3のmyHash.hヘッダーファイルと似ている部分もあります。 違いは、線形リストにもたせるデータ自体をvoid型のポインタで(void *data)とした点と、 これにより、キャストを組み合わせれば、基本的にどんな型のポインタであっても線形リストに保持できる点です。 キャストを使えば、かなりの場合で、C言語でできていたような、 違う型の間での代入が可能です。 もちろん、本質的に異なるデータ間でのキャストはできない場合がほとんどです。 いずれにせよ、void型へのポインタは、キャスト前提なら、 ほとんどのポインタ間で互換性があります、 テンプレートを使わないで一般化するなら、 このようにvoid型へのポインタを使う方法もあります。 繰り返しますが、void *型は、テンプレートを学習したあとでは、 できるだけ使わないのが、C++的なプログラミング・スタイルです。

ヘッダーファイルからの2重定義の防止方法

myHash.hでもそうでしたが、上のプログラム(ヘッダーファイル)2-1では、

#ifndef MY_LIST
#define MY_LIST

のように宣言して、ヘッダーファイルの最後で

#endif

としています。 こうすることで、 ヘッダーファイルを同時に使用するクラスファイルが多数ある場合、 クラスの2重定義の防止をしています。 これは、C言語やC++言語では、比較的よく使われる常套手段ですので、 覚えておくとよいでしょう。

Triangleクラス

次に、三角形クラスを定義しましょう。 プログラム2-2に与える、 三角形クラスは、 線形リストクラスとは独立していることに注意してください。

myTriangle.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* プログラム2-2: myTriangle.h 三角形クラス定義 */
#ifndef MY_TRIANGLE
#define MY_TRIANGLE

using namespace std;
#include <iostream>
#include <fstream>
using Coord2 = float[3][2];//C++11から導入されたusingの使い方(alias宣言)

class myTriangle {/* 三角形クラス */
  private: /* 外からはこれらのデータに直接アクセスさせない */
	float R, G, B;/* 色データ */
	Coord2 x;/* 三角形の頂点 */
	float area;/* 三角形の面積 */
	
  private:/* 外からアクセスできない関数 */
	void assignCoord ( Coord2 x ){
		for ( int i = 0 ; i < 3 ; i ++ )
			for ( int j = 0 ; j < 2 ; j++ )
				this->x[i][j] = x[i][j];
	}

  public:/* 以降はpublicで公開する関数(メソッド) */
	myTriangle(){}/* コンストラクタ */
	myTriangle ( Coord2 x, float R, float G, float B ){/* コンストラクタ:多重定義 */
		assignCoord( x );/* 頂点座標をセット */
		this->R = R;/* 赤色をセット */
		this->G = G;/* 緑色をセット */
		this->B = B;/* 青色をセット */
		computeArea();/* 面積を計算 */
	}
	~myTriangle(){ }
	/* 以下、型を持つ関数群のプロトタイプ */
	void computeArea(){/* 三角形の面積を平方根も余弦定理も使わずに計算しちゃおう*/
		float x1 = x[0][0];/* 第1点目のX座標 */
		float x2 = x[1][0];/* 第2点目のX座標 */
		float x3 = x[2][0];/* 第3点目のX座標 */
		float y1 = x[0][1];/* 第1点目のY座標 */
		float y2 = x[1][1];/* 第2点目のY座標 */
		float y3 = x[2][1];/* 第3点目のY座標 */
		float t = (x2-x1)*(y3-y1) - (x3-x1)*(y2-y1);/* 符号付き面積の2倍 */
		t = (t < 0.0F) ? -t : t; /* 負なら符号を反転 */
		this->area = 0.5 * t;//三角形の面積が計算できた!
	}
	float getR() { return R; }/* 赤色を返す */
	float getG() { return G; }/* 緑色を返す */
	float getB() { return B; }/* 青色を返す */
	float getArea(){ return area; }/* 面積を返す */
	Coord2& getCoord() { return x; }/* 座標のアドレスを参照変数として返す */
	void psPrint(ofstream &fout){/* PostScriptで書出す関数(ofstreamを参照変数で渡す!)*/
  	  fout << getR() << " " 
 	       << getG() << " " 
 	       << getB() << " setrgbcolor" << endl;
		fout << "newpath" << endl;
		Coord2& p = getCoord();//三角形の頂点配列[3][2]の先頭アドレスをゲット
		fout << p[0][0] << " " <<  p[0][1] << " moveto " << endl;
		fout << p[1][0] << " " <<  p[1][1] << " lineto "   << endl;
		fout << p[2][0] << " " <<  p[2][1] << " lineto "   << endl;
		fout << "closepath" << endl;
		fout << "stroke" << endl;
	}
};
#endif /* myTriangle.h */

プログラム2-2では、 赤色のフォントになっている<fstream>の使い方が参考になるかと思います。 まず8行目に、C++11から導入されたusingを用いるエイリアス宣言が登場します。これは、C言語にもあるtypedefを使ってtypedef float Coord2[3][2];としても、ほぼ同様の意味となりますが、typedefでは変数の解釈(セマンティックス)が難しいのと、C++言語のtemplateを使って一般化しようとするとうまく対応できないという問題があるため、C++でtypedefを使いたいなら、using A = ...の構文でのエイリアス宣言を推奨します。 50行目で、関数の引数としてファイルストリーム変数を渡すときは、 参照変数にすることを忘れないようにしてください。 繰り返しになりますが、プログラム2-2で例示している三角形クラスは、 線形リストとは完全に独立したクラスであることに注意してください。 また、このように独立に、異なる機能をオブジェクト(クラス)で作ることこそ、 オブジェクト指向言語的なプログラミングの第一歩になると考えてください。 逆に、あるクラスと別のクラスが、継承以外の方法で依存しあっている場合は、 オブジェクト指向言語的な要素が薄れていると考えられます。 この節の最後に、main関数を含むファイルを示します。

線形リストクラスとTriangleクラスをmainで呼び出し結合

プログラム2-3は、プログラム2-1で述べた線形リストクラスと、 プログラム2-2で述べた三角形クラスとを結合させ、 オブジェクト指向言語的なプログラムを目指したものです。

myTriangleList.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* プログラム2-3: myTriangleList.cpp */
using namespace std;
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <ctime>
#include <string>
#include "myList.h" /* リストクラスのヘッダーファイル */
#include "myTriangle.h" /* 三角形クラスのヘッダーファイル */


/* グローバルな変数と関数 */
const string psHeader[] ={
	"%%!PS-Adobe-2.0",
	"%%File: ", 
	"%%Author: 青野雅樹, 01162069"
};
const float width = 550.0F;/* キャンバスの横サイズ */
const float height = 650.0F;/* キャンパスの縦サイズ */
float getRand(float min, float max){// [min,max]の間の乱数発生
	float t = (float)rand()/(float)RAND_MAX;// RAND_MAXはシステムマクロ
	return(min+(max-min)*t);/* [min, max]に写像 */
}

int main(int ac, char **av){
	Coord2 x;/*三角形の3頂点座標 */
	float R, G, B;/* 赤、緑、青 */
	myTriangle *triP;/* 三角形クラスへのポインタ */
	myList *head = nullptr, *list = nullptr;/* リストクラスへのポインタ */
	string fname;/* 出力ファイル名 */
	int n = 0;/* 出力する三角形数 */

	cout << "出力ファイル名を入力してください > " << endl;
	cin >> fname;/* ファイル名を読み込みstring変数に代入 */
	/* 出力ファイルストリーム宣言: c_str()はC言語の文字列を得る関数 */
	ofstream fout( fname.c_str(), ios::out );

	cout << "三角形の数を入力してください > " << endl;
	cin >> n;
	srand ( time(nullptr) );/* 現在時刻で乱数を初期化 */

	const float XRANGE = ::width;//PostScriptキャンバスの横幅
	const float YRANGE = ::height;//PostScriptキャンバスの縦幅
	const float RGBRANGE = ::RGBmax;//0.0 <= r,g,b <= 1.0
	for ( int k = 0 ; k < n ; k++ ){ 
		for ( int i = 0 ; i < 3 ; i++ ){
			x[i][0] = ::getRand(0.0, XRANGE);
			x[i][1] = ::getRand(0.0, YRANGE);
		}
		R = ::getRand(0.0, RGBRANGE );/* 赤色成分を乱数で発生 */
		G = ::getRand(0.0, RGBRANGE );/* 緑色成分を乱数で発生 */
		B = ::getRand(0.0, RGBRANGE );/* 青色成分を乱数で発生 */

		triP = new myTriangle(x, R, G, B);/* 三角形オブジェクトの生成 */
		if (!head)
			head = list = new myList((void *)triP);/* リストクラスのオブジェクト生成 */
		else 
			list = list->insert((void *)triP);/* 2回目以降は直前の線形リストの末尾に挿入 */
	}
	/* PostScriptで出力 */
	fout << ::psHeader[0]  << endl; /* PostScriptのヘッダー */
	fout << ::psHeader[1]  << fname.c_str() << endl;/* コメント */
	fout << ::psHeader[2]  << endl;/* 作者名 */

	myList *p =head;/* 線形リストの先頭からたどる */
	int count = 1;
	while (p){
		triP = (myTriangle *)p->getData();/* void *からキャスト */
		fout << "%%" << count << "番目の図形" << endl;
		triP->psPrint(fout);/* 三角形クラスのPostScript出力関数呼び出し */
		count++;
		p = p->getNext();/* nextはprivateなので公開された関数でアクセス */
	}
	fout << "showpage" << endl;/* PostScriptの最終行:図形描画を実行 */
	fout.close();/* ファイルのclose */
	head->myDelete(head);/* head用メモリの解放 */
	return 0;
}

プログラムのコメントとしては、まず、main関数の外で定義された各種のグローバル定数グローバル関数をmain関数で参照するとき、先頭に::(ダブルコロン)がついていることに注意してください。 次にファイルに関しては、36行目で出力ファイルストリームを定義しています。 このとき、STLの一種であるstring型変数から、 ファイル名の文字列を取り出すために、fname.c_str()というようにc_str()関数を使っています。 三角形クラスのオブジェクトを生成しているのが54行目で、 線形リストクラスのオブジェクトを生成しているのが、56行目のnew myList()というコンストラクタと58行目の関数insert()になります。 このようにnew演算子を使った場合は、どこでオブジェクトが生成されているかが明確になります。 ポインタ型変数の場合、new演算子でオブジェクトを生成する場合に適します。 なお、new演算子は、動的にメモリを確保しながらも、C言語のmallocやcalloc関数と異なり、 sizeofのような関数でクラスのサイズを指定する必要はありません。 サイズを指定しないことで、 mallocやcallocでサイズを間違ったサイズの動的なメモリ確保によるメモリリークの危険性は減ります。 一方、ポインタ変数以外だと、C++言語の場合、クラス変数を宣言するだけで 暗黙的にオブジェクトが生成されてしまいます。 ちなみに、Java言語では、ポインタ型はありませんが、 オブジェクトはnew演算子以外では生成できないため、 オブジェクトの生成のメカニズムがシンプルで理解しやすいです。

三角形を何個か定義したあと、線形リストに保持された図形を取り出してPostScriptで定義しているのが67行目から73行目のwhileループになります。 プログラム2-1で示した線形リストでは、図形部分を(void *)で定義していました。 そこで 三角形クラスをさすポインタ(myTriangle *)に(逆)変換するため、キャストしています。ここで使った(void *)は、クラスの継承が登場すると、基底クラスのポインタに置き換わり、(void *)はもはや不要になりますので一時的な措置と理解してください。

プログラム2-3のコンパイルと実行の様子は以下の通りです。



vectorクラスを上手に使おう

プログラム2-3では、自前で作成した線形リストのクラス(myList.h)を利用しましたが、 「C++で推奨されること」にも含まれているvectorクラスを使って書き直してみます。 ここでは、vector < myTriangle*> list;という宣言で、myTraingleクラスのポインタを可変個、有するvectorを定義してみます。
myTriangleVector.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* プログラム2-3A: myTriangleVector.cpp */
using namespace std;
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>
#include "myTriangle.h" /* 三角形クラスのヘッダーファイル */

/* グローバルな変数と関数 */
const string psHeader[] ={
	"%%!PS-Adobe-2.0",
	"%%File: ", 
	"%%Author: 青野雅樹, 01162069"
};
const float width = 550.0F;/* キャンバスの横サイズ */
const float height = 650.0F;/* キャンパスの縦サイズ */
float getRand(float min, float max){// [min,max]の間の乱数発生
	float t = (float)rand()/(float)RAND_MAX;// RAND_MAXはシステムマクロ
	return(min+(max-min)*t);/* [min, max]に写像 */
}

int main(int ac, char **av){
	Coord2 x;/*三角形の3頂点座標 */
	float R, G, B;/* 赤、緑、青 */
	myTriangle *triP;/* 三角形クラスへのポインタ */
 	vector <myTriangle*> list; /* 三角形クラスへのポインタの可変長リスト */
	string fname;/* 出力ファイル名 */
	int n = 0;/* 出力する三角形数 */

	cout << "出力ファイル名を入力してください > " << endl;
	cin >> fname;/* ファイル名を読み込みstring変数に代入 */
	/* 出力ファイルストリーム宣言: c_str()はC言語の文字列を得る関数 */
	ofstream fout( fname.c_str(), ios::out );

	cout << "三角形の数を入力してください > " << endl;
	cin >> n;
	srand ( time(nullptr) );/* 現在時刻で乱数を初期化 */

	const float XRANGE = ::width;//PostScriptキャンバスの横幅
	const float YRANGE = ::height;//PostScriptキャンバスの縦幅
	const float RGBRANGE = ::RGBmax;//0.0 <= r,g,b <= 1.0
	for ( int k = 0 ; k < n ; k++ ){ 
		for ( int i = 0 ; i < 3 ; i++ ){
			x[i][0] = ::getRand(0.0, XRANGE);
			x[i][1] = ::getRand(0.0, YRANGE);
		}
		R = ::getRand(0.0, RGBRANGE );/* 赤色成分を乱数で発生 */
		G = ::getRand(0.0, RGBRANGE );/* 緑色成分を乱数で発生 */
		B = ::getRand(0.0, RGBRANGE );/* 青色成分を乱数で発生 */

		triP = new myTriangle(x, R, G, B);/* 三角形オブジェクトの生成 */
                list.push_back(triP);/* listに追加 */
	}
	/* PostScriptで出力 */
	fout << ::psHeader[0]  << endl; /* PostScriptのヘッダー */
	fout << ::psHeader[1]  << fname.c_str() << endl;/* コメント */
	fout << ::psHeader[2]  << endl;/* 作者名 */

	for (auto i=0 ; i < (int)list.size() ; i++){
		triP = list[i];/* vectorのi番目のデータ*/
		fout << "%%" << (i+1) << "番目の図形" << endl;
		triP->psPrint(fout);/* 三角形クラスのPostScript出力関数呼び出し */
		delete (triP);
	}
	fout << "showpage" << endl;/* PostScriptの最終行:図形描画を実行 */
	fout.close();/* ファイルのclose */
	head->myDelete(head);/* head用メモリの解放 */
	return 0;
}

2つ以上のクラスを作るときに注意すること

オブジェクト指向言語で中規模以上のプログラムを作るようになると、 そのありがたみがわかってきます。 とはいえ、小さなプログラムであっても、 クラスを2つ以上作ることは頻繁に発生します。 プログラム2-1~2-3までの線形リストと三角形のクラスは完全に独立していました。 しかし、現実的には、どんなにモジュール化したとしても、 互いに依存しあうクラス(たとえば、互いのクラスへのポインタをデータメンバー変数として有する場合)が平気で表れます。 先に、データ構造とアルゴリズムのアナロジーで、 データメンバー(変数)とメンバー関数にたとえて述べました。 その意味で、しばしば幅広い分野で利用されるデータ構造にグラフ構造があります。 以下では、簡単なグラフ構造をクラスで表現することを試みます。

グラフとは、ノード(頂点)エッジ(=アーク)(辺) からなります。 ここで、簡単なグラフとして、以下のような図を考えることにします。



この図には、A, B, C, D, E, F, Gと7つのノードと、 数字で重みのついた11本のエッジがあります。 このようなグラフをデータ表現することを目指して、 ノードのクラスとエッジのクラスを作ってみます。 なお、グラフに示すように、ノードには、開始点ノートと目的地ノードがあるとします。 ここで考える問題は、開始点から目的地までエッジを通り行けるパス(経路)の中で、 重みの合計が最小の経路を求めるとします。 これは、いわゆるダイクストラのアルゴリズム問題ですが、 ノードとエッジには、どのようなデータメンバー(変数)とメンバー関数があればいいでしょうか? まず、ノードクラスを作ってみましょう。
myNode.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
/*プログラム2-4: グラフのノードクラスの定義例 */
#ifndef MY_NODE
#define MY_NODE
class myNode {
private:
	char label;/*文字ラベルを保持 */
public:
	/* 以下のコンストラクタで初期化 */
	myNode(char label) :  label(label) {}
	myNode(){ label = '?'; }/* 無引数の場合はlabelを?とする */
	char getLabel(){ return label;}/* ラベルを返す関数 */
};
#endif
この例では、 コンストラクタの初期化リストが、myNodeコンストラクタの直後にコロンのあとlabel(label)のように登場しています。 これは、このクラスの変数名(初期値変数名、あるいは初期値)のあとに初期値を与えるもので、 もし、複数の初期値を設定する場合は、 カンマをつけてリストすることで、初期値を設定できます。 このとき、以下のことに注意してください。

コンストラクタの初期値リストで複数のメンバ変数を初期化する場合
メンバ変数の定義で出現した順序に合わせて初期化してください
次にエッジのクラスを作成してみます。 エッジに持たせたいのは、以下の図にあるように、両端のノード情報と エッジの重み情報です。



これらを以下のように表現してみます。
myEdge.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
/*プログラム2-5: グラフのエッジクラスの定義例 */
#ifndef MY_EDGE
#define MY_EGDE
#include "myNode.h"

class myEdge {
private:
	myNode* node1;/* エッジの片方の端のノード */
	myNode* node2;/* エッジのもう片方の端のノード */
	int weight; /* 重み */
public:
	myEdge(myNode* node1, myNode* node2, int weight)
		: node1(node1), node2(node2), weight(weight){/* 初期化 */}
	myNode* getN1(){ return node1;} /* node1を返す */
	myNode* getN2(){ return node2;} /* node2を返す */
	int getWeight(){ return weight;}/* 重みを返す */
};
#endif


さて、これらのノードとエッジを定義できたら、ダイクストラのアルゴリズムを 適用するためのクラスを定義します。 ダイクストラのクラスでは、隣接行列が重要な役割を担います。 ここでは、詳細は説明しませんが、 ソース('A')から目的ノード('G')までの最短距離を求めるアルゴリズムの例です。 Dijkstra.hは以下のような感じです。 これは、http://easy-learn-c-language.blogspot.jp/2013/02/implementation-of-dijkstras-shortest.html を参考にしたものです。 隣接行列をcin(ストリーム入力)で与える代わりに、 重みのあるエッジを直接与えるように変更したものです。 グラフの隣接行列表現は以下のようになっています。



この表で、w(i,j)は重みの初期値ですが、INFは無限大、すなわち、 2つのノードを直接リンクするエッジがないことを表します。 ダイクストラの最短経路アルゴリズムを使うためのヘッダーファイルにクラスの詳細を書いておきます。 たとえば、以下のようになります。
Dijkstra.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*プログラム2-6: ダイクストラの最短距離発見アルゴリズム 
	無向重みつきグラフで、ソース(開始ノード)から、目的ノード
	までの最短距離(すべてではない)を1つ貪欲算法で求める。*/
#ifndef MY_DIJKSTRA
#define MY_DIJKSTRA

#include <iostream>
using namespace std;
#include "myEdge.h"

class Dijkstra{
    private:
        int **adjMatrix;/* グラフの隣接行列 */
        int *predecessor; /* 直前のノード */
        int *distance;/* 距離 */
        bool *mark; /* これまで訪問したノードかどうか */
        int source;/* スタートノード */
        int numOfVertices;/* ノード総数 */
    public:
        void initialize(int nodeId);/* 初期化 */
        void makeAdjacentMatrix(int numV, int numE, myEdge **edge);
        int getClosestUnmarkedNode();/* まだ訪れていないノード */
        void calculateDistance(int nodeId);/* ソースからの最小距離計算 */
        void print();/* プリント */
        void printPath(int);/* 道筋をプリント */
};
#endif


Dijkstra.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*プログラム2-7: ダイクストラの最短距離発見アルゴリズム 
	無向重みつきグラフで、ソース(開始ノード)から、目的ノード
	までの最短距離(すべてではない)を1つ貪欲算法で求める。*/
#include "Dijkstra.h"

const int INFINITY = 99999; // INF weight
const char CHARA = 'A';// referring to ASCII

void Dijkstra::initialize(int source){/* 初期化 */
	this->source = source;/* スタートノード */
	this->mark = new bool[numOfVertices];/* boolean flagのメモリ割り当て*/
	this->predecessor = new int[numOfVertices];/* 直前のデータ保持割り当て */
	this->distance = new int[numOfVertices];/* 距離用データ割り当て */
	for (int i = 0 ;i < numOfVertices ;i++ ) {/* グラフの頂点数だけループ */
		mark[i] = false;/* マークされていないとする */
		predecessor[i] = -1;/* 直前要素はないとする */
		distance[i] = ::INFINITY;/* 十分遠いとする */
	}
	distance[source]= 0;/* スタートノード自身との距離はゼロとする */
}

// メモリの解放
void Dijkstra::cleanup(){
	delete[] mark;
	delete[] predecessor;
	delete[] distance;
	for (int i = 0 ; i < numOfVertices ; i++)
		delete adjMatrix[i];
	delete[] adjMatrix;
}

int Dijkstra::getClosestUnmarkedNode(){/*未訪問で最近傍のノードを探索 */
	int minDistance = ::INFINITY;/* グラフの初期化 */
	int closestUnmarkedNode;/* マークされていないノード番号を保持 */
	for (int i = 0 ; i < numOfVertices ; i++ ) {
		/* 未訪問で最小値が見つかったら入れ替える */
		if((!mark[i]) && ( minDistance >= distance[i])) {/* 見つかった! */
			minDistance = distance[i];/* 最小値を入れ替え :/
			closestUnmarkedNode = i; /* 最近傍のノードをiに設定 */
		}
	}
	return closestUnmarkedNode;/* 最寄のマークされていないノード番号を返す */
}

void Dijkstra::calculateDistance(int source){/* 貪欲アルゴリズムで重み距離の小さいものを探す */
    initialize(source);/* 初期化 */
    int closestUnmarkedNode;/* 最寄のマークされていないノード番号 */
    int count = 0;/* カウンタ */
    while (count < numOfVertices) {/* 全部のノードで反復 */
        closestUnmarkedNode = getClosestUnmarkedNode();
        mark[closestUnmarkedNode] = true;/* マークする */
        for (int i = 0 ; i < numOfVertices ; i++ ) {
            if ((!mark[i]) && (adjMatrix[closestUnmarkedNode][i] > 0) ) {
                if(distance[i] > distance[closestUnmarkedNode]+adjMatrix[closestUnmarkedNode][i]) {
                  /* i番目のノードの距離が現在まだマークされていないノードの距離
                     と、そのノードとi番目のノードの隣接行列の和よりも大きい場合 */
                  distance[i] = distance[closestUnmarkedNode]+adjMatrix[closestUnmarkedNode][i];
                  predecessor[i] = closestUnmarkedNode;/* 自分のひとつ前を非マークノードとする */
                }
            }
        }
        count++;
    }
}

void Dijkstra::printPath(int node){/* 再帰的にパス(道筋)をプリント */
    if (node == source)
        cout << (char)(node + ::CHARA) << "..";/* ノードを'A'からはじまるアルファベットで示す */
    else if (predecessor[node] == -1)
        cerr << "No path from " << source << "to " << (char)(node + ::CHARA) << endl;
    else {
        printPath(predecessor[node]);/* ソースから見て直前のデータ(predecessor)をプリントする */
        cout << (char) (node + ::CHARA) << "..";
    }
}

void Dijkstra::print(){/* ソースからのノードラベルの出力 */
    for (int i = 0 ; i < numOfVertices ;i++) {
        if ( i == source )/* ソースであることを明記する */
            cout << (char)(source + ::CHARA) << "がソースです" << endl;
        else
            printPath(i);
        if (i != source)
            cout << "->" << distance[i] << endl;
    }
}

void Dijkstra::makeAdjacentMatrix(int numV, int numE, myEdge **edge){/* 隣接行列の作成 */
	this->numOfVertices = numV;
	adjMatrix = new int*[numV];/* ポインタオブジェクト(ポインタ配列)を頂点数だけ作成 :/
	for (int i = 0 ; i < numV ; i++)
		adjMatrix[i] = new int[numV];/* 隣接行列の初期化 */
	for (int i = 0 ; i < numV ; i++)
		for (int j = 0 ; j < numV ; j++ )/* 対角要素はゼロ、そうでないと最大値を代入 */
			adjMatrix[i][j] = (i == j) ? 0 :  ::INFINITY;

	for (int i = 0 ; i < numE ; i++){/* エッジに対して */
		myEdge *p = edge[i];/* i番目の配列にあるポインタを代入 */
		int V1 = (int)(p->getN1()->getLabel() - ::CHARA);/* 文字'A'からの差 */
		int V2 = (int)(p->getN2()->getLabel() - ::CHARA);/* 文字'A'からの差 */
		int weight = p->getWeight();
		adjMatrix[V1][V2] = weight;/* V1->V2の重み */
		adjMatrix[V2][V1] = weight;/* 対称性からV2->V1の重みも */
	}
}
最後にメインプログラムとその実行例をつけておきます。 最初に提示したグラフで実行した例となっています。 プログラム2-8で与えているダイクストラのアルゴリズムでは、 ノードもエッジもポインタのポインタとして表現しています。 STLに慣れてくれば、これらは、vectorやmap等で表現することで、 よりC++言語らしいシンプルな表現で記述できるようになります。 なお、プログラム2-7では、 6行目と7行目にconstがついた整数定数と文字定数を使っています。 これを参照するとき、グローバル変数であることがわかるように、 ::INFINITYや::CHARAのように参照している点に注意してください。
DijkstraMain.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* プログラム2-8: ダイクストラの最短距離発見アルゴリズム 
	mainプログラム */
#include "Dijkstra.h"
#define NUM_NODES (7)
#define NUM_EDGES (11)
class EdgeWeight {
	public:
	int V1, V2, weight;
};
int main(){
	EdgeWeight ew[NUM_EDGES] = {/*図で示したエッジと重みのペアを初期化で設定 */
		{0, 1, 4}, /* A, B, 4 */
		{0, 2, 1}, /* A, C, 1 */
		{0, 3, 2}, /* A, D, 2 */
		{1, 2, 2}, /* B, C, 2 */
		{1, 6, 3}, /* B, G, 3 */
		{2, 4, 3}, /* C, E, 3 */
		{2, 3, 1}, /* C, D, 1 */
		{3, 4, 2}, /* D, E, 2 */
		{3, 5, 1}, /* D, F, 1 */
		{5, 6, 4}, /* F, G, 4 */
		{4, 6, 2}, /* E, G, 2 */
	};
	myNode **node = new myNode*[NUM_NODES];/* 2GBの壁を越えてメモリを使うコツ!(その1) */
	for (int i = 0 ; i < NUM_NODES ; i++) 
		node[i] = new myNode((char)('A'+i));/* 'A'からの増分で文字ラベルを与えmyNodeオブジェクト生成 */
	myEdge **edge = new myEdge*[NUM_EDGES];/* myEdgeへのポインタ配列オブジェクト生成 */
	for (int i = 0 ; i < NUM_EDGES ; i++){
		myNode *v1 = node[ew[i].V1];/* i番目のEdgeWeightの頂点1 */
		myNode *v2 = node[ew[i].V2];/* i番目のEdgeWeightの頂点2 */
		edge[i] = new myEdge(v1, v2, ew[i].weight);/* myEdgeオブジェクトを生成 */
	}
	cout << "【ダイクストラのアルゴリズムで最短経路】" << endl;
	Dijkstra *G = new Dijkstra();/* ダイクストラ・クラスオブジェクトを生成 */
	G->makeAdjacentMatrix(NUM_NODES, NUM_EDGES, edge);/* 隣接行列作成 */
	int source = 0; /* ソースはゼロ番('A')とする */
	G->calculateDistance(source);/* ダイクストラのアルゴリズムを実行 */
	cout << "Aから";
	cout.put('A'+NUM_NODES-1);/* 文字を出力するときはcout.put関数 */
	cout << "までの最短距離を探します" << endl;
	G->print();/* 結果のプリント */
	for (int i = 0 ; i < NUM_EDGES ; i++ ) delete edge[i]; delete[] edge;/*配列型オブジェクトの解放 */
	for (int i = 0 ; i < NUM_NODES ; i++ ) delete node[i]; delete[] node;/*配列型オブジェクトの解放 */
	G->cleanup(); /* Dijkstraオブジェクトの解放 */
	return 0;
}


以下が実行例です。



ダイクストラの最短経路アルゴリズムは、有向グラフに対しても適用できます。 その例として、 世界的に有名なアルゴリズムの教科書であるMIT出版社のIntroduction to Algorithmsにある、以下の例を紹介します。



この例では、ノード集合={s,t,x,y,z}で、ソースがsになります。 隣接行列は以下のような初期値を持ちます。



ダイクストラのアルゴリズムでは貪欲算法でソースである  s  から繋がっているエッジの中で、 重み最小のエッジを探します。図の(a)では、  y  へのエッジの重みが5で、  t  へのエッジの重みが10なので、  y  のほうが最短路として採用されます。そこで、  y  のノードの初期値の無限大が、重み5で置き換えられます。 一方、  t  のノードも無限大よりは、エッジの重み10にしたほうが小さいので、10に置きかえられます。 こうして得られたのが(b)です。次に、  y  のノードからリンクのあるノードの中で重み最小で動ける経路を探します。 実際は  y  から  t  が重み3、  y  から  x  が重み9、  y  から  z  が重み2で移動できます。 この中の最小値は  z  への経路である重み2なので、  y  の値である5に  z  へのエッジの重みの2を足した7が  z  のノードの値となります。更に、  t  への経路も5+3のほうが、(b)の図にある   t  の10より小さいので置き換えます。同様に、   x  のノードは無限大なので、これを5+9である14に置き換えます。こうして得られたのが(c)です。 以下同様に、貪欲にその場で最小のコストとなる経路ならびにノードの値の更新を反復し、最終的に図(f)の状態が得られます。この流れをプログラム2-7のcalculateDistance関数で行います。最小の経路を見つけるのが、32行目から43行目の関数getClosestUnmarkedNodeです。一方、ノードの値を置き換えるのは、54行目から58行目にあり、calculateDistance関数のループ内で行います。 最終的に、図の白抜きで示すノードの値が隣接行列に求まります。 これはまた、ソースであるノードsから、他の任意のノードまでの距離を表しています。

C++言語のクラス定義例(Part 2)

以下では、「画像」ファイルに関するクラスの作成を試みます。

Imageクラスを作ってみよう

下の「象画像」に示すような画像(Image)クラスを作るとすると、どのような要素があればいいでしょうか? ここでは、Web上にある画像を対象とします。 まずは、その画像名を含むURLを入れるのが自然かと思います。 次に、画像は通常、矩形ですので、その縦横サイズ(解像度)も必要と思います。 さらに、それが写真であれ、イラストであれ、作者がいて、 製作された日付もあるかと思います。 また、その画像を説明するアノテーション(注釈)もあると便利かと思います。 画像のフォーマットを表すタイプ(たとえばJPEG, PNG, BMPなど)もあるといいかと思います。 クラスのメンバー変数はだいたい、このあたりが外向きでは必要です。 あとはアプリケーションに依存しますが、たとえば、画像を検索することを考えると、 画像の特徴量を表現するデータがあるといいかと思います。 一方、クラスのメンバー関数としては何が必要でしょうか? メンバー変数にアクセスする関数(setとget)は必要です。 また、printする関数も必須だと思います。 あとは、必要に応じて特徴量を計算(抽出)する関数などがあるとよさそうです。 これらをもとに作成したのが、以下のImageクラスになります。 なお、このクラスには、コンストラクタや画像ファイルのI/O関数などはまだ、 実装されていません。なお、 明示的なコンストラクタがない場合は、無引数のコンストラクタがあると解釈されます。



Image.h
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/* プログラム2-9: 画像クラス例 Image.h*/
#ifndef _IMAGE
#define _IMAGE
#include <iostream>
using namespace std;
#include <cstring>
#include <string>
#include <vector>

class Image {
	private:
		int width;  //横幅
		int height; //高さ
		int depth; //pixelのdepth (=1(Binary),=2(Grayscale),=3(RGB), =4(RGB+透明度))
		string url;  // 画像のURL
		string format; // 画像フォーマット("JPEG","PNG","BMP",etc.)
		string annotation; //注釈
		string date; // 日付
		string location;//場所
		unsigned char *buffer; //画像バッファ
		vector <float> imageFeature;//画像の特徴量(可変長)
	public:
		int getWidth() { return width; }//横幅のゲット
		int getHeight() { return height; }//高さのゲット
		int getDepth() { return depth;}//ピクセル深度のゲット
		string getUrl() { return url; }// 画像のURLのゲット
		string getFormat(){ return format;}// 画像フォーマットのゲット
		string getAnnotation(){ return annotation;}//注釈のゲット
		string getDate(){ return date; }// 日付のゲット
		string getLocation(){ return location;}//場所のゲット
		unsigned char *getBuffer(){ return buffer;}//画像バッファのゲット
		vector <float> getImageFeature(){ return imageFeature;};//画像の特徴量のゲット
		void setWidth(int width){ this->width = width;}//横幅のセット
		void setHeight(int height) { this->height = height;}//高さのセット
		void setDepth(int depth) { this->depth = depth;}//ピクセル深度のセット
		void setUrl(string url){ this->url = url; }// 画像のURLのセット
		void setFormat(string format){ this->format = format;}// 画像フォーマットのセット
		void setAnnotation(string annotation){ this->annotation = annotation;}//注釈のセット
		void setDate(string data) { this->date = date;}// 日付のセット
		void setLocation(string location) { this->location = location;}//場所のセット
		void setBuffer(unsigned char *buffer){ //画像バッファのセット
			this->buffer = new unsigned char[width*height*depth];
			memcpy(this->buffer, buffer, width*height*depth);
		}
		void setImageFeature(vector <float> imageFeature){//画像の特徴量のセット
			this->imageFeature = imageFeature;
		}
		void print(){}// not implemented yet
};
#endif


C++言語の<iomanip>クラスでのプリントフォーマットの仕方(少し)

C言語でのprintfでは、いろいろと細かくプリントの仕方を制御できましたが、 coutやfoutのようなストリーム出力では、 一見、フォーマットの整形が困難な印象を与えます。 C++言語では、 #include <iomanip>をincludeすることで幾つかの整形フォーマット関数にアクセスできます。

ライブラリでのフォーマット例

iomanipの関数例 主な内容
setw(int n) 出力幅をn文字分とる
setprecision(int n) 出力精度をn桁までとる
setfill(charT c) 出力を文字cで埋める(パディング)
left 左アジャスト
right 右アジャスト
oct 8進数
hex 16進数
fixed 固定小数点表示
scientific 浮動小数点表示
lowercase 小文字表示
uppercase 大文字表示

たとえば

cout << left << setw(10) << x;

とすると、xを10文字分幅をとって左詰でプリントすることを意味します。

文字列、整数、実数に対して、<iomanip>を使った出力の整形例を紹介します。 特に、coutを使いながらiomanipクラスのマニピュレータを使って各種フォーマッティングの例を紹介します。
FormatSample.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*プログラム2-10: フォーマット事例 */
#include <iostream>
#include <iomanip>
#include <string>
#include <cmath>
using namespace std;
int main(){
	int i_number = 12345;
	double napier = M_E;/* ネイピア数 */
	double value = 1.0 / pow(M_PI, 10.0);
	string s1("June is a rainy season in Japan.");
	string s2("6月は梅雨の季節です。");

	/* 文字列のプリント */
	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	/* 10進数、16進数、8進数表示 */
	cout << i_number << "は16進数で" << hex << i_number << "です" << dec << endl;
	cout << i_number << "は8進数で" << oct << i_number << "です" << dec << endl;
	cout << setbase(10) << i_number << "は10進数で" << i_number << "です" << endl;
	cout << "整数の基底を表示すると" << endl;
	cout << showbase;
	cout << i_number << "は10進数(基底表示)で" << i_number << "です" << endl;
	cout << i_number << "は16進数(基底表示)で" << hex << i_number << "です" << dec << endl;
	cout << i_number << "は8進数(基底表示)で" << oct << i_number << "です" << dec << endl;
	
	/* 実数の桁表示 */
	cout << endl;
	cout << "自然対数e(ネイピア数)を表示桁数を変更して出力します" << endl;
	for (int places = 1 ; places <= 9 ; places++ )
		cout << "小数点以下" << (places-1) << "桁では " << 
			setprecision( places ) << napier << "です" << endl;

	/* 左、右詰表示 */
	cout << "デフォルトの表示は\n" << i_number << "です" << endl;
	cout << "左詰め10文字幅での表示は\n" << left << setw(10) << i_number << "です" << endl;
	cout << "右詰め10文字幅での表示は\n" << right << setw(10) << i_number << "です" << endl;
	cout << "右詰め10文字幅での表示で*でpaddingすると"<< endl;
	cout << right;
	cout.fill('*');
	cout << setw(10) << i_number << "です" << endl;

	/* 固定小数点、浮動小数点表示 */
	cout << "valueの固定小数点表示は" << fixed << value << "です" << endl;
	cout << "valueの浮動小数点表示は" << scientific << value << "です" << endl;
	return 0;
}


このプログラムの実行の様子は以下のようです。



名前空間(Namespace)

C++言語には、名前空間(Namespace)という独自の機能があります。 クラス自体もそうですが、C++では、 一連の関連オブジェクトを名前空間でまとめておくことが奨励されます。 名前空間を使う最大のメリットは、変数名や関数名での衝突を避けることです。 名前空間の定義では、namespace 名前A(またま無記名) {   }  という構文で定義します。 利用する場合は、名前A::xxxのように書きます。 ここで、xxxは、名前空間内の変数や関数名です。 あるいは、 using 名前A::xxx;(通称、usingディレクティブ)を定義しておいて、 xxxに、いきなりアクセスすることも可能です。

今後、coutやendlなどを使う例を述べる際に、 using namespace std;というusingディレクティブを宣言する例を多く述べますが、 これは、プログラムが比較的小さい場合に「読みやすさ」を優先したものです。 std名前空間には、実際は非常に多くの変数や関数を含むため、実は、 usingディレクティブを使いすぎると、折角、名前空間を定義したにもかかわらず、 名前の衝突を助長する場合があります。
namespace1.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
/*プログラム2-11: 名前空間例1 */
#include <iostream>
using std::cout;/* 名前空間内で使うものだけ書いても良い */
using std::endl;/* 名前空間内で使うものだけ書いても良い */
namespace foo {
	int bar=3;/* 円周率の超粗い近似 */
	double pi;/* 円周率 */
}
int main(){
	int *pi = &foo::bar;/* pi = &bar; とは書けない。piはnamespace中のpiとは異なる */
	cout << "piのアドレス = " << pi << " piの値 = " << *pi << endl;
	return 0;
}


このプログラムの実行の様子は以下のようです。



次の例では、名前空間のネスト、名前なし名前空間、名前空間のスコープを見ていきます。 また、グローバル変数、ならびにネストした名前空間内の変数へのアクセス方法を確認します。 いろいろな方法でグローバル変数的に名前空間を利用できますが、 推奨されるのは、 ちゃんとした名前を持つ名前空間を利用して、自分(やプロジェクトチーム)の中で名前のルールを決めた上で、 グローバル変数、グローバル定数、グローバル関数として意味のある単位で管理する、という書法です。
namespace2.cpp
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*プログラム2-12: 名前空間例2 */
#include <iostream>
#include <iomanip>
using std::cout;/* cout */
using std::endl;/* endl: end-of-line */
using std::setprecision;/* 有効数字:精度表示 */

int integer1 = 999; /* グローバル変数 */

namespace myName {/* 名前をつけたほうがグローバル変数や関数が見やすい! */
	const double G = 9.80665; /* 重力加速度 */
	const double E = 2.718281828459045; /* ネイピア数 */
	int integer1 = 8; /* myName名前空間中の変数 */
	void printValue();/* プロトタイプ */
	namespace myName2 {/* namespaceはネストできる */
		enum Years { ACADEMIC1=2013, ACADEMIC2, ACADEMIC3};
	}
}
namespace { /* 名前なし名前空間 */
	double halfPI = 1.57079632679489661922;/* 円周率の半分 */
}
int main(){
	cout << "PI/2 = " << setprecision(10) << halfPI << endl;
	cout << "global integer1 =  " << integer1 << endl;
	cout << "myName::G = " << myName::G << endl;
	cout << "myName::E = " << myName::E << endl;
	cout << "myName::ineteger1 = " << myName::integer1 << endl;
	cout << "ACADEMIC3 = " << myName::myName2::ACADEMIC3 << endl;
	myName::printValue();
	return 0;
}
void myName::printValue(){/* namespace中でプロトタイプ宣言された関数 */
	cout << "in printValue():" << endl;
	cout << "G = " << G << endl;
	cout << "E = " << E << endl;
	cout << "integer1 = " << integer1 << endl;
	cout << "ACADEMIC3 = " << myName2::ACADEMIC3 << endl;
}


このプログラムの実行の様子は以下のようです。