STM32資料 発展編2

クラスを使ってみよう

発展編の目標である、センサーのライブラリ作成に不可欠なクラスという概念を勉強してみよう

たくさんコードを書いてる人でも難しい部分があるので、こんな感じでやるんだな程度の理解で大丈夫

クラスとは

構造体との違い

C++ではクラスは構造体とよく似ていて、初期のアクセス権がprivateな構造体である

しかし、一般的には用途によって使い分けられていることが多く

  • 構造体・・・データをまとめて管理する用途
  • クラス・・・複雑な機能を持つ集合体を管理する用途
  • 応用編で扱ったセンサーで例をあげると

  • センサーから取得したデータ・・・構造体
  • データを取得するプログラム・・・クラス
  • のような使い分けになることが多い

    ファイル構成

    クラスを使うときは、ヘッダーファイル(.hpp)ソースファイル(.cpp)の2つのファイルを作成する

    //2つのファイルを作成する
    (ClassName).hpp
    (ClassName).cpp

    ヘッダーファイルには、クラスの定義やメンバー変数、メンバー関数の宣言を記述する

    ソースファイルには、ヘッダーファイルで宣言したメンバ関数の中身を記述する

    このように分けることで、他のファイルからはヘッダーファイルをインクルードするだけで使用できるようになる

    //使うときはヘッダーファイルのみをインクルード
    #include (ClassName).h

    中身が少ない場合は1つのファイルにまとめることもあるが、基本は2つに分けよう

    ヘッダーファイル(.hpp)の書き方

    ヘッダーファイルでは、クラスの宣言をおこなう

    publicはインクルードした時に使用者が使う部分になるので、わかりやすい命名を心がけよう

    使用者は基本的に関数のみを使って操作し、その関数が変数を動かして中身の処理をするので、

    publicに関数、privateに変数という書き方になることが多い(やりたいことによって例外あり)

    (GetSensorData()のようなわかりやすい名前だと使うとき嬉しいね)

    class ClassName{
    
        public:
                
            //Class名と同じ名前でなければOK(次回説明)
            void Function();
                    
        private:
                    
            //変数型はなんでもOK
            int value = 0;
    };

    ソースファイル(.cpp)の書き方

    ClassName内の関数を実装する

    実装するときは(Class名)::(関数名)というように書く必要がある

    "::"をスコープ演算子といって、このクラスの中の関数のことだよという意味を示している

    また、メンバー変数はこの関数内では自由に使用することができる

    ある関数で値1を10倍して、ほかの関数で値1を1/2にするなど関数をまたいだ処理ができる

    (同じ名前で同じ型の変数は共通なので、この2つの関数を実行すると値1が元の5倍になる)

    #include "ClassName.h"
    
    ClassName::Function(){
                
        //ここに関数の処理を書く
                
    } 

    クラスの呼び出し

    クラスは構造体と同じで、クラス名 変数のような宣言をすることで実際に使えるようになる

    例えば応用編で使ったセンサーであるICM45686のクラス"ICM45686"を作ったとしよう

    その場合はこのように変数名のようなものをつけることで使えるようになる

    クラスではこの動作をインスタンス化と呼ぶ

    ICM45686 icm; //ICM4568クラスのインスタンスを作成

    このようにして作ったインスタンスは、構造体と同じように中身を使うことができる

    例えば、ICM45686クラスのメンバ関数である"GetSensorData()"を使う場合はこのように書く

    icm.GetSensorData()

    また同じセンサーを複数使う場合は

    ICM45686 icm1; //それぞれ中身の変数は独立に管理される
    ICM45686 icm2;
    ICM45686 icm3;

    のように複数のインスタンスを作成でき、それぞれを別の設定で好きなタイミングで実行できる

    コンストラクタ

    クラスで便利な機能の1つにコンストラクタがある

    これは、インスタンス化する時に1度だけ実行されるものである

    今回は、STM32のどのピンを使っているかの情報を渡すのに利用する例を実際に紹介する

    コンストラクタの定義

    コンストラクタはクラス名と同じ名前の関数を作成することで定義できる

    普通の関数と同じように引数を渡すことができるので、ここではI2CのPin情報を持つ"I2C_HandleTypeDef*"を引数にした

    また、Pin情報を保存したいので同じ型のメンバー変数を定義した

    class ICM45686{
    
    public:
    ICM45686(I2C_HandleTypeDef* use_i2c_pin); //コンストラクタの定義
    void GetSensorData(); //メンバ関数の宣言
        
    private:
    I2C_HandleTypeDef* i2c_pin; //I2Cのピン情報を格納するメンバ変数
    }

    コンストラクタの実装

    コンストラクタもほかの関数と同じように実装できる

    ここでは、引数で受け取った値をメンバー変数に格納している

    #include "ICM45686.hpp" //ヘッダーファイルをインクルード
    
    ICM45686(I2C_HandleTypeDef* use_i2c_pin){ //コンストラクタの実装
    
    i2c_pin = use_i2c_pin; //引数で渡されたI2Cのピン情報をメンバ変数に格納
    }
    
    //実際にI2Cの関数を使うときは
    //第1引数にi2c_pinを渡すことで、インスタンス化したときに渡したI2Cのピン情報を使うことができる
    //例: HAL_I2C_Mem_Write(i2c_pin, address, reg_address, 1, buffer, len, 1);
    

    このようにすることで、インスタンス化する時にI2Cのピン情報を渡すことができる

    先ほどの例を使うと、異なるI2Cのピンにセンサーをそれぞれつなげるようになる

    これによって汎用性が上がるだけでなく、急な仕様変更にも対応しやすくなる

    ICM45686 icm1(&hi2c1); 
    ICM45686 icm2(&hi2c2);
    ICM45686 icm3(&hi2c3);

    終わりに

    今回はクラスについて説明してみました

    クラスはかなり難しい部分が多いので、実際に書きながら慣れていこう

    リンク

    ・メインページ