C++入門 – クラス

C++入門 – 基礎に続いて、引き続きC++について勉強する。
オブジェクト指向として中核を担っているC++のクラスについての勉強を進める。

オブジェクト指向の三大要素

まずはオブジェクト指向の三大要素を知っておく。
– カプセル化
– 継承
– ポリモーフィズム

カプセル化

オブジェクトの安全性を高めるために、オブジェクトのメンバーを保護すること。
オブジェクト指向においてはアクセス制御の概念があるため、外部からアクセスできるものとアクセスできないものを明示的に分けている。
モジュールのブラックボックス化を簡易に行い、高い保守を可能にする。

継承

クラスを引き継いで新しいクラスを定義することできること。
共通部分をまとめて、再利用できるという利点がある。

ポリモーフィズム

オーバライド(親クラスのメソッドを子クラスで上書きすること)やオーバロード(引数や戻り値が異なるが名称が同一のメソッドを複数定義すること)によって、メソッドを状況によって使い分けることができる。

基本の書き方

#include <iostream>
using namespace std;

class Kitty {
  public :
    char *str;
    void print(){
      cout << str;
    }
} obj;

int main(){
  obj.str = "kitty on your lap";
  cout << obj.print();
  return 0;
}

クラスを宣言する時はclassキーワードをつけることで可能
クラス内で定義されているstrはメンバ変数として定義されている。構造体同様に、クラス内部で定義されるデータはメンバと呼ぶ。

構造体では宣言したメンバに直接アクセスすることができるが、三大要素で取り上げたようにアクセス制御があるためクラスはデフォルトでデータにアクセスできません。
そこで、外部からアクセス可能にするため、publicキーワードでデータを公開する必要がある。
アクセス方法は構造体と同じくクラス型変数名.メンバ名 でアクセス可能。
Kittyクラスの最後に定義されているobjはKitty型のクラス型変数として呼ぶことができる。
そのためmain関数内ではobj.strでアクセスを行っている。

メンバ変数同様にクラスで定義されている関数をメンバ関数と呼びます。(これはC++固有で、オブジェクト指向型ではメソッドと呼ぶ)
定義したメンバ関数へのアクセス方法は、メンバ変数と同じようにオブジェクト名とドット演算子を使用します。
以下はメンバ関数を定義したクラス

#include <iostream>
using namespace std;

class Kitty {
  public:
    char const *str;
    void print() {
      cout << str;
    }
} obj ;

int main() {
  obj.str = "Kitty on your lap";
  obj.print();
  return 0;
}

C++ 11 does not allow conversion from string literal to 'char *'エラーが出る場合があるのでconstでstrを定義しています。

main関数内でobj.printを使用し、Kittyのメンバ関数であるprintを呼び出しています。

#include <iostream>
using namespace std;

class Kitty {
  private:
    int point;
  public:
    void setPoint(int i);
    int getPoint(int i);
};

void Kitty::setPoint(int i){
  point = i;
}

int Kitty::getPoint(int i){
  point += i;
  return point;
}

int main(){
  Kitty obj;
  obj.setPoint(0);
  for(int i= 0; i < 10; i++){
    cout << obj.getPoint(i) << "\n";
  }
  return 0;
}

publicに対するアクセス不能領域をprivateキーワードで明示できます
プライベート領域に値の変更にシビアなメンバ変数を置くことで、外部からの誤ったオペレートを避けることができます。これはオブジェクト指向の三大要素でいうカプセル化に該当します。
privateで外部からのアクセスをできないようにして、メンバ関数のsetPointとgetPointで値を取り扱っています。

また、ここで出てきている::スコープ解決演算子(Scope resolution operator)と呼ぶもので、クラスの外部で取り扱うときに使用する演算子です。
見てわかるようにsetPointとgetPointはクラスの外で定義されています。
メンバ関数の定義を行うときに、どのクラスの関数なのか明示的に示す必要があります。そこでこのスコープ解決演算子を使用して指定しています。

ただ文法の統一のため、クラスの内部ですべて定義すべきでしょう。

クラスの構文

class
class [tag [: base-list]]
{
member-list
} [declarators];

[ class ] tag declarators
  • tag クラス型の名前を指定します
  • base-list ベースクラスを指定、省略可
  • member-list クラスのメンバ、またはフレンドを宣言
  • declarators – オブジェクトを1つ以上宣言、グローバルの場合は省略可

※最後のセミコロンを付け忘れると構文エラーになるので付け忘れに注意

おさらい
  • クラス定義にはclassキーワードを使用、最後にdeclaratorsにクラス型変数を指定するこも可能
  • メンバ変数、メンバ関数(メソッド)が存在する
  • publicとprivateでカプセル化させる
  • スコープ解決演算子を使用することでクラス外でもメンバの定義が可能

コンストラクタ

コンストラクタによる初期化、オブジェクト指向プログラミングではしばし出てくるこのコンストラクタ。
クラスが呼び出されたタイミングで自動実行される関数です。
そのためオブジェクトが初期値として持つべき値などはここで設定することができる。
C++において、コンストラクタはクラス名と同じ名前の関数で実行することが可能です。

#include <iostream>
using namespace std;

class Kitty {
  public:
    Kitty();
} obj;

Kitty::Kitty() {
  cout << "Kitty on your lap\n";
}

int main() {
  Kitty obj;
  return 0;
}

コンストラクタには戻り値が存在せず、引数の受け取りは可能です。
iso c++ forbids converting a string constant to ‘char*’エラーが出る場合があるので、サンプルとは別に以下のように書く

#include <iostream>
using namespace std;

class Kitty {
  public:
    char const *str;
    Kitty(const char *);
};

Kitty::Kitty(const char *ch) {
  str = ch;
}

int main() {
  Kitty obj("Kitty on your lap");
  cout << obj.str;
  return 0;
}

デストラクタ

コンストラクタとは対象にオブジェクトの値を破棄する関数です。
オブジェクトの破棄もコンストラクタ同様に自動化することで、バグやメモリリークを避けることができます
コンストラクタの逆の処理を担当するものがデストラクタ関数で、デストラクタ関数はオブジェクトの消滅時に自動的に呼び出されます。
定義方法はコンストラクタの名前の前にチルダ( ~ ) をつけるとデストラクタ関数になります。
デストラクタは戻り値は存在せず、引数も受け取りません。

#include <iostream>
using namespace std;

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

Kitty::~Kitty(){
  cout << "Kitty on your lap\n";
}

void createKitty(){
  Kitty obj;
}

int main(){
  createKitty();
  createKitty();
  return 0;
}

オブジェクトが破棄されるタイミングはスコープが外れたときになります。

スコープ

スコープ解決演算子とグローバルスコープ解決演算子の使い方についてを学びます。
C++においてローカル変数名とグローバル変数名が同じだったとき識別子の衝突がおこります。
ローカル変数とグローバル変数が同じだったとき関数内で変数にアクセスするとローカル変数を優先して実行されます。これを解決するのにメンバ関数では内部でメンバ変数を明示する必要があります。

スコープ解決演算子

スコープ解決演算子を使用するとメンバ関数内でメンバ変数を明示できるようになります。
これでメンバ関数内ローカル変数とメンバ変数を取り扱えるようになります。
以下のようにメンバ関数でスコープ解決演算子を使用してクラスを指定します。

#include <iostream>
using namespace std;

class Kitty {
  public:
    char const *str;
    void print(const char *);
};


void Kitty::print(const char *str){
  cout << str << '\n';
  cout << Kitty::str << '\n';
}

int main(){
    Kitty obj;
    obj.str = "Kitty on your lap";
    obj.print("Di_Gi_Gharat");
}

print()メンバ関数はメンバ変数とローカル変数のstrをスコープ解決演算子で区別しています

グローバルスコープ解決演算子

グローバルスコープ解決演算子もスコープ解決演算子と同様にグローバル変数を関数内で明示することができます。

#include <iostream>
using namespace std;

const char *str = "Kitty on your lap\n";

int main(){
  const char *str = "Hello World\n";
  cout << str;
  cout << ::str; // グローバルスコープ解決演算子
  return 0;
}

関数も同様にグローバルスコープ解決演算子で明示することが可能です。

#include <iostream>
using namespace std;

void function(){
  cout << "Di_Gi_Gharat\n";
}

class Kitty {
  void function();
  public:
    Kitty();
} obj;

Kitty::Kitty(){
  ::function();
  Kitty::function();
}

void Kitty::function(){
  cout << "Kitty on your lap\n";
}

int main(){
  return 0;
}

関連記事