第6回

クラスとオブジェクト

C++の中心的な考え方であるクラスを理解する。

クラスを使わない場合

int motorSpeed;

void setMotorSpeed(int speed) {
    motorSpeed = speed;
}

問題:

  • 関連するデータと処理が離れている
  • グローバル変数が増える
  • モータが複数あると管理しづらい

クラス化

class Motor {
public:
    void setSpeed(int speed);

private:
    int speed;
};

データと処理をまとめる。これがクラスの一番の役割である。

オブジェクト

Motor motor1;
Motor motor2;

同じクラスから複数のオブジェクトを作れる。motor1とmotor2はそれぞれ独立したspeedを持つ。

   class Motor      ← 設計図
        │
   ┌────┴────┐
   ↓         ↓
 motor1    motor2   ← 実体(オブジェクト)

メンバ変数

private:
    int speed;

メンバ関数

public:
    void setSpeed(int speed);

アクセス制御

クラスのメンバにはアクセス制御が適用される。詳細は第8回カプセル化を参照。

  • public: 公開。外部からアクセス可能
  • private: 非公開。クラス内部でのみアクセス可能
  • protected: 限定公開。派生クラスからはアクセス可能

this

void Motor::setSpeed(int speed) {
    this->speed = speed;
}

引数speedとメンバ変数speedを区別する。

コンストラクタ

class Motor {
public:
    Motor(int pin);

private:
    int pin;
};
Motor::Motor(int pin) {
    this->pin = pin;
}

使用:

Motor motor(24);

コンストラクタは、オブジェクトが作られるときに必ず1回呼ばれる。「初期化を忘れさせない」ための仕組みである。

メンバ初期化リスト

Motor::Motor(int pin)
    : pin(pin) {
}

こちらを推奨する。

動かして確かめる

演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/06-led-class/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。

code/examples/06-led-class/src/main.cpp
// 第6回:LEDクラス(1ファイル版)
// まずは main.cpp の中だけでクラスを書く。分割は第7回で行う。

#include <Arduino.h>

class LED {
public:
    /**
     * @brief コンストラクタ
     * @param pin LEDを繋いだピン番号
     */
    LED(int pin)
        : pin(pin), state(false) {
    }

    /**
     * @brief ピンを初期化して消灯状態にする
     */
    void begin() {
        pinMode(pin, OUTPUT);
        off();
    }

    /**
     * @brief 点灯する
     */
    void on() {
        state = true;
        digitalWrite(pin, HIGH);
    }

    /**
     * @brief 消灯する
     */
    void off() {
        state = false;
        digitalWrite(pin, LOW);
    }

    /**
     * @brief 点灯と消灯を切り替える
     */
    void toggle() {
        if (state) {
            off();
        } else {
            on();
        }
    }

private:
    int pin;
    bool state;
};

// 同じクラスから複数のオブジェクトを作れる。違うのはピン番号だけ
LED led1(LED_BUILTIN);  // ボード上のRGB LED。配線なしで見える
LED led2(23);           // 何もつないでいないピン。外付けLEDを挿せば光る

void setup() {
    led1.begin();
    led2.begin();
}

void loop() {
    led1.toggle();
    delay(500);

    led2.toggle();
    delay(500);
}

RGB LED(led1)が0.5秒ごとに切り替わる。led2はGPIO23に割り当ててあるが、ボード上のLEDは1つしかないので2つ目の点滅は見えない。2つ同時に見たい場合は、GPIO23に外付けのLEDを1つつなぐ(部品と配線は開発環境の「実物をつなぐ場合」にまとめてある)。つながなくても、この回の主題は出力ではなくコードの形なので先に進んでよい。

確かめること。

  • LEDクラスからled1とled2の2つを作っている。違うのはコンストラクタに渡したピンだけ
  • toggle()が内部でstateを見て切り替えている。使う側は今の状態を知らなくてよい
  • pinとstateはprivateなので、外から直接は触れない
  • クラス定義の最後の;を消すと、どんなエラーになるかを見ておく

演習:LEDクラスを作る

必要な機能:

LED led(23);

led.on();
led.off();
  • コンストラクタでピン番号を受け取り、メンバ初期化リストで初期化する
  • on() / off()の中でdigitalWrite()を呼ぶ
  • pinMode()をどこで呼ぶべきかを考える