TENKAIKEN Storehouse

Contents ♪

C++ クラス 2024.07.15
クラスの定義の簡単な例
#include <iostream>

class Ctest {
public:
    Ctest(int a);       // コンストラクタ
    ~Ctest();           // デストラクタ
                        // コンストラクタ・デストラクタは戻り値がない
    void show();
private:                // プライベートメンバはメンバ関数からのみアクセス可
    int add(int a);
    int x;
};

Ctest::Ctest(int a)
{
    x = a;
}

Ctest::~Ctest() { }

void Ctest::show()
{
    std::cout << add(1) << std::endl;
                        // プライベート関数の呼び出し
}

int Ctest::add(int a)
{
    x += a;
    return x;
}

int main()
{
    Ctest ct1(10), ct2(100);
    ct1.show();     // 11
    ct2.show();     // 101

    ct1.show();     // 12
    ct1.show();     // 13
    ct1.show();     // 14
    return 0;
}
C++ クラス 2024.07.15
メンバ関数をインスタンスのポインタ・参照から呼び出す
#include <iostream>

class Ctest {
public:
    Ctest(int a);       // コンストラクタ
    ~Ctest();           // デストラクタ
    void show();
private:
    int x;
};

Ctest::Ctest(int a)
{
    x = a;
}

Ctest::~Ctest() { }

void Ctest::show()
{
    std::cout << "x=" << x << std::endl;
}

int main()
{
    Ctest ct(10);
    Ctest* pct = &ct;       // ポインタへインスタンス代入
    Ctest& rct = ct;        // インスタンスの参照
    pct->show();        // x = 10
    rct.show();         // x = 10
    return 0;
}
C++ クラス 2024.07.15
using namespace 名前空間の指定
初期のC++はiostreamではなくC言語と同様にiostream.hとしてグローバルスコープに定義していた。C++98から名前空間が導入され標準ライブラリががstd名前空間の定義となったことで、それらはデフォルトではstd::の名前空間の指定が必要になった。using namespaceでstd名前空間を宣言することでstd::が省略できる。
#include <iostream>
using namespace std;

int main()
{
    int a;
    cin >> a;
    cout << "A=" << a << endl;
    return 0;
}
C++ クラス 2024.07.15
コンストラクタの役割(設定値チェック)
コンストラクタで入力値のチェックなどを行うことで、不正なデータの状態になることを防ぐことができる。
#include <iostream>
using namespace std;

class Time {
public:
    Time(int h, int m);     // コンストラクタ
    ~Time();
    void show();
private:
    int hour, min;
};

Time::Time(int h, int m)
{
    if (h < 0)          // コンストラクタで初期値のチェック
        hour = 0;
    if (h > 23)
        hour = 23;
    if (m < 0)
        min = 0;
    if (m > 59)
        min = 59;
}

Time::~Time() { }

void Time::show()
{
    cout << hour << ":" << min << endl;
}

int main()
{
    Time t(24, 63);
    t.show();           // 23:59
    return 0;
}
C++ クラス 2024.07.15
複数のコンストラクタを定義する
引数の異なる複数のコンストラクタを実装することができる。インスタンス生成時の引数に適合するコンストラクタが選択される。
#include <iostream>
using namespace std;

class Time {
public:
    // 異なる引数で複数のコンストラクタを定義できる
    Time(int h, int m);
    Time(int h, int m, int s);
    void show();
private:
    int hour, min, sec;
};

Time::Time(int h, int m)
{
    hour = h;
    min = m;
    sec = 0;
}

Time::Time(int h, int m, int s)
{
    hour = h;
    min = m;
    sec = s;
}

void Time::show()
{
    cout << hour << ":" << min << "\'" << sec << endl;
}

int main()
{
    Time t1(13, 23);
    Time t2(5, 45, 18);
    t1.show();          // 13:23'0
    t2.show();          // 5:45'18
    return 0;
}
コンストラクタを定義しない場合は、暗黙に引数を持たない何もしないデフォルトコンストラクタが自動的に定義される。
#include <iostream>
using namespace std;

class Ctest {
public:
    int a;
    // 暗黙にデフォルトコンストラクタ定義
};

int main()
{
    Ctest c;
    c.a = 100;
    printf("%d\n", c.a);    // 100
    return 0;
}
引数のあるコンストラクタを何か定義した場合は、デフォルトコンストラクタは暗黙には定義されないので、引数のない処理のないコンストラクタが必要ならば、それを明示的に定義しなければならない。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    Ctest() { };
    Ctest(int x) { a = x; }
    void set(int x) { a = x; }
    int get() { return a; }
};

int main()
{
    Ctest c1;
    c1.set(10);
    cout << "a=" << c1.get() << endl;   // a=10
    Ctest c2(100);
    cout << "a=" << c2.get() << endl;   // a=100
    return 0;
}
C++ クラス 2024.07.15
コンストラクタとデストラクタが呼び出されるタイミング
デストラクタは、インスタンスが消滅するときに自動的に呼び出される。
Ctestクラスのインスタンス「c」の宣言によりコンストラクタが自動的に呼び出され、ブロックスコープから出るときデストラクタが呼び出されている。
#include <iostream>
using namespace std;

class Ctest {
public:
    Ctest();
    ~Ctest();
};

Ctest::Ctest()
{
    cout << "Construct\n";
}

Ctest::~Ctest()
{
    cout << "Destruct\n";
}

int main()
{
    cout << "Hello\n";
    {
        Ctest c;
    }
    cout << "World\n";
    return 0;
}
Hello
Construct
Destruct
World
C++ クラス 2024.07.15
セッター・ゲッター関数(カプセル化)
データメンバを直接アクセスできないようにprivateメンバとして、それらのゲッター・セッター関数を用意することでオブジェクトをカプセル化する。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    void set(int x);
    int get();
};

void Ctest::set(int x)
{
    a = x;
}

int Ctest::get()
{
    return a;
}

int main()
{
    Ctest c;
    c.set(10);
    cout << "a=" << c.get() << endl;    // a=10
    return 0;
}
C++ クラス 2024.07.15
インラインメンバ関数
inline宣言によりメンバ関数get()の呼び出しがインライン展開される。
set()のように、クラス定義の中で直接実装した場合、可能ならばインライン関数として展開される。実際にインライン展開されるかどうかは、コンパイラの設定や状況判断による。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    void set(int x) { a = x; }
    int get();
};

inline int Ctest::get()
{
    return a;
}

int main()
{
    Ctest c;
    c.set(10);
    cout << "a=" << c.get() << endl;    // a=10
    return 0;
}
C++ クラス 2024.07.15
コンストラクタの中でメンバ関数を呼び出す
コンストラクタの中で、メンバ関数を呼び出すことができる。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    Ctest(int x) { set(x); }
    void set(int x) { a = x; }
    int get() { return a; }
};

int main()
{
    Ctest c(10);
    cout << "a=" << c.get() << endl;    // a=10
    return 0;
}
C++ クラス 2024.07.15
メンバの参照を返すメンバ関数
getref()の戻り値がメンバaの参照になっている。aはprivateメンバであるにもかかわらず、参照を返すgetref()を通してアクセスできている。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    int& getref();
    int get() { return a; }
};

int& Ctest::getref()
{
    return a;
}

int main()
{
    Ctest c;
    c.getref() = 10;
    c.getref()++;
    cout << "a=" << c.get() << endl;    // a=11
    return 0;
}
C++ クラス 2024.07.15
メンバ関数のconst宣言
constで宣言したクラスのインスタンスは、メンバ変数をコンストラクタ以降変更できない。変更操作が行われる実装を検出した時点でコンパイルエラーとなる。更に、全てのメンバ関数の呼び出しも禁止となりエラーになる。ただし、変更操作を行わない(表示やゲッターなど)関数のプロトタイプに、constを付けて定義することで、その制限から除外され呼び出しができるようになる。
#include <iostream>
using namespace std;

class Ctest {
    int a;
public:
    Ctest() { }
    Ctest(int x) { a = x; }
    void set(int x) { a = x; }
    int get() const;
};

int Ctest::get() const      // 実装にもconstが必要
{
    return a;
}

int main()
{
    const Ctest c1(10);
    cout << "a=" << c1.get() << endl;   // a=10
                // get()にconstを付けないと、ここでコンパイルエラーになる
    Ctest c2;
    c2.set(100);
    cout << "a=" << c2.get() << endl;   // a=100
    return 0;
}
constで宣言される可能性があるクラスを定義するときは、変更操作のないメンバ関数にconstを付けておくとよい。
C++ クラス 2024.07.15
メンバイニシャライザ
CtestはDateクラスのメンバdを持つ。Ctestのコンストラクタ実行前に、Dateクラスのdのコンストラクタをメンバイニシャライザにより呼び出す必要がある。メンバイニシャライザはCtestのコンストラクタの中より先に実行される。メンバイニシャライザを省略した場合はDateのデフォルトコンストラクタが暗黙に呼び出される。
#include <iostream>
using namespace std;

class Date {
public:
    int year, month, day;
    Date(int y, int m, int d);
    void show();
};

Date::Date(int y, int m, int d)
{
    year = y;
    month = m;
    day = d;
}

void Date::show()
{
    cout << year << "/" << month << "/" << day << endl;
}

class Ctest {
public:
    Date d;
    Ctest();
};

Ctest::Ctest(): d(1970, 12, 31)     // メンバイニシャライザ
{
}

int main()
{
    Ctest cdate; 
    cdate.d.show();     // 1970/12/31

    return 0;
}
この例ではDateクラスのメンバがpublicなので、dのメンバイニシャライザはデフォルトコンストラクタの暗黙の呼び出しに任せて、Ctestのコンストラクタでdの初期値を代入する方法もある。
class Date {
public:
    int year, month, day;
    Date() {};  // 追加
    Date(int y, int m, int d);
    void show();
};

//中略

Ctest::Ctest()
{
    d.year = 1970;
    d.month = 11;
    d.day = 20;
}
CtestのDateクラスのメンバdをconstにすると、Ctestのコンストラクタ内で初期化しようとしても代入ができなくなるのでエラーになる。その場合はメンバイニシャライザで初期化しなければならない。
class Ctest {
public:
    const Date d;
    Ctest();
};

//中略

Ctest::Ctest(): d(1977, 10, 23)
{
}
C++ クラス 2024.07.15
int型などのメンバのメンバイニシャライザ
メンバイニシャライザはクラスのオブジェクトメンバだけでなくintやcharのようなプリミティブ型にも適用できる。例えば、int型メンバ変数iをメンバイニシャライザで次のように初期化できる。
#include <iostream>
using namespace std;

class Ctest {
    const int i;
public:
    Ctest();
    void show() {cout << i << endl;}
};

Ctest::Ctest(): i(10)
{
}

int main()
{
    Ctest c; 
    c.show();       // 10
    return 0;
}
C++ クラス 2024.07.15
newによるオブジェクト生成
new演算子はフリーストアからmalloc()のようにメモリを割り当てインスタンスを生成し、コンストラクタを呼び出してインスタンスの初期化を行う。newは生成したインスタンスのメモリ領域を示すクラス型のポインタを返す。
deleteはfree()に相当するもので、デストラクタを呼び出しメモリ領域を解放する。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest(int a);
    ~Ctest();
    void show();
private:
    int x;
};

Ctest::Ctest(int a)
{
    x = a;
}

Ctest::~Ctest()
{
    cout << "destruct" << endl; 
}

void Ctest::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    Ctest* ct = new Ctest(10);
    ct->show();                 // x=10
    delete ct;                  // destruct (デストラクタ呼び出し)
    return 0;
}
引数のないコンストラクタはクラス名だけで()は省略できる。deleteにより呼び出されるデストラクタが実装されていない場合は、デフォルトデストラクタが呼び出される。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest() { x = 1; }
    Ctest(int a);
    void show();
private:
    int x;
};

Ctest::Ctest(int a)
{
    x = a;
}

void Ctest::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    Ctest* ct = new Ctest;      // 引数がないコンストラクタ 
    ct->show();                 // x=1
    delete ct;                  // デフォルトデストラクタ
    return 0;
}
C++ クラス 2024.07.15
int型等をnewで生成
new演算子はクラス型に限らずint型などのプリミティブ型でも適用できる。deleteもint型などの内部的なクラスのデストラクタが実行される。
#include <iostream> 
using namespace std;

int main()
{
    int* i = new int(10);
    cout << *i << endl;     // 10
    delete i;
    return 0;
}
C++ クラス 2024.07.15
newでオブジェクトの配列を生成
newでインスタンスを配列で生成する場合は、クラス名に続けて[]で要素数を指定する。
new クラス名[要素数]
このとき、「new クラス名(引数)[要素数]」のようにはできない。
配列のデストラクタには[]を付ける。
delete [] インスタンス
配列の削除でdeleteに続く[]を忘れた場合は、先頭要素のみの削除と解釈される。これはメモリ解放忘れになる。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest();
    void show();
private:
    int x;
};

Ctest::Ctest()
{
    x = 10;
}

void Ctest::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    Ctest* ca = new Ctest[3];
    ca[0].show();
    ca[1].show();
    ca[2].show();
    (ca+0)->show();
    (ca+1)->show();
    (ca+2)->show();
    delete[] ca;
    return 0;
}
x=10
x=10
x=10
C++ クラス 2024.07.15
newで数値型等の配列を生成
new演算子はint型などのプリミティブ型の配列を生成できる。delete []により配列メモリを解放できる。
#include <iostream> 
#include <cstring>

using namespace std;

int main()
{
    char* s = new char[10];
    strcpy(s, "Hello\n");
    cout << s;          // Hello
    delete [] s;
    return 0;
}
C++ クラス 2024.07.15
クラスのメンバをnewで生成する
クラスのメンバをnewでメモリ確保しているので、デストラクタでそのメモリを解放する必要がある。
#include <iostream> 
#include <cstring>
using namespace std;

class Ctest {
public:
    Ctest(const char *s);
    ~Ctest();
    void show();
private:
    char* buf;
};

Ctest::Ctest(const char *s)
{
    buf = new char[10];
    strcpy(buf, s);
}

Ctest::~Ctest()
{
    delete [] buf;
}

void Ctest::show()
{
    cout << buf;
}

int main()
{
    Ctest cs("Hello");
    cs.show();      // Hello
    return 0;
}
C++ クラス 2024.07.15
インスタンス間の代入
クラスのインスタンス同士の代入は、構造体変数同士の代入と同様に、メンバ間でのコピーになる。
#include <iostream> 
using namespace std;

class Ctest {
public:
    void show();
    int x;
    int y;
};

void Ctest::show()
{
    cout << "x=" << x << " y=" << y << endl;
}

int main()
{
    Ctest c1;
    c1.x = 10;
    c1.y = 20;
    Ctest c2 = c1;
    c2.show();      // x=10 y=20
    return 0;
}
C++ クラス 2024.07.15
現インスタンスを参照するthis
インスタンスが自身のメンバを、thisという特殊なポインタで参照できる。thisはインスタンスを指すポインタなので、静的メンバはアクセスできない。例はthisを使う必要はなく冗長だが、thisで参照するとこのようになる。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest(int a);
    int get();
    void show();
private:
    int x;
};

Ctest::Ctest(int a)
{
    this->x = a;
}

int Ctest::get()
{
    return this->x;
}

void Ctest::show()
{
    cout << "x=" << this->get() << endl;
}

int main()
{
    Ctest c(10);
    c.show();                   // x=10
    return 0;
}
C++ クラス 2024.07.15
thisでインスタンスを判断
add()関数は、同じCtestクラスのインスタンスのxを自身のxに加算する。このとき、自分のインスタンスを示すthisが引数で与えられたCtestオブジェクトと同じならば、自分から自分への加算になるので「same Ctest」という警告を出力する。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest(int a);
    void add(Ctest* c);
    void show();
    int x;
};

Ctest::Ctest(int a)
{
    x = a;
}

void Ctest::add(Ctest* c)
{
    if (c == this)
        cout << "same Ctest" << endl;
    else
        x += c->x;
}

void Ctest::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    Ctest c1(10);
    Ctest c2(20);
    c2.add(&c1);
    c2.show();          // x=30
    c1.add(&c1);        // same Ctest
    return 0;
}
C++ クラス 2024.07.15
代入演算子オーバーロードの自己代入チェック
代入演算子をオーバーロードしたときに、自分自身への再代入にならないかをthisでチェックできる。
#include <iostream> 
using namespace std;

class CInt {
public:
    CInt& operator=(int i);
    void operator=(CInt& ci);
    void show();
private:
    int x;
};

CInt& CInt::operator=(int i)
{
    x = i;
    return *this;
}

void CInt::operator=(CInt& ci)
{
    if (&ci != this) 
        x = ci.x;
    else
        cout << "set myself" << endl;
}

void CInt::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    CInt i, n;
    i = 10;
    n = i;
    n.show();                   // x=10
    n = n;                      // set myself
    return 0;
}
C++ クラス 2024.07.15
連続する代入のためにthisの参照を返す
「a=b=c」のような連続する代入演算で、代入式の結果を返して次の演算で参照されるために、「=」のオーバーロードがthisの参照を返す。
#include <iostream> 
using namespace std;

class CInt {
public:
    CInt& operator=(int i);
    CInt& operator=(CInt& ci);
    void show();
private:
    int x;
};

CInt& CInt::operator=(int i)
{
    x = i;
    return *this;
}

CInt& CInt::operator=(CInt& ci)
{
    if (&ci != this) 
        x = ci.x;
    return *this;
}

void CInt::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    CInt i, n, m;
    i = 20;
    m = n = i;
    m.show();                   // x=20
    return 0;
}
C++ クラス 2024.07.15
コピーコンストラクタ
最初のn=30 は代入ではなく初期化なので、int型を引数で受けるコンストラクタの呼び出しになる。同様にm=nも、Cintクラスの初期化である。この場合、自身と同じクラスのオブジェクトを引数に受ける「コピーコンストラクタ」の呼び出しになる。
もし、
CInt m = n;
ではなく、
CInt m;
m = n;
であれば、初期化ではなく代入なので、代入演算子のオーバーロードを実装しなければならない。初期化の場合は、今から生成するインスタンスを自己初期化するということはないので、thisによる自己代入のチェックは不要である。
#include <iostream> 
using namespace std;

class CInt {
public:
    CInt(int a);
    CInt(const CInt& i);
    void show();
private:
    int x;
};

CInt::CInt(int a)
{
    cout << "constructor by int" << endl;
    x = a;
}

CInt::CInt(const CInt& i)
{
    cout << "copy constructor" << endl;
    x = i.x;
}

void CInt::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    CInt n = 30;                // constructor by int
    n.show();                   // x=30
    CInt m = n;                 // copy constructor
    m.show();                   // x=30
    return 0;
}
C++ クラス 2024.07.15
引数を渡すときのコピーコンストラクタ
func()はCIntの引数を受けるときに、引数iへの初期化になるので、コピーコンストラクタが呼び出される。
#include <iostream> 
using namespace std;

class CInt {
public:
    CInt(int a);
    CInt(const CInt& i);
    void show();
private:
    int x;
};

CInt::CInt(int a)
{
    cout << "constructor by int" << endl;
    x = a;
}

CInt::CInt(const CInt& i)
{
    cout << "copy constructor" << endl;
    x = i.x;
}

void CInt::show()
{
    cout << "x=" << x << endl;
}

void func(CInt i)
{
    i.show();
}

int main()
{
    CInt n = 30;            // constructor by int
    func(n);                // copy constructor
    return 0;
}
C++ クラス 2024.07.15
同一クラスインスタンスのprivateメンバアクセス
Ctest1::set()は同じCtest1クラスのインスタンスを引数aで受けて、a.xのようにprivateメンバのxを直接参照できている。
c1aのインスタンスのメンバ関数setに、別のインスタンスのc1bを引数で与えたとき、別の実体なのだからprivateメンバにアクセスできないよう見えるが、そうではない。
Ctest1::set()はCtest1クラスの関数であり、Ctest1クラスの実装の中では、Ctest1のインスタンスであればprivateメンバのアクセスは問題ない。もちろん、Ctest2::set()は別のクラスのprivateメンバーアクセスになるのでエラーになる。
#include <iostream> 
using namespace std;

class Ctest1 {
public:
    Ctest1();
    void set(Ctest1& a);
private:
    int x;
};

Ctest1::Ctest1()
{
    x = 10;
}

void Ctest1::set(Ctest1& a)
{
    x = a.x;
}

class Ctest2 {
public:
    void set(Ctest1& a);
    int x;
};

void Ctest2::set(Ctest1& a)
{
    //x = a.x;      // これはprivateメンバーアクセスのエラーになる
}

int main()
{
    Ctest1  c1a, c1b;
    c1a.set(c1b);
    Ctest2  c2;
    c2.set(c1a);
    return 0;
}
C++ クラス 2024.07.15
静的メンバの初期化
静的メンバは、他のメンバと同様にクラスのメンバ関数からアクセスできるが、常に唯一の変数を参照することになる。クラスの静的メンバは、永続的に存在するので、グローバルスコープでCtest::sumを定義する必要がある。
静的メンバがprivateの場合は、コンストラクタやメンバ関数から代入はできるが、初期化できる機会はグローバルスコープの定義のときしかない。publicならば、グローバルスコープでの定義時には初期化しなくても、main()などでインスタンスを生成する前にCtest::sum=0のように初期値を代入できる。
#include <iostream> 
using namespace std;

class Ctest {
public:
    void set(int a);
    void show();
private:
    static int sum;
    int x;
};

int Ctest::sum = 0;

void Ctest::set(int a)
{
    x = a;
    sum += a;
}

void Ctest::show()
{
    cout << "x=" << x << " sum=" << sum << endl;
}

int main()
{
    Ctest n[3];
    n[0].set(1);
    n[0].show();        // x=1 sum=1
    n[1].set(2);
    n[1].show();        // x=2 sum=3
    n[2].set(3);
    n[2].show();        // x=3 sum=6
    return 0;
}
C++ クラス 2024.07.15
静的メンバ関数
静的メンバ関数は、インスタンスのメンバ関数から呼び出せる。privateの静的メンバ関数は、メンバ関数以外からは呼び出すことはできない。静的メンバ関数は、静的メンバのみアクセスでき、インスタンスのデータにはアクセスできない。
#include <iostream> 
using namespace std;

class Ctest {
public:
    void set(int a);
    void show();
private:
    static void showsum();
    static int sum;
    int x;
};

int Ctest::sum = 0;

void Ctest::set(int a)
{
    x = a;
    sum += a;
}

void Ctest::showsum()
{
    cout << "sum=" << sum << endl;
}

void Ctest::show()
{
    showsum();
}

int main()
{
    Ctest n[3];
    n[0].set(1);
    n[2].show();        // sum=1
    n[1].set(2);
    n[2].show();        // sum=3
    n[2].set(3);
    n[2].show();        // sum=6
    return 0;
}
一方で、publicの静的メンバ関数は、スコープ解決によりグローバルスコープで呼び出すことができる。
#include <iostream> 
using namespace std;

class Ctest {
public:
    void set(int a);
    static void showsum();
private:
    static int sum;
    int x;
};

int Ctest::sum = 0;

void Ctest::set(int a)
{
    x = a;
    sum += a;
}

void Ctest::showsum()
{
    cout << "sum=" << sum << endl;
}

int main()
{
    Ctest n[3];
    n[0].set(1);
    n[1].set(2);
    n[2].set(3);
    Ctest::showsum();       // sum=6
    return 0;
}
C++ クラス 2024.07.15
フレンドクラス
friendで宣言するクラスに対し、自身のクラスのprivateメンバへのアクセスを特別に許可する。
フレンドクラスは、friend宣言した側のクラスのprivateへの参照を許可するものであり、逆のfriend宣言される側のprivateにはアクセスはできない。friend宣言するクラスが、privateメンバの参照を許可するクラスを選ぶのであり、friend宣言により他のクラスのprivateにアクセスできるようになるわけではない。
friend宣言にはprivateやpublicのアクセス制限は無関係なので、クラス定義のどこに書いてもよい。
#include <iostream> 
using namespace std;

class Cmul {
friend class Csq; 
private:
    void mul(int a, int b);
    int x;
};

void Cmul::mul(int a, int b)
{
    x = a * b;
}

class Csq {
public:
    Csq();
    void sq(int a);
private:
    Cmul cm;
};

Csq::Csq() : cm()
{
}

void Csq::sq(int a)
{
    cm.mul(a, a);
    cout << "square " << a << "=" << cm.x << endl;
}

int main()
{
    Csq s;
    s.sq(4);        // square 4=16
    return 0;
}
Cmulクラスは、publicメンバが存在しないので、フレンドクラスとしてCsqクラスで使われることでしか利用できない。
C++ クラス 2024.07.15
フレンド関数
関数に対してfriend宣言できる。宣言された関数の中で、friend宣言したクラスのprivateメンバにアクセスできる。
#include <iostream> 
using namespace std;

class Cmul {
friend void square(int a);
private:
    void mul(int a, int b);
    int x;
};

void Cmul::mul(int a, int b)
{
    x = a * b;
}

void square(int a)
{
    Cmul cm;
    cm.mul(a, a);
    cout << "square " << a << "=" << cm.x << endl;
}

int main()
{
    square(3);      // square 3=9
    return 0;
}
C++ クラス 2024.07.15
オブジェクトの配列の初期化
クラスのインスタンスの配列は、各要素がコンストラクタを呼び出し初期化する。配列要素を初期化しないで宣言するときや、要素数に満たない初期化を省略した要素は、デフォルトコンストラクタが暗黙に呼び出される。
#include <iostream> 
using namespace std;

class Ctest {
public:
    Ctest();
    Ctest(int a);
    void show();
private:
    int x;
};

Ctest::Ctest()
{
    x = 1;
}

Ctest::Ctest(int a)
{
    x = a;
}

void Ctest::show()
{
    cout << "x=" << x << endl;
}

int main()
{
    Ctest n[3] = {Ctest(10), 20};
    n[0].show();        // x=10
    n[1].show();        // x=20
    n[2].show();        // x=1
    return 0;
}
例の20の初期化のように、引数が一つのみのコンストラクタならば、その引数だけの指定にできる。
C++ クラス 2024.07.15
new/deleteのオーバーロード
newとdeleteはオーバーロードして独自のメモリ管理を実装できる。new演算子のオーバーロードの引数にはsize_tが入る。このサイズは自動的に計算されて与えられる。
#include <iostream>
#include <stdlib.h>
#include <memory.h>

using namespace std;

void *operator new(size_t size)
{
    void *p = malloc(size);
    memset(p, 0xff, size);
    return p;
}

void operator delete(void *p)
{
    free(p);
}

class Cbuf {
public:
    void set(unsigned char d);
    void dump();
private:
    unsigned char buf[16];
};

void Cbuf::set(unsigned char d)
{
    memset(buf, d, sizeof(buf));
}

void Cbuf::dump()
{
    for (int i = 0; i < sizeof(buf); i++)
        cout << std::hex << (int)buf[i] << ":";
    cout << endl;
}

int main()
{
    Cbuf* dp = new Cbuf;
    dp->dump();     // ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
    dp->set(0xaa);
    dp->dump();     // aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:
    return 0;
}
C++ クラス 2024.07.15
newのオーバーロードで追加の引数
new演算子のオーバーロードには、独自の引数を設置できる。size_tのサイズは必須で、それに続く任意の引数を宣言できる。newを使うときは、new(引数)のようにする。
#include <iostream>
#include <stdlib.h>
#include <memory.h>
using namespace std;

void *operator new(size_t size, unsigned char clr)
{
    void *p = malloc(size);
    memset(p, clr, size);
    return p;
}

void operator delete(void *p)
{
    free(p);
}

class Cbuf {
public:
    void set(unsigned char d);
    void dump();
private:
    unsigned char buf[16];
};

void Cbuf::set(unsigned char d)
{
    memset(buf, d, sizeof(buf));
}

void Cbuf::dump()
{
    for (int i = 0; i < sizeof(buf); i++)
        cout << std::hex << (int)buf[i] << ":";
    cout << endl;
}

int main()
{
    Cbuf* dp = new(0xcc) Cbuf;
    dp->dump();     // cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:cc:
    dp->set(0xaa);
    dp->dump();     // aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:
    return 0;
}
C++ クラス 2024.07.15
クラス専用のnew/deleteオーバーロード
newとdeleteのオーバーロードをクラスに定義して、そのクラスのインスタンス生成にのみそれらを適用させることができる。newとdeleteのオーバーロードはそれぞれコンストラクタの前、デストラクタの後に呼び出される。
グローバルのnewを使いたい場合は、
dp = ::new Cbuf;
のようにスコープ解決する。
#include <iostream>
#include <stdlib.h>
#include <memory.h>
using namespace std;

class Cbuf {
public:
    void* operator new(size_t size);
    void operator delete(void *p);
    void set(unsigned char d);
    void dump();
private:
    unsigned char buf[16];
};

void* Cbuf::operator new(size_t size)
{
    void *p = malloc(size);
    memset(p, 0x88, size);
    return p;
}

void Cbuf::operator delete(void *p)
{
    free(p);
}

void Cbuf::set(unsigned char d)
{
    memset(buf, d, sizeof(buf));
}

void Cbuf::dump()
{
    for (int i = 0; i < sizeof(buf); i++)
        cout << std::hex << (int)buf[i] << ":";
    cout << endl;
}

int main()
{
    Cbuf* dp = new Cbuf;
    dp->dump();     // 88:88:88:88:88:88:88:88:88:88:88:88:88:88:88:88:
    dp->set(0xaa);
    dp->dump();     // aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:
    return 0;
}