アフィリエイト広告を利用しています

広告

この広告は30日以上更新がないブログに表示されております。
新規記事の投稿を行うことで、非表示にすることが可能です。
posted by fanblog

2017年12月12日

《その174》 『 is-a 』の関係


 基底クラスを指すポインタは、派生クラスを指すことができます。また、基底クラスへの参照は、派生クラスを参照できます。このことを、下のプログラムで確認してみます。

 ◆ クラス Aaa を基底クラスとして、public派生により 派生クラス Bbb を作ります。
 最初に、下のプログラムの説明を書きます。



【基底クラス Aaa について】

 非公開データメンバ int a,公開メンバである コンストラクタ Aaa,公開メンバ関数であるゲッタ get_a とセッタ set_a で構成されています。

 コンストラクタ Aaa が、デフォルトコンストラクタとしてはたらく場合(引数無しで呼び出された場合)は、データメンバ a は 10 で初期化されます。


【派生クラス Bbb について】

 クラス Aaa を親として、public派生により 作成される子クラスです。

 コンストラクタ Bbb が、デフォルトコンストラクタとしてはたらく場合は、データメンバ a は 30 で初期化されます。


【関数 twice について】
 Aaa型クラスオブジェクトへの参照を受け取り、データメンバ a の値を2倍します。

 基底クラスへの参照は、派生クラスのオブジェクトを参照することができます。
関数 twice は、Aaa型クラスオブジェクトへの参照を受け取る仕様(プログラムの★1)であるにもかかわらず、実際に関数としてはたらく際には、Bbb型クラスオブジェクトへの参照を受け取る(プログラムの★2)こともできる点に注目してください。


【その他】
 基底クラスのメンバ関数型のポインタ(プログラムの★3)は、派生クラスオブジェクトのメンバ関数を指すことができます(プログラムの★4)。

 基底クラスオブジェクト型のポインタ(プログラムの★5)は、派生クラスのオブジェクトを指すことができます(プログラムの★6)。


『 is-a 』の関係

 今回のプログラムの例では、基底クラス Aaa への参照は、派生クラス Bbb を参照できました。また、Aaa へのポインタは、Bbb を指すことができました。
このことから、
「Bbb は Aaa の一種である。」
「Bbb is a Aaa.」 (*。・ ・。* ) an Aaa.
と表現することもできる、ということから、このような関係を『 is-a 』と呼ぶことがあります。


// ------------------------------------
#include <iostream>
using namespace std;

class Aaa { // ◆基底クラス
int a;
public:
Aaa(int n = 10)
: a(n) { }
int get_a() const { return a; }
void set_a(int n) { a = n; }
};

class Bbb : public Aaa { // ◆派生クラス
int b;
public:
Bbb(int n = 30)
: Aaa(n), b(99) { }
int get_b() const { return b; }
};

void twice(Aaa& x) { /* ★1 */
int n = x.get_a();
x.set_a(2 * n);
}

int main() {
Aaa a1;
Bbb b1;

cout << a1.get_a() << '\n';
cout << b1.get_a() << '\n';

twice(a1);
twice(b1); /* ★2 */

cout << a1.get_a() << '\n';
cout << b1.get_a() << '\n';

int (Aaa::*ptr)()const; /* ★3 */
// クラス Aaa のメンバ関数へのポインタ ptr の宣言
ptr = &Aaa::get_a;
// ポインタ ptr がメンバ関数 get_a を指すように設定
cout << (b1.*ptr)() << '\n'; /* ★4 */

Aaa* ptr2; /* ★5 */
// Aaa*型のポインタ ptr2 の宣言
ptr2 = &b1;
// ポインタ ptr2 がメンバオブジェクト b1 を
// 指すように設定

cout << (*ptr2).get_a() << '\n'; /* ★6 */
}
// ------------------------------------
f04_000502.png


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





2017年12月11日

《その173》 コピーコンストラクタ,デストラクタ,代入演算子 の継承


 コピーコンストラクタ,デストラクタ,代入演算子 の継承

 基底クラスからの派生によって作成される派生クラスについて、デフォルトコンストラクタ,コピーコンストラクタ,代入演算子が、どのような形で継承されるのかを、下のプログラムで確認してみます。

 ◆ クラス Array を基底クラスとして、public派生により 派生クラス ArrayX を作ります。
 最初に、下のプログラムの説明を書きます。


基底クラス Array について

 配列要素数 num = 5
 p は配列の先頭要素へのポインタ用の変数です。

 char型の chr には、main関数内で作成する クラスオブジェクト a, b, c を識別するために、それぞれ文字 'a', 'b', 'c' を、実行時に代入します。プログラム終了時にクラスオブジェクトが、デストラクタによって破壊される直前に、この文字を表示します。

 Array() は引数無しのデフォルトコンストラクタです。
new int[num] により int型配列用の領域を動的に確保して、その先頭要素のアドレスをポインタ p に代入します。

 Array(const Array& x) はコピーコンストラクタです。
コピー元の配列 int[x.num] と同じ大きさの領域を動的に確保し、そこに、コピー元の配列要素をすべてコピーします。

 ~Array() はデストラクタです。
プログラム終了時に、クラスオブジェクトが破壊されるときに呼ばれます。動的に確保した記憶領域を解放します。また、 'a', 'b', 'c' のいずれかの文字を表示して自分の正体をアピールします。

 Array& operator=(const Array& x) は代入演算子の多重定義です。
代入演算子「=」が、Array型オブジェクトを扱えるようにするための多重定義です。
代入される側のオブジェクトが、自分自身の中に、コピー元の配列 int[x.num] と同じ大きさの領域を動的に確保し、そこに、コピー元の配列要素をすべてコピーします。

 メンバ関数 void set(int v)
配列 p の全要素に値 v を代入します。

 メンバ関数 void print() const
配列 p の全要素の値を表示します。


派生クラス ArrayX について

 クラス Array を親として、public派生により 作成される子クラスです。継承の際は、コンストラクタ,デストラクタ,代入演算子 を定義せず、すべて基底クラス Array の資産を利用するものとします。

 int k は、派生クラス ArrayX独自のデータメンバです。

 void set_k(int x) は、派生クラス ArrayX独自のメンバ関数で、k のセッタです。


// ------------------------------------
#include <iostream>
using namespace std;

class Array { /* ◆基底クラス */
const int num = 5;
int *p;

public:
char chr;
Array() : p(new int[num]) {
cout << "デフォルトコンストラクタ(領域確保)\n";
}
// ↑デフォルトコンストラクタ
// 要素数 num の int型配列用領域を確保


Array(const Array& x) : p(new int[x.num]) {
for (int i = 0; i < num; i++) p[i] = x.p[i];
cout << "コピーコンストラクタ\n";
}
// ↑コピーコンストラクタ

~Array() {
delete[] p;
cout << "デストラクタ, chr … " << chr << '\n';
}
// ↑デストラクタ

Array& operator=(const Array& x) {
cout << "代入演算子\n";
for (int i = 0; i < num; i++)
p[i] = x.p[i];
return *this;
}
// ↑代入演算子


void set(int v) {
for (int i = 0; i < num; i++) p[i] = v;
}
// ↑メンバ関数
// 全要素に値 v を代入


void print() const {
for (int i = 0; i < num; i++) cout << p[i] << ' ';
}
// ↑メンバ関数
// 全要素の値を表示

};


class ArrayX : public Array { /* ◆派生クラス */
int k;
public:
int get_k() const { return k; }
void set_k(int x) { k = x; }
};
// コンストラクタ,コピーコンストラクタ,デストラクタ
// の定義はしていない。


int main() {
ArrayX a; // ArrayX a を作成
/* ↑デフォルトコンストラクタが作動 */
a.set(10); // aの全要素に 10 を代入
a.set_k(99); // a.k に 99 を代入

ArrayX b(a); // ArrayX b を作成(aで初期化)
/* ↑コピーコンストラクタが作動 */


ArrayX c; // ArrayX c を作成
/* ↑デフォルトコンストラクタが作動 */

c = a;
/* ↑代入演算子が作動(aの全要素をcにコピー) */

a.chr = 'a'; b.chr = 'b'; c.chr = 'c';

cout << '\n';
cout << "配列a : "; a.print(); cout << '\n';
cout << " k : " << a.get_k() << '\n';
cout << "配列b : "; b.print(); cout << '\n';
cout << " k : " << b.get_k() << '\n';
cout << "配列c : "; c.print(); cout << '\n';
cout << " k : " << c.get_k() << '\n';
cout << '\n';
}
// ------------------------------------
f04_0004.png

 プログラムの実行結果を見ると・・・、
 派生クラス ArrayX の中では、コピーコンストラクタ,デストラクタ,代入演算子 が定義されていないのですが、基底クラス Array の コピーコンストラクタ,デストラクタ,代入演算子 が、派生クラスに対しても、きちんと動作しているようです。
また、派生クラス独自のデータメンバである k や、ゲッタ get_k(),セッタ set_k() も、期待通りにコピーされています。

派生クラス内で、コピーコンストラクタ,デストラクタ,代入演算子 が定義されなければ、基底クラスのものと実質的に同じ働きをするものが、コンパイラにより自動的に定義されます。
派生クラス ArrayX内で自動的に定義される代入演算子は、次の形式のものです。
  ArrayX& ArrayX::operator=(const ArrayX&);
自動的に定義される コピーコンストラクタ,デストラクタ,代入演算子 は、inline かつ public です。


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





2017年12月10日

《その172》 派生クラスオブジェクトの初期化順序 & p.161演習4-2


 派生クラスオブジェクトの初期化順序

 派生クラスのコンストラクタが初期化作業を行うとき、その作業は次の順序で実行されます。

(1) 基底クラス部分オブジェクトの初期化
   初期化作業は、基底クラスのコンストラクタに任せる。

(2) データメンバの初期化
   クラス定義内で宣言された順に、データメンバが初期化される。

(3) コンストラクタ本体の実行


新版明解C++中級編 p.161 演習4-2
 次のプログラムによって、派生クラスオブジェクトの初期化順序を確認することができる。
このプログラムを、デストラクタの起動順序を確認できるように書きかえたプログラムを作成せよ。

// ------------------------------------
#include <iostream>
using namespace std;

// クラス Derived の基底クラス
class Base {
int x;
public:
Base(int a = 0) : x(a) {
cout << "Base::x を" << x << "で初期化。\n";
}
};

// クラス Derived にメンバとして含まれるクラス
class Memb {
int x;
public:
Memb(int a = 0) : x(a) {
cout << "Memb::x を" << x << "で初期化。\n";
}
};

// 派生クラス Derived を生成
// (基底クラス Base から public派生)
class Derived : public Base {
int y;
Memb m1;
Memb m2;
void say() {
y = 0;
cout << "Derived::y を" << y << "で初期化。\n";
}
public:
Derived() { say(); }
Derived(int a, int b, int c)
: m2(a), m1(b), Base(c) {
say();
}
};

int main() {
Derived d1;
cout << '\n';
Derived d2(1, 2, 3);
}

// *** 実行結果 ****************
// * Base::x を0で初期化。 *
// * Memb::x を0で初期化。 *
// * Memb::x を0で初期化。 *
// * Derived::y を0で初期化。 *
// * *
// * Base::x を3で初期化。 *
// * Memb::x を2で初期化。 *
// * Memb::x を1で初期化。 *
// * Derived::y を0で初期化。 *
// *****************************
// ------------

◆以下が解答のプログラムと、その実行結果です。

// p161_演習4-2
#include <iostream>
using namespace std;

// クラス Derived の基底クラス
class Base {
int x;
public:
Base(int a = 0) : x(a) {
cout << "Base::x を" << x << "で初期化。\n";
}
~Base() { cout << "デストラクタ ~Base, "
<< "x = " << x << '\n';
}
};

// クラス Derived にメンバとして含まれるクラス
class Memb {
int x;
public:
Memb(int a = 0) : x(a) {
cout << "Memb::x を" << x << "で初期化。\n";
}
~Memb() { cout << "デストラクタ ~Memb, "
<< "x = " << x << '\n'; }
};

// 派生クラス Derived を生成
// (基底クラス Base から public派生)

class Derived : public Base {
int y;
Memb m1;
Memb m2;
void say() {
y = 0;
cout << "Derived::y を" << y << "で初期化。\n";
}
public:
Derived() { say(); }
Derived(int a, int b, int c)
: m2(a), m1(b), Base(c) {
say();
}
~Derived() { cout << "\nデストラクタ ~Derived, "
<< "y = " << y << '\n';
}
};

int main() {
Derived d1;
cout << '\n';
Derived d2(1, 2, 3);
}
f04_02.png
 この実行結果から、デストラクタの起動順序は、コンストラクタの初期化作業のときとは逆の順序であることが確認できます。



新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





《その171》 継承とデフォルトコンストラクタ & p.159演習4-1


 継承とデフォルトコンストラクタ

 派生クラスにおいてコンストラクタが定義されていない場合は、コンパイラによって自動でデフォルトコンストラクタが定義されます。その際、『その派生クラスの直接基底クラスが"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていなければ、コンパイルエラーとなります。』
次の 演習4-1 は、そのことを確認するための問題です。

新版明解C++中級編 p.159 演習4-1
 下記のプログラムで、クラス Base のコンストラクタを、次の二通り @, Aのコンストラクタに置きかえたプログラムを作成して、下の【重要事項】の内容を確認せよ。
 @ Base::Base(int xx)
   : x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
 A Base::Base(int xx = 99)
   : x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
 【重要事項】コンストラクタを定義しないクラスは、そのクラスの直接基底クラスが"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていなければ、コンパイルエラーとなる。

// ------------------------------------
#include <iostream>
using namespace std;

class Base {
int x;
public:
Base() : x(99) { cout << "Base::x を 99 で初期化。\n"; }
// ↑のコンストラクタを書きかえること。

int get_x() const { return x; }
};

class Derived : public Base {
};

int main()
{
Derived d;
cout << "d.get_x() = " << d.get_x() << '\n';
}

// *** 実行結果 ******************
// * Base::x を 99 で初期化。 *
// * d.get_x() = 99 *
// *******************************
// ------------

◆以下が解答になります。
 元々のコード
  Base() : x(99) { cout << "Base::xを99で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっているので、コンパイルは成功します。

 @のコード
  Base::Base(int xx)
   : x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていないので、コンパイルエラーになります。

 Aのコード
  Base::Base(int xx = 99)
   : x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっているので、コンパイルは成功します。

以下は、Aの場合のプログラムと、その実行結果です。

// p159_演習4-1
#include <iostream>
using namespace std;

class Base {
int x;
public:
Base::Base(int xx = 99) : x(xx) {
cout << "Base::x を " << x << " で初期化。\n";
}
int get_x() const { return x; }
};

class Derived : public Base {
};

int main()
{
Derived d;
cout << "d.get_x() = " << d.get_x() << '\n';
}
f04_01.png


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





《その170》 クラスの継承(3)


 派生クラスのコンストラクタ

 派生クラスの初期化時、基底クラスから継承したデータメンバの初期化は、基底クラスのコンストラクタに任せるようにします。次のプログラムで、派生クラス C_drvd のコンストラクタ内の赤文字部分です。

// ------------------------------------
#include <iostream>
using namespace std;

// 基底クラス C_base
class C_base {
int a;
int b;
public:
C_base(int x, int y) : a(x), b(y) { }
int get_a() const { return a; }
int get_b() const { return b; }
};

// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
public:
C_drvd(int x, int y, int z)
: C_base(x, y), k(z) { }
void disp() const {
cout << "a = " << get_a()
<< ", b = " << get_b()
<< ", k = " << k;
}
};

int main() {
C_drvd d(1, 2, 3);
d.disp();
cout << '\n';
}
// ------------------------------------
f04_0003.png


 派生クラスのデフォルトコンストラクタ

 派生クラスにコンストラクタの定義が無い場合は、デフォルトコンストラクタが自動的に定義されます。
よって、初期化時に値を与える必要がある基底クラスを親にして、コンストラクタの定義がない派生クラスを作ろうとするとエラーになります。
次のような場合です。

// ----------------------------
// 基底クラス C_base
class C_base {
int a;
public:
C_base(int x) : a(x) { }

// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
}; // エラー
// ----------------------------


コンパイラによって自動的に定義されるデフォルトコンストラクタ
  C_drvd() { }
は、int a の初期化を、基底クラス C_base に任せますから、
  C_base(int x) : a(x) { }   // C_baseのコンストラクタ
が呼ばれることになります。
ところが、このコンストラクタは int x の値を与えないと呼び出せません。


 このエラーは、基底クラス C_base のコンストラクタを、引数を与えなくても呼び出せるようにすれば、回避できます。
例えば、次のようにします(赤文字部分)。

// ----------------------------
// 基底クラス C_base
class C_base {
int a;
public:
C_base(int x = 0) : a(x) { }
};

// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
}; // 大丈夫
// ----------------------------


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





2017年12月09日

《その169》 クラスの継承(2)


 クラスの継承

 前回《168》、クラス Base を元にして、public派生の形態で派生クラス Derived を作りました。
 今回は、private派生の形態で派生クラスを作り、その違いを見てみます。前回同様、基底クラスの名前を Base、派生クラスの名前を Derived とすることにします。

 public派生とprivate派生の差異の詳細は、ここでは扱いませんが、いちばん目立つ違いは、次の点だろうと思います。

public派生
  基底クラスの publicメンバは、派生クラスにおいても publicメンバである。

private派生
  基底クラスの publicメンバは、派生クラスにおいては privateメンバである。


// ------------------------------------
#include <iostream>
using namespace std;

// 基底クラス Base
class Base {
int x;
public:
Base() : x(99) { }
int get_x() const { return x; }
void set_x(int n) { x = n; }
};

// 派生クラス Derived
class Derived : private Base { // private派生
// private派生の場合、基底クラス Base の publicメンバ
// は、派生クラス内では、privateメンバになります。

public:
int y;
int get_x_() const { return get_x(); }
// 基底クラス Base の publicメンバ関数は、派生クラ
// ス内では private になってしまいますから、
// 利用者は get_x関数 や set_x関数を使うこ
// とができません。
// 利用者が、データメンバ x の値を知る必要があるな
// ら、そのためのゲッタが必要です。


void set_x_(int n) { set_x(n); }
// 利用者が、データメンバ x に値をセットするための
// セッタです。

};

int main()
{
Derived d;

cout << d.get_x_() << '\n'; // 99
d.set_x_(10);
cout << d.get_x_() << '\n'; // 10
d.y = 20;
cout << d.y << '\n'; // 20
cout << d.get_x_() << " " << d.y << '\n'; // 10 20

Base b;

cout << b.get_x() << '\n'; // 99

// b = d; // 不可
// アクセスできない基底クラス Base への変換は許可され
// ていません。

}
// ------------------------------------
f04_0001.png



新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





2017年12月08日

《その168》 クラスの継承(1)


 クラスの継承

 クラス A を元にして、派生クラス B を作ることが可能です。クラス A が持っている機能をそのまま継承して、さらにそれに新しい機能を付加することができます。
 次のプログラムは、自分用のレベルなので初歩の初歩レベルなのですが、派生クラスの作り方や基本事項のいくつかが含まれています。

// ------------------------------------
#include <iostream>
using namespace std;

// 基底クラス Base
class Base {
int x;
public:
Base() : x(99) { }
int get_x() const { return x; }
void set_x(int n) { x = n; }
};

// 派生クラス Derived
class Derived : public Base {
// Baseクラスを親クラスとして、子クラス Derived を作成。
// public派生なので、基底クラス Base のpublicメンバは、
// 派生クラス Derived においても publicメンバです。

public:
int y;

// 派生クラス Derived 独自のデータメンバです。
};


int main()
{
Derived d;

cout << d.get_x() << '\n'; // 99
// Baseクラスの公開メンバ関数 get_x は、
// 派生クラス Derived の公開メンバ関数です。


d.set_x(10);
// データメンバ x に値をセット。
// Baseクラスの公開メンバ関数 set_x は、
// 派生クラス Derived の公開メンバ関数です。


cout << d.get_x() << '\n'; // 10
// 値が変更されています。

int Derived::*ptr = &Derived::y;
d.*ptr = 20;
// Derivedクラスには、データメンバ y 用のセッタが
// ないので、Derived::y へのポインタ ptr を利用
// して、y に値をセット。
// y は publicメンバなので、単に
// d.y = 20;
// とするだけでいいのに、ちょっと前回《167》の復習
// をしてみたかったので・・・ (*^^*)ゞ


cout << d.y << '\n'; // 20
// 値が変更されています。

cout << d.get_x() << " " << d.y << '\n'; // 10 20
// 現時点での x, y の値です。

Base b;
// Base型のクラス b を作りました。

cout << b.get_x() << '\n'; // 99
// Base のコンストラクタの設定通りの値です。

b = d;
// 基底クラス(親クラス) Base のオブジェクト b に、
// 派生クラス(子クラス) Derived のオブジェクト
// d を代入します。
// b と d は型が違うのですが、大丈夫です。


cout << b.get_x() << '\n'; // 10
// Base のコンストラクタの設定通りの値だったのが、
// オブジェクト d のデータメンバ x の値に変更
// されています。
// b = d; は可能ですが、
// d = b; はできません。 d.y に何を代入したらいい
// のかが分からないからです。

}
// ------------------------------------
f04_0001.png



新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





2017年12月07日

《その167》 静的メンバへのポインタ & p.135演習3-14


 静的メンバへのポインタ

 静的データメンバへのポインタ や 静的メンバ関数へのポインタについて、チェックしてみたのが次のプログラムです。
 クラスの静的メンバを指すポインタは、通常のタイプのポインタ型として定義します。静的メンバは、クラスに属してはいますが、その実体は個々のクラスオブジェクトとは無関係なので、通常のタイプのポインタで指すことができます。

// ------------------------------------
#include <iostream>
using namespace std;

class C {
public:
static int n; // 静的データメンバ
static void func() { // 静的メンバ関数
std::cout << "static void func();\n";
}
int a; // 通常のデータメンバ
int get_a() const{ return a; }
// 通常のメンバ関数
C() { a = 1000; } // コンストラクタ
};

int C::n = 9999;
// 静的データメンバの実体は、クラス定義の外で
// static を付けずに定義する。


int main()
{
C test;

int *ptr1 = &C::n;
// 静的データメンバへのポインタ
void(*ptr2)() = &C::func;
// 静的メンバ関数へのポインタ
int C::*ptr3 = &C::a;
// 通常のデータメンバへのポインタ
int (C::*ptr4)() const= &C::get_a;
// 通常の静的メンバ関数へのポインタ

cout << *ptr1 << '\n';
(*ptr2)();
cout << test.*ptr3 << '\n';
cout << (test.*ptr4)() << '\n';
}
// ------------------------------------
f03_0039.png


新版明解C++中級編 p.135 演習3-14
 次のようなクラス Person と、Person型の配列がある。この配列をクイックソートアルゴリズムによってソートするプログラムを作成せよ。qsort関数を利用することなく実現すること。
【 クラス person 】
class Person {
public:
char name[10]; // 名前
int height; // 身長
int weight; // 体重
};

【 Person型の配列 】
Person x[]= {{"Shibata", 170, 52},
{"Takaoka", 180, 70},
{"Nangoh", 172, 63},
{"Sugiyama", 165, 50},
};

// p135_演習3-14
#include <iomanip>
#include <cstdlib>
#include <iostream>
using namespace std;

class Person {
public:
char name[10]; // 名前
int height; // 身長
int weight; // 体重
};

// x, yの指すnバイトの領域を交換する関数。
namespace {
void memswap(void* x, void* y, size_t n)
{
unsigned char* a = reinterpret_cast<unsigned char*>(x);
unsigned char* b = reinterpret_cast<unsigned char*>(y);

for (; n--; a++, b++) {
unsigned char c = *a;
*a = *b;
*b = c;
}
}
}

void quicksort(void* base, size_t nmemb, size_t size,
int(*compar)(const void*, const void*))
{
if (nmemb > 0) {
const char* v = reinterpret_cast<const char*>(base);
// 配列 base の先頭要素へのポインタ v
size_t pl = 0; // 探索範囲左端の位置(添字)
size_t pr = nmemb - 1; // 探索範囲右端の位置(添字)
size_t pv = nmemb; // 基準値の位置(添字)
size_t pt = (pl + pr) / 2; // 更新後の基準値の位置(添字)

do {
const char* x = &v[pt * size];
// const char*型のポインタ x
// 関数内の配列 v の各要素を指すポインタ用の変数です。
// v[pt * size] は 基準値 base[pt] に対応します。


if (pv != pt) x = &v[(pv = pt) * size];
// 基準値の位置に変更があった場合は、更新します。

while (compar(reinterpret_cast<const void*>(&v[pl * size]), x) < 0)
pl++;
// 探索範囲左端からチェックしていき、基準値のほうが大きければ、
// 探索範囲左端の位置 pl をインクリメント。
// 基準値以上の値を見つけたら、pl はその値の位置で止まります。


while (compar(reinterpret_cast<const void*>(&v[pr * size]), x) > 0)
pr--;
// 探索範囲右端からチェックしていき、基準値のほうが小さければ、
// 探索範囲右端の位置 pr をデクリメント。
// 基準値以下の値を見つけたら、pr はその値の位置で止まります。


if (pl <= pr) {
// 探索範囲の左端と右端が逆転していないうちは・・・
pt = (pl == pv) ? pr : (pr == pv) ? pl : pv;
// 探索範囲の左端が基準値の位置に達している場合は、基準値の位置
// を現時点での右端の位置に更新します。
// 探索範囲の右端が基準値の位置に達している場合は、基準値の位置
// を現時点での左端の位置に更新します。
// それ以外なら、基準値の位置は更新しません。
// ※以上の操作は、memswap関数が値を交換しても基準値の値が変化
// しないようにするため。


memswap(const_cast<void*>(reinterpret_cast<const void*>(&v[pl * size])),
const_cast<void*>(reinterpret_cast<const void*>(&v[pr * size])),
size);
pl++;
if (pr == 0) // 符号無し整数 0 からのデクリメントを避ける。
goto QuickRight;
pr--;
}
} while (pl <= pr);
// 配列 base は、do文が終了すると、quicksort関数内で最初に決めた
// 基準値 base[pt] に対してその値以上の部分と以下の部分に分けられます。


if (0 < pr)
// do文で二つに分けられた区間の左側を対象にして、
// quicksort関数を再帰的に呼び出します。

quicksort(const_cast<void*>(reinterpret_cast<const void*>(&v[0])),
pr + 1, size, compar);
QuickRight:
if (pl < nmemb - 1)
// do文で二つに分けられた区間の右側を対象にして、
// quicksort関数を再帰的に呼び出します。

quicksort(const_cast<void*>(reinterpret_cast<const void*>(&v[pl * size])),
nmemb - pl, size, compar);
}
}

int acmp(char* x, char* y) {
// 比較関数 : 配列による文字列用
return strcmp(x, y);
}

int pcmp(char** x, char** y) {
// 比較関数 : ポインタによる文字列用
return strcmp(*x, *y);
}

int ncmp(int* x, int* y) {
// 比較関数 : int型の整数用
return *x < *y ? -1 :
*x > *y ? 1 : 0;
}

int npcmp(const Person* x, const Person* y) {
// 比較関数 : Person型の名前メンバ用
return strcmp((*x).name, (*y).name);
}

int hpcmp(const Person* x, const Person* y) {
// 比較関数 : Person型の身長メンバ用
return (*x).height < (*y).height ? -1 :
(*x).height >(*y).height ? 1 : 0;
}

int wpcmp(const Person* x, const Person* y) {
// 比較関数 : Person型の体重メンバ用
return (*x).weight < (*y).weight ? 1 :
(*x).weight >(*y).weight ? -1 : 0;
}

void print_person(const Person x[], int no) {
// Person型データを一覧表示
for (int i = 0; i < no; i++)
cout << setw(10) << left << x[i].name << " "
<< x[i].height << "cm " << x[i].weight << "kg\n";
}


int main()
{
Person x[] = { { "Shibata", 170, 52 },
{ "Takaoka", 180, 70 },
{ "Nangoh", 172, 63 },
{ "Sugiyama", 165, 50 },
};

int nx = sizeof(x) / sizeof(x[0]);

puts("ソート前");
print_person(x, nx);

// 名前昇順にソート
quicksort(x, nx, sizeof(Person),
reinterpret_cast<int(*)(const void*, const void*)>(npcmp));

cout << "\n名前昇順ソート後\n";
print_person(x, nx);

// 身長昇順にソート
quicksort(x, nx, sizeof(Person),
reinterpret_cast<int(*)(const void*, const void*)>(hpcmp));

cout << "\n身長昇順ソート後\n";
print_person(x, nx);

// 体重降順にソート
quicksort(x, nx, sizeof(Person),
reinterpret_cast<int(*)(const void*, const void*)>(wpcmp));

cout << "\n体重降順ソート後\n";
print_person(x, nx);
}

f03_14.png


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





《その166》 データメンバへのポインタ & p.133演習3-13


 クラスのデータメンバへのポインタ

 データメンバへのポインタは、メンバ関数のときと同じ形式で宣言します。
次のプログラムで確認してください。

// ------------------------------------
#include <iostream>

class C {
public:
char chr;
C() { chr = 'a'; }
char ch() { return chr; }
};

C c;// C型のクラスオブジェクト c を作る。

int main() {
char C::*ptr_c; // データメンバへのポインタ ptr_c の宣言
char (C::*ptr_f)(); // メンバ関数へのポインタ ptr_f の宣言

ptr_c = &C::chr;
// ポインタ ptr_c がデータメンバ C::chr を指すように設定
ptr_f = &C::ch;
// ポインタ ptr_f がメンバ関数 C::ch を指すように設定

std::cout << (c.*ptr_f)() << '\n';
// std::cout << c.ch() << '\n'; と同じこと
c.*ptr_c = 'b';
// c.chr = 'b'; と同じこと
std::cout << (c.*ptr_f)() << '\n';
}
// ------------------------------------

f03_0038.png


新版明解C++中級編 p.133 演習3-13
 次のプログラムは、ユーザが年/月/日の中から選んだ値を当てさせる日付当てゲームである。
年/月/日のすべてを、この順で当てさせるようにしたプログラムを作成せよ。なお、すべての値が当たるまでは、プログラムは終了しないものとする。

// ------------------------------------
// SimpleDate.h ヘッダファイル

#ifndef ___Class_SimpleDate
#define ___Class_SimpleDate

class SimpleDate {
int y; // 西暦年
int m; // 月
int d; // 日

public:
SimpleDate(int yy = 1, int mm = 1, int dd = 1)
: y(yy), m(mm), d(dd) { }

int year() const { return y; }
int month() const { return m; }
int day() const { return d; }

int comp_y(int yy) const { return yy - y; }
// yyから年を減じた値を返却
int comp_m(int mm) const { return mm - m; }
// mmから月を減じた値を返却
int comp_d(int dd) const { return dd - d; }
// ddから日を減じた値を返却
};

#endif

// ------------

// SimpleDateTest.cpp ファイル

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

int main() {
typedef int (SimpleDate::*Comp)(int) const;
Comp pcomp[] = {
&SimpleDate::comp_y,
&SimpleDate::comp_m,
&SimpleDate::comp_d,
};
int menu;
const SimpleDate birthday(1963, 11, 18);

cout << "私の誕生日を当ててください。\n";

do {
cout << "0…年/1…月/2…日/3…終了:"; cin >> menu;

if (menu >= 0 && menu <= 2) {
int value;
cout << "いくつかな:"; cin >> value;

int diff = (birthday.*pcomp[menu])(value);
if (!diff)
cout << "正解です。\n";
else if (diff > 0)
cout << diff << "だけ大きいです。\n";
else
cout << -diff << "だけ小さいです。\n";
}
} while (menu != 3);
}
// ------------------------------------

 下のプログラムが解答です。なお、ヘッダファイル SimpleDate.h は問題で指定された上記のプログラムと同じなので、SimpleDateTest.cppファイルのコードだけを下記します。

// p133_演習3-13
// 問題で指定された仕様に変更した SimpleDateTest.cppプログラム
#include <iostream>
#include "SimpleDate.h"
using namespace std;

int main() {
typedef int (SimpleDate::*Comp)(int) const;
Comp pcomp[] = {
&SimpleDate::comp_y,
&SimpleDate::comp_m,
&SimpleDate::comp_d,
};
const char* menu[] = { "年", "月", "日" };
const SimpleDate birthday(1963, 11, 18);

cout << "私の誕生日を当ててください。\n";

for (int i = 0; i < 3; i++) {
int value;
do {
cout << "◆ " << menu[i] << " を入力 : "; cin >> value;
int diff = (birthday.*pcomp[i])(value);

if (!diff) {
cout << "正解です。\n";
break;
}
else if (diff > 0)
cout << diff << "だけ大きいです。\n";
else
cout << -diff << "だけ小さいです。\n";
} while (1);
}
}

f03_13.png


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)





《その165》 関数へのポインタ,メンバ関数へのポインタ


 関数へのポインタ と メンバ関数へのポインタ

 下のプログラムに実用的な意味はありませんが、main関数が関数 func を呼び出す際に、次の情報を渡しています。
 ・呼び出された関数 func が、次に呼び出すべき関数
              (この場合は関数 f1 と f2 のどちらか)
 ・孫呼出しされる関数(f1 または f2)が使用する、クラス C のメンバ関数
              (この場合は関数 get_n1 と get_n2 のどちらか)


// ------------------------------------
#include <iostream>
using namespace std;

class C {
int num1;
int num2;
public:
C() { num1 = 100; num2 = 200; }
int get_n1() const { return num1; }
int get_n2() const { return num2; }
};

C c;

int f1(int (C::*get)() const);
int f2(int (C::*get)() const);
int func(int(*f)(int (C::*)() const), int (C::*get)() const);

int main() {
cout << func(f1, &C::get_n1) << '\n'; // @
// func(f1, &C::get_n1) … 関数 func を呼び出します。
// 引数 f1 は関数 f1 へのポインタです。関数名は、その関数へのポインタと解釈されます。
// 引数 &C::get_n1 は、クラス C のメンバ関数 get_n1 へのポインタです。

cout << func(f1, &C::get_n2) << '\n';
cout << func(f2, &C::get_n1) << '\n';
cout << func(f2, &C::get_n2) << '\n';
}

int func(int(*f)(int (C::*)() const), int (C::*get)() const) {
// @で呼び出された場合は・・・
// 仮引数の f が、ポインタ f1 を受け取ります。
// 仮引数の get が、 get_n1 へのポインタを受け取ります。


return (*f)(get);
// @で呼び出された場合は・・・
// *f は *f1 なので、関数 f1 を呼び出します。
// 引数 get は、仮引数が受け取った get です。
// 関数 f1 からの返却値を、main関数に return します。

}

int f1(int (C::*get)() const) {
// @で呼び出された関数 func から呼び出された場合は・・・
// クラス C のメンバ関数 get_n1 へのポインタを、仮引数 get が受け取ります。


return (c.*get)();
// クラスオブジェクト c のメンバ関数 get_n1 を呼び出します。
// 間接ドット演算子「 .* 」を利用しています。
// メンバ関数 get_n1 からの返却値を関数 f1 に return します。

}

int f2(int (C::*get)() const) {
return (c.*get)() * 100;
}
// ------------------------------------

f03_0037.png


新版 明解C 入門編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:13時点)

新版 明解C 中級編 (明解シリーズ)

新品価格
¥2,916から
(2017/11/10 13:14時点)






 たまに、クリック お願いします m(_ _)m

 AA にほんブログ村 IT技術ブログ C/C++へ

こうすけ:メール kousuke_cpp@outlook.jp

【1】★★C++ 記事目次★★ ← 利用可能です。
・新版明解C++入門編 / 新版明解C++中級編
・その他 C++ 関連記事

【2】★★こうすけ@C#★★
・C# の初歩的な記事


検索
<< 2017年12月 >>
          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            
プロフィール
こうすけさんの画像
こうすけ

 たまに、クリック お願いします m(_ _)m

 AA にほんブログ村 IT技術ブログ C/C++へ

こうすけ:メール kousuke_cpp@outlook.jp

【1】★★C++ 記事目次★★ ← 利用可能です。
・新版明解C++入門編 / 新版明解C++中級編
・その他 C++ 関連記事

【2】★★こうすけ@C#★★
・C# の初歩的な記事


×

この広告は30日以上新しい記事の更新がないブログに表示されております。