最近のアプリケーションはユーザが「グラフィカル・ユーザ・インターフェイス」(GUI)を利用して、操作順序を管理するものが多くなっています.そこで、この章では、GUIを使ったプログラミングについて学んでいきます.
ここでは、初めに「GUI」をつくり、マウスの「クリック」「ドラッグ」や「キーの操作」「ウィンドウの開閉」などのイベントが操作を要求するのを待つアプリケーションを作成します.「ボタン」「メニュー」などの部品は、イベントというオブジェクトを発生します.このイベントの発生源を「イベント・ソース」といいます.
イベントを処理するオブジェクトを割り当てるプロセスを「委託」(delegate)といい、そのオブジェクトを「リスナ」(listener)といいます.オブジェクト指向の基本原理は、「ある作業にもっとも適したオブジェクトがその作業を担当する」なので、このイベント処理はリスナに「委託」されます.
JavaのGUI
JavaのGUIには「アプレットGUI」と「アプリケーションGUI」とがあります.この章では、アプリケーションGUIについて学びます. GUIの部品では「AWT(Abstract Windows Tools)」と「SWING」がよく用いられ、これらはユーザ・インターフェイスを作成したり、グラフィックスや画像を描くのに用いられます.
AWTはC/C++のようなネイティブ・コードを使っていますが、SWINGはすべてJavaで書かれた移植可能なGUIで、Javaの基本クラス(JFC)の構成要素になっています.AWTと比較すると、SWINGは必要なシステム・リソースが少なく軽い部品なので、ここでは主としてSWING部品を使います.
GUIアプリケーション
GUIアプリケーションは「JFrame」のサブクラスによってGUIを作ります.インタープリタには他のアプリケーションと同じようにjava.exeを用います. アプリケーションはJFrameのサブクラスのインスタンスによって、ウィンドウを作り、サブクラスのコンストラクタでウィンドウを初期化し、ウィンドウのタイトルを表示して、「setSize()」メソッドでウィンドウの大きさを指定します.用いるメソッドと部品が多くて分からなくなることがあるので、ここでは、必要最低限のことを紹介します.
まず、引数を”Hello world”とし、楕円の中にHello worldと表示するアプリケーションを考えます.
1.まず、楕円と文字列を書く必要があります.図形の描画にはpaint()メソッドを用いることができます.paintメソッドはJPanelのメソッドですので、Hello1を第1クラスとして、「JPanel」を継承(extend)し、paint()メソッドによってJPanelに楕円と文字を書くことができます.paint()メソッドは、図形を描画して、その外観を作り上げるためのメソッドです.paintメソッドの引数では、描画の対象となるオブジェクトが渡されます.それは、Graphicsクラスのインスタンスです.このインスタンスは描画領域、色、フォントなど描画に関するさまざまな情報を管理しています.例えば、次のようなものがあります.
Graphicsクラスのインスタンス | |
g.setColor(Color.pink) | ピンク色に指定.他にも色には |
cyan,blue,black,white,gray,green,orange,red,yellowなどがある. | |
g.fillOval(0,0,199,99) | 楕円を塗りつぶす.(0,0)は起点、199はx軸、99はy軸の値を表す |
ただし、(0,0)は左上隅である.他にもぬりつぶしには | |
g.fillRect(長方形の塗りつぶし) | |
g.fillArc(円弧の塗りつぶし)などがある. | |
Font f = new Font(“Dialog”,Font.PLAIN,18) | "Dialog"は論理フォント名.他にも |
g.setFont(f) | Serif,DialogInput,Monospaced,Symbolなどがある. |
Font.PLAINはフォントの形式.他にもITALIC,BOLDなどがある. | |
g.setFont(f)でこのあとに続くフォントにはこの設定が生かされる. | |
g.drawString(Hello.str,70,50) | Hello.strはクラスHelloの引数が入る. |
(70,50)は文字列の開始点を表す. | |
他にもg.drawLine(2つの座標点間の直線) | |
g.drawArc(左上の起点、横、縦、始角、弧角による円弧) | |
g.drawOval(左上の起点、横、縦による楕円) | |
g.drawRect(左上の起点、横、縦による長方形)などがある |
2.次に、どこに表示するかを考えます.部品のレイアウトと貼り付けは、中間コンテナの「ContentPane」を経由して行います.Hello2を第2クラスとして、「JFrame」を継承し、第1クラスのオブジェクトを中間クラス「ContentPane」に「add()」メソッドを使って貼り付けます.次のような画面のレイアウトを行います.
setTitle("Hello world"); | //タイトル |
setSize(250,150); | //画面の大きさの設定 |
Container c = getContentPane(); | //入れ物の用意 |
c.add(new Hello1()); | //1で描画したものを貼り付ける |
3.表示したい文字列を読み取り、表示します.Helloをメインクラスとして、パラメターをコマンド・ラインから受け取り第2クラスのオブジェクトを作り、「show()」メソッドによってウィンドウに表示し、「WindowListener」によってイベントを処理を行います.
show(); | |
addWindowListener(new WindowAdapter() | |
public void windowClosing(WindowEvent e) | |
System.exit(0); |
全ての定数は初期化されなければならない.
演習問題解答
a. while(e) s
b. while(true) {s; e;}
i | x |
4.15 | |
0 | 8.3 |
1 | 16.6 |
2 | 33.2 |
int main()
{
int m,n,r;
cout "2つの正の数を入力してください";
cin m n;
if(m n){
int w = m;
m = n;
n = w; }
cout m "と" n "の最大公約数は";
while(n 0){
r = m%n;
m = n;
n = r;
}
cout m endl;
}
確認問題解答
別々にコンパイルされた関数は,ブラックボックスとして扱うことができる.関数が隅々まで一度チェックされれば,ユーザは関数がどのように働くかを気にする必要がなくなり,メイン関数の開発に集中することができるようになる.また.将来関数が改良されても,メイン関数には影響を与えることなく関数だけを入れ替えればよい.
関数の定義を別のファイルで行なっておくことの利点は,その関数を呼ぶ関数に変更があったとしても,エディタに呼び込む必要がない.
この関数は,ディフォルトの値を持っている変数bがディフォルトの値を持っていない変数cの前にある.これは,全てのディフォルト値は他の引数の後に来なければならないという関数の約束に違反している.
演習問題解答
y % 4 == 0 && (y % 100 != 0 y % 400 == 0)の方が効率がよい.なぜなら,もしyが4で割り切れなければそれ以上のテストを行なう必要がないからである.
long comb(int n, int k);
int main()
{
for (int i = -1; i 6; i++){
for (int j = -1; j i+1; j++){
cout " " comb(i,j);
}
cout endl;
}
}
long fact(int n);
long comb(int n, int k)
{
if (n 0 k 0 k n) return 0;
return fact(n)/(fact(k)*fact(n-k));
}
long fact(int n)
{
if (n 2) return 1;
long f = 1;
for(int i=2; i n; i++)
f *= i;
return f;
}
確認問題解答
6.1
配列の要素は全て同じ型でなければならない.したがって1通り.
6.3
配列の大きさより初期化の値が少ないときには,配列の添え字の小さいほうから初期化され,残りは0に初期化される.
演習問題解答
void selection_sort(float a[], int n)
{ //a[k] = max{a[0],a[1],...,a[n-i]}
for (int i=1; i n; i++){
int k = 0;
for(int j=1; j n-i;j++){
if (a[j] a[k]) k = j;
}
swap(a[k], a[n-i]);
}
}
確認問題解答
7.3 int n1 = n;はn1をnのクローンとして定義する.つまり,n1はnと同じ値を持つ別のオブジェクトである.int& n2 = n;はn2をnの別名として定義する.したがって,n2とnは同じアドレスを持ったオブジェクトである.
a. &xと&yはxとyの別名であるから,&x == x,&y == y.したがって,x == yならば&x == &y
b. 間違い.異なるオブジェクトでも同じ値を持つことができる.
7.7 配列名aは定数ポインタなので,増加することができない.したがって,
short a[20];
short* p = a;
for(int i = 0; i 20;i++)
*p++ = i*i;
と書き直せばよい.
7.9 p + qは不可能.なぜなら,ポインタどうしの和は取れない.
演習問題解答
===================================================
double* copy(double a[], int n)
{
double* p = new double[n];
for (int i=0; i n; i++){
p[i] = a[i];
}
return p;
}
void print(double [], int)
int main()
{
double a[8] = {22.2, 33.3, 44.4, 55.5, 66.6, 77.7, 88.8, 99.9}
print(a,8);
double* b = copy(a,8);
a[2] = a[4] = 11.1;
print(a,8);
print(b,8);
}
===================================================
7.3 公式より関数 をで微分するには,小さいhが必要である.そこで,微分係数を求める関数をderivative()関数とすると,
===================================================
double derivative)double (*)(double), double, double);
double cube(double);
int main()
{
cout derivative(cube, 1, 0.01) endl;
}
double derivative(double (*pf)(double t), double a, double h)
{
return ((*pf)(a + h) - (*pf)(a - h)/(2 * h));
}
double cube(double t)
{
return sqrt{t};
}
===================================================
確認問題解答
8.1
a. 次の8個が文字配列の宣言である.
char s[6];
char s[6] = {'H', 'e', 'l', 'l', 'o'};
char s[6] = "Hello";
char s[] = {'H', 'e', 'l', 'l', 'o'};
char s[] = "Hello";
char* s;
char* s = new char[6];
char* s = "Hello";
b. 次の4個が文字配列"Hello"と初期化される.
char s[6] = {'H', 'e', 'l', 'l', 'o'};
char s[6] = "Hello";
char s[] = "Hello";
char* s = "Hello";
c. 実行時にこのように初期化することは不可能である.
d. 次の2つが関数の引数として有効である
char s[];
char* s;
8.3 これは,文字配列s内の大文字を小文字に変えるので,1326 2nd st, brooking, sdと表示される.
8.5 代入s1 = s2により,s1はs2の別名となる.つまり,両方とも同じ文字のポインタである.strcpy(s1,s2)は文字配列s2の文字を文字配列s1にコピーする.したがって,s1はs2の複製である.
演習問題解答
8.1 文字配列s2を文字配列s1にコピーする関数
char* Strcpy(char* s1, const char* s2)
{
char* p;
for (p=s1; *s2; ){
*p++ = *s2++;
}
*p = '0';
return s1;
}
8.3
char* Strncpy(char* s1, const char* s2, size_t n)
{
char* p = s1;
for (; n 0 && *s2;n ){
*p++ = *s2++;
}
for ( ; n 0; n){
*p++ = 0;
}
*p = '0';
return s1;
}
確認問題解答
9.1 文字配列は文字の配列であり,ナル文字を用いて文字列の終わりを表している.C++文字列はstringファイルで定義されたstring型のオブジェクトである.stringファイルにはlength()関数やreplace()関数など多くの文字列関係の関数がある.
char cs[8] = "ABCDEFG"; // csは文字配列
string s = "ABCDEFG"; // sはC++文字列
9.3 抽出演算子は全ての余白文字を無視するので余白文字(空白,タブ,改行,etc)を読むことができない.
9.5 文字列,外部ファイル,内部ファイルの処理に同じような関数を定義することにより簡素化を行なっている.
演習問題解答
char cs1[] = "ABCDEFGHIJ"; // cs1を文字配列として定義
char cs2[] = "ABCDEFGH"; // cs2を文字配列として定義
cout cs2 endl; // 表示: ABCDEFGH
cout strlen(cs2) endl; // 表示:8
cs2[4] = 'X'; // cs2を"ABCDXFGH"に変更
if (strcmp(cs1,cs2) 0) cout cs1 " " cs2 endl;
else cout cs1 " " cs2 endl; // 表示:ABCDEFGHIJ ABCDXFGH
char buffer[80]; // bufferを80文字以下の文字配列として定義
strcpy(buffer, cs1); // bufferを"ABCDEFGHIJ"に変更
strcat(buffer, cs2); // bufferを"ABCDEFGHIJABCDXFGH"に変更
char* cs3 = strchr(buffer, 'G'); // cs3をbuffer[6]のポインタに変更
cout cs3 endl; // 表示:"GHIJABCDXFGH"
確認問題解答
10.1 公開メンバはクラスの外からもアクセス可能であるが,非公開メンバはクラスの外からはアクセスできない.
10.3 ディフォルトコンストラクタは引数を持たない唯一のコンストラクタである.
10.5 コンストラクタはクラスと同じ名前でなければならない.また,ディストラクタもクラスと同じ名前であるが,名前の前にが付く.
10.7 全部で7回.fを一回呼ぶとコピーコンストラクタが3回呼ばれる.
演習問題解答
10.1
#include cmath
#include iostream
using namespace std;
class Point
{
public:
Point(float x = 0, float y = 0, float x=0): x_(x), y_(y), z_(z) { }
Point(const Point& p) : x_(p.x), y_(p.y), z_(p.z) { }
void negate() {x_ *= -1; y_ *= -1; z_ *= -1;}
double norm() {return sqrt(x_*x_ + y_*y_ + z_*z_); }
void print()
{cout '(' x_ y_ "," z_ ")";}
private:
float x_, y_, z_;
};
確認問題解答
11.1
予約語operatorは演算子を多重定義する関数名を作成するのに用いられる.例えば,代入演算子=を多重定義する関数名は"operator="となる.
11.3
thisポインタはメンバ関数を呼び出すオブジェクトのポインタである.したがって,メンバ関数以外ではthisポインタを用いることができない.
11.5
宣言Ratio y = x;はコピーコンストラクタを呼ぶ.Ratio y; y = x;はまず,ディフォルトコンストラクタを呼び,次に代入演算子を呼ぶ.
11.7
左側オペランドはストリームオブジェクトでなければならないからである.
演習問題解答
11.1
void Fraction::reduce()
{
if (numerator != 0 && denominator != 0){
int tmp = gcd(abs(numerator), denominator);
numerator /= tmp;
denominator /= tmp;
}
}
int Fraction::gcd(int x, int y)
{
while(xint r = xx = y;
y = r;
}
return y;
}
確認問題解答
12.1
クラスの合成とは,一つ以上のクラスを別のクラスの定義の中で用いることである.それに対して,継承は基本クラスから派生クラスを派生させることである.
12.3
仮想関数とは,基本クラス内で宣言され,派生クラス内で再定義されるメンバ関数のことである.
12.5
多相とは,仮想関数を持ったクラスでオブジェクトポインタを用いたときにおきる動的結合のことである.
12.7
静的結合とは,コンパイル時にメンバ関数の呼び出しを関数自身にリンクすることである.また,動的結合は,これを実行時まで延期する.
演習問題解答
12.1
protectedメンバ変数aは,それが現行のオブジェクトのメンバであるときだけ派生クラスYからアクセスが可能である.したがって,Yはx.aにアクセスすることができない.
確認問題解答
13.1
関数テンプレートは汎用関数を作成するのに用いるテンプレートである.テンプレート関数はテンプレートによって作成された関数である.例えば,swap(T&, T&)は関数テンプレートであるが,swap(m,n)によりテンプレート関数が生成される.
13.3
ベクタは添え字演算子を用いることにより,その要素に直接アクセスできる.よって,要素が順に並んでいれば2分探索法で簡単にどこにあるか調べることが出来る.それに対して,リストは動的であるという利点を持っている.つまり,必要以上の容量をとらない.また,コンピュータのメモリの制限以外のサイズの制限がない.したがって,ベクタは時間の優位性があり,リストは容量の優位性がある.
演習問題解答
13.1
最小関数miniは同じ型の2つのものを比較し,小さい方を返す関数である.そこで,型は汎用型Tとし次のように定義する.
template class T
T mini(T x, T y){
return (x y ? x: y);
}
この関数を実装するためのテストドライバを次のようにすると,
#includeiostream
using namespace std;
int main()
{
cout "min(22,44) = " mini(22,44) endl;
cout "min(34.43, 43.34) = " mini(34.43, 43.34) endl;
}
確認問題解答
14.1
配列とC++ベクタの違いは
a. 配列は string[8] a;と宣言されベクタは vectorstring v(8);と宣言される.
b. 代入演算子はベクタには定義されているが,配列にはない.したがって,配列では1つ1つの要素をコピーしなくてはならない.
c. 比較演算子はベクタには定義されているが,配列にはない.
if (v == w) //2つのベクトルが等しければ真
if (v w) //ベクトルの辞書順序により比較
d. size()メンバ関数がベクタには使えるが,配列では無理
e. at()メンバ関数がベクタには使えるが,配列では無理
string v8 = v.at(8); //ポジション8でのvの要素
演習問題解答
14.1
int frequency(vectorint v, int x)
{
int n=0;
for (vectorint::iterator it=v.begin(); ; it++){
it = find(it, v.end(), x);
if (it==v.end()) return n;
++n;
}
return n;
}