C++ のお勉強 クラス編

C++ のお勉強二日目。前回の「C++ のお勉強 関数編」の続きになる。
だんだん C++ が解ってきた…気がする。
クラスとかオブジェクトとかみんなカッコイイこと言ってるから,クラスは難しいもんだと思っていたけど,ただの構造体じゃないか。C だってうまくすれば構造体の中に関数もてるよ。まぁ隠蔽は出来ないんだけどさ。

参照変数

  • C のポインタのようなもの
    • 書式が普通の変数と見分けが付かない
    • 参照変数宣言時に代入する必要がある
    • クラスをパラメーターとして渡すときに便利
#include <stdio.h>

int main() {
  int x = 10;
  int &y = x; //これが参照変数

  y++;
  printf("x %d y %d \n", x, y); // => x 11 y 11
  return 0;
}

クラス

  • C の構造体を拡張したものにすぎない
    • メンバーとして変数の他に関数も持つことができる
    • さらに,それぞれのメンバーにアクセス制限をかけることが出来る
      • 次のようなアクセス修飾子を記述した部分以降のすべてのメンバーに適用される。また,省略時は private が適用される
種類 クラス内からのアクセス 継承クラスからのアクセス 外部関数からのアクセス
private × ×
protected ×
public
    • あるクラス型として宣言された変数はオブジェクトと呼ばれる
      • 次のコードで a はClass1型のオブジェクトである
#include <stdio.h>

class Class1 {
 public:
  int id;
  void show() {
    printf("%d, %s\n", id, name);
  }
}

int main() {
  Class1 a;
  a.id = 10;
  a.show();  // => 10
  return 0;
}
    • メンバ関数はプロトタイプ宣言可能で,上下のコードは等価
#include <stdio.h>

class Class1 {
 public:
  int id;
  void show();
}
void Class1::show() {
  printf("%d, %s\n", id, name);
}
int main() {
  Class1 a;
  a.id = 10;
  a.show();  // => 10
  return 0;
}

演算子オーバーロード

つまり,

c = a + b;

この計算は,

c = a.(operator+)(b);

と解釈される。

  • 例えば次のようにベクトルの足し算が可能
#include <stdio.h>

class Vector {
  int x, y;
public:
  void set(int tmp_x, int tmp_y) {
    x = tmp_x;
    y = tmp_y;
  };
  void show() {
    printf("x %d y %d \n", x, y);
  }
  Vector operator+ (Vector &v) {
    Vector c;
    c.x = x + v.x;
    c.y = y + v.y;
    return c;
  }
};

int main() {
  Vector a, b, c;

  a.set(1, 2);
  b.set(2, 3);
  a.show();   // => x 1 y 2
  b.show();   // => x 2 y 3

  c = a + b;
  c.show();   // => x 3 y 5

  return 0;
}

メモリの動的確保

  • new/delete
    • C のmalloc()/free()のようなもの
      • new 演算子は,データ型を与えると,それに十分なメモリを確保しそのポインタを返す。確保できないときは 0 を返す
int *pi = new int;
      • new 演算子により確保されたメモリは,次のように delete 演算子で明示的に開放しない限りプログラム終了時まで存在する
delete pi;

代入と初期化

  • コンストラクタ/デストラク
    • クラス名と同じメンバ名で関数を作成すると,その関数はコンストラクタと呼ばれ,オブジェクト作成時に実行される
      • つまり,初期化が出来る
    • クラス名の先頭に「~」を加えたメンバ名で関数を作成すると,その関数はデストラクタと呼ばれ,delete 演算子によりオブジェクトを破棄するときに実行される
    • コンストラクタは関数のオーバーロードと同様にして,複数作成可能
#include <stdio.h>

class Color {
public:
  int r, g, b;
  
  Color() {
    r = g = b = 0;
  }
  ~Color() {
    printf("object is deleted\n");
  }
};
int main() {
  Color *pc = new Color;
  print("r %d g %d b %d \n", pc->r, pc->g, pc->b); // => r 0 g 0 b 0
  delete pc; // => object is deleted
}

継承

  • クラスは別クラスに引き継ぐことができる
    • このとき,基になったクラスを基底クラス。派生したクラスを派生クラスと呼ぶ
    • また,基底クラスが複数の場合を多重継承と呼ぶ
    • 派生クラスの宣言方法:
class クラス名 : [アクセス権] 基底クラス名 [,[アクセス権] 基底クラス名…] { 
  変更部分 
};
    • アクセス権の指定で,基底クラスから派生クラスへのメンバの取り込み方を制御できる
      • private なら基底クラスのすべてがプライベート部に入れられる
      • public なら基底クラスのパブリックメンバーはそのまま派生クラスのパブリック部に入れられる
      • 何も指定しなければ,基底クラスが struct ならば public,class ならば private と等価
    • 派生クラスの初期化は,まず基底クラスのコンストラクタが呼ばれ,続いて派生クラスのコンストラクタが呼ばれる
      • したがって,派生クラスのコンストラクタには,基底クラスの初期設定に必要なパラメータを記述する
    • メンバの先頭に virtual を付ければ,派生クラスでそのメンバを再定義できる。この関数は仮想関数と呼ばれる
#include <stdio.h>

class Pencil {
protected:
  int core;      // 鉛筆の芯の長さ
public:
  Pencil(int n) {
    core = n;
  }
  void write() {
    if (--core < 0)  // 書くたびに芯が 1mm ずつ小さくなる
      core = 0;
  }
  virtual void info() { // これが仮想関数
    printf("core %d \n", core);
  }
};

class ErasePencil : public Pencil {
protected:
  int rubber;    // 消しゴムの大きさ
public:
  ErasePencil(int a, int b) : Pencil(a){ // 基底クラスのコンストラクタにパラメーターを渡す
    rubber = b;
  }
  void remove() {
    if (--rubber < 0) // 消すたびにゴムが 1 立方mm ずつ小さくなる
      rubber = 0;
  }
  virtual void info() { // この仮想関数に再定義される
    printf("core %d rubber %d \n", core, rubber);
  }
};

int main() {
  Pencil pen1(15);
  pen1.write();
  pen1.info();  // => core 14

  ErasePencil pen2(20, 10);
  pen2.write();
  pen2.remove();
  pen2.info();  // => core 19 rubber 9

  return 0;
}