【電子工作C++編】A-1.ArduinoでC++言語のクラスを作ろう
Arduinoで、C++が使えることがわかりました。
ところが、Arduinoはプロトタイプ宣言がいらない謎の方式だったので、
イマイチと思っていたのですが、
どうも、C++の場合はプロトタイプ宣言が必要である模様。
ということは、C++言語の勉強用デバイスとして使えそうです。
ということで、C++学習用のデバイスにしようかと思います。
それでは、
今回はクラスを作ってみましょう。
1.クラスを理解しよう
まず最初にクラスとは?
一番簡単なイメージとしては、
C言語でいう構造体です。
関数と変数がセットになった構造体と考えるのが、
一番イメージしやすいんじゃないかと思います。
関数が3つと、変数が2つを構造体を作っているイメージです。
使い方ですが、
構造体を使うのと一緒です。
このクラスをローカル変数として定義することで、
クラスを生成できます。
/* スタック上にクラスを生成 */
LedClass lLedClass(IoPort);
※引数がある点はいったん無視してください。
また、構造体の変数を扱うような形で、関数を呼び出せば、
関数の機能を使えます。
ここでポイントとなるのが、
変数も構造体の中に配置されていることです。
もしC言語であれば、
通常関数で処理をする場合、
何かの処理状態を残す際には、
関数とセットで複数のグローバル変数を定義する必要があります。
つまり、↓こんなイメージ。
static int gPort;
static bool gStatus;
void SetOutput(bool aOutput)
もし、制御するポートが3つに増えたら、
これを同じ数だけ定義する必要があります。
また、変数は同名で定義できないので、複数定義しなければならず、
複数のLED制御する際には定義が面倒です。
関数にも引数を増やしたりうまく実装する必要があります。
static int gPort1;
static bool gStatus1;
static int gPort2;
static bool gStatus2;
static int gPort3;
static bool gStatus3;
void SetOutput(bool aOutput,int aPort, bool aStatus);
※といはいえ、実装方法は10人10色でやり方は色々です。
では、これをC++言語で実装する場合は?
C++では、クラス内に関数と変数がパッケージになっています。
LedClass LedClass1(StackIoPort);
LedClass LedClass2(StackIoPort);
LedClass LedClass3(StackIoPort);
これで関数を呼び出すだけ。
似たような処理を作る場合など、C++言語はすごく便利ですね!
※今日は理解できなくとも、こんなスマートになるんだと
思っていただければと思います。
2.クラスと構造体の違い
クラスには、
クラス生成時に呼び出されるコンストラクタと
クラス削除時に呼び出されるデストラクタがあります。
構造体の場合には、
構造体を定義した後、各数値を各々初期値を設定する必要があります。
LedStr gStruct;
gStruct.iPort = 13;
gStruct.iStatus= false;
クラスの場合には、
クラス生成時にコンストラクタが呼び出されますので、
コンストラクタ内で初期化してやれば、
使用するたびに初期化するような処理を作らなくて済みます。
LedClass LedClass1(StackIoPort);
特に便利なのが、設計変更で変数を追加した際です。
構造体の場合、構造体を使用している個所すべてで
変数を初期化する処理を入れる必要が出てきますね。
LedStr gStruct;
gStruct.iPort = 13;
gStruct.iStatus= false;
gStruct.iAppend = 100;
クラスの場合はコンストラクタで処理をしていますので、
従来から使用している側は特に処理を書く必要がなくなります。
LedClass LedClass1(StackIoPort);
また、デストラクタという機能も便利です。
出力のフラグを落とし忘れたとしても、
デストラクタに出力停止の処理を書いていれば、
IO出力の落とし忘れが防げます。
3.クラスを書いてみよう
LEDをIO制御するためのクラスを作ってみましょう。
変数は、
iPortは、出力制御をするためのポートを覚えておくための変数
iStatusは、出力状況を覚えておくためのStatusです。
(ただ、今回はStatusは使いません。)
関数は、
コンストラクタとデストラクタ
そして、出力を制御する関数です。
これをクラスにすると、
--------------------------------------
class LedClass
{
public:
/* costructor */
LedClass(int aPort);
/* destructor */
~LedClass();
/* 出力設定 */
void SetOutput(bool aOutput);
private:
int iPort;
bool iStatus;
};
--------------------------------------
◆3.1 コンストラクタ
コンストラクタでは、
内部変数の初期化を行います。
また、このクラスを使うための事前処理、
今回でいうと出力ポートの設定をします。
LedClass::LedClass(int aPort)
:iPort(aPort),iStatus(false) // 変数の初期化 iPort = aPort; iStatus = false; と同じ、
{
/* コンストラクタで指定されたポートを出力設定 */
pinMode(iPort, OUTPUT);
}
◆3.2 デストラクタ
デストラクタは、異常終了しても必ず出力を停止させるように、出力を落とす
LedClass::~LedClass()
{
digitalWrite(iPort, LOW);
}
◆3.3 処理関数
引数に従って、コンストラクタで指定したポートを制御する
void LedClass::SetOutput(bool aOutput)
{
digitalWrite(iPort, aOutput);
}
4.使ってみよう
実際に使ってみよう。
※コードは最後に書いてますので、試してください。
loop処理の最後、出力がONのまま終わっているはずが、
関数を抜けた後に出力がOFFになる動作が見えたでしょうか?
そのタイミングでデストラクタが動作しているのです。
便利でしょ!
SAIでした。
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
雑談
クラスを理解するのがに苦労した人に聞いてみると、
この部分の理解に苦労したとのことでした。
SAIは、コードを関数、変数ごと、
部品として丸っとパッケージになっているというイメージを持ったため、
クラスという概念に苦労することはありませんでした。
なので、この辺の説明は下手かもしれないです。
クラスの理解に苦しんだ方、
どの辺に苦しんだか教えていただけると、
このページの説明の改善になります。
もしよろしければ、苦労した点をコメントいただければと思います。
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
本日使ったコードは以下の通りです!
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
/*
* @file CPP_L_A_1_TestCLASS
* @brief Lesson A_1 Classを作ってみよう。
* @author SAI
* @date 2021/03/15
* @note Classを作ってみよう
*/
/* *** Define 定義 *** */
const int HeapIoPort = 11;
const int LoopIoPort = 12;
const int StackIoPort = 13;
/*
* @note テストクラスのプロトタイプ
*/
class LedClass
{
public:
/* costructor */
LedClass(int aPort);
/* destructor */
~LedClass();
/* 出力設定 */
void SetOutput(bool aOutput);
private:
int iPort;
bool iStatus;
};
/*
* @fn 初期設定
* @brief コンストラクタ
* @param aPort LEDを制御するポート番号
*/
LedClass::LedClass(int aPort)
:iPort(aPort),iStatus(false)
{
/* コンストラクタで指定されたポートを出力設定 */
pinMode(iPort, OUTPUT);
}
/*
* @fn 終了設定
* @brief デストラクタ
* @note 終了時には必ずリソースを終了状態にする。
* 生成したヒープ、つないだセッションなど開放する
*/
LedClass::~LedClass()
{
digitalWrite(iPort, LOW);
}
/*
* @fn 出力設定
* @brief 出力の設定を切り替える
* @param aOutput ON/OFFを設定する true=点灯 false=消灯
*/
void LedClass::SetOutput(bool aOutput)
{
digitalWrite(iPort, aOutput);
}
/*
* @note Arduinoで最初に呼ばれる関数
*/
void setup()
{
//今回はなし
}
/*
* @note ぐるぐるループ
*/
void loop()
{
delay(5000);
/* スタック上にクラスを生成 */
LedClass lStackLedClass(StackIoPort);
delay(500);
lStackLedClass.SetOutput(true);
delay(500);
lStackLedClass.SetOutput(false);
delay(500);
lStackLedClass.SetOutput(true);
delay(1000);
/* 関数終了時にスタックが解放されて、デストラクタが走る
同時に、デストラクタでLEDを消灯する*/
}



