クラスとオブジェクト:実際に使用する必要のあるファイルタイプはいくつですか?


20

C ++やCの経験はありませんが、C#のプログラミング方法を知っていて、Arduinoを学んでいます。私はスケッチを整理したいだけで、Arduino言語には限界がありますが、Arduinoプログラミングにはオブジェクト指向のアプローチが必要です。

だから私はあなたがコードを整理するためにあなたが次の方法(網羅的なリストではない)を持つことができることを見てきました:

  1. 単一の.inoファイル。
  2. 同じフォルダー内の複数の.inoファイル(IDEが呼び出して「タブ」のように表示するもの)。
  3. 同じフォルダーに.hおよび.cppファイルが含まれている.inoファイル。
  4. 上記と同じですが、ファイルはArduinoプログラムフォルダ内にインストールされたライブラリです。

次の方法も聞いたことがありますが、まだ機能していません。

  • 同じ単一の.inoファイルでC ++スタイルのクラスを宣言します(聞いたことはありますが、動作することはありません-それは可能ですか?)。
  • [推奨されるアプローチ]クラスが宣言されている.cppファイルを含むが、.hファイル使用しない(このアプローチが必要ですか?

コードをよりパーティション化するためにクラスのみを使用したいことに注意してください。私のアプリケーションは、ボタン、LED、ブザーのみを含む、非常にシンプルなものでなければなりません。


興味のある方のために、ヘッダ無し(CPPのみ)クラス定義についての興味深い議論がここにあります:programmers.stackexchange.com/a/35391/35959
heltonbiker

回答:


31

IDEが物事を整理する方法

まず、IDEが「スケッチ」を整理する方法です。

  • メイン.inoファイルは、それが入っているフォルダーと同じ名前のものです。したがって、foobar.inoin foobarフォルダーの場合、メインファイルはfoobar.inoです。
  • .inoそのフォルダー内の他のファイルは、メインファイルの末尾にアルファベット順に連結されます(メインファイルの場所に関係なく、アルファベット順に)。
  • この連結されたファイルは.cppファイル(例:)になりますfoobar.cpp-一時的なコンパイルフォルダーに配置されます。
  • プリプロセッサは、そのファイルで見つかった関数の関数プロトタイプを「役立つように」生成します。
  • メインファイルの#include <libraryname>ディレクティブがスキャンされます。これにより、IDEがトリガーされ、関連するすべてのファイルが各(言及された)ライブラリから一時フォルダーにコピーされ、コンパイルの指示が生成されます。
  • 任意の.c.cppまたは.asmスケッチフォルダ内のファイルは(つまり、それらは別々のファイルとして通常の方法でコンパイルされている)別のコンパイル単位としてビルドプロセスに追加されます
  • すべての.hファイルは一時コンパイルフォルダーにもコピーされるため、.cまたは.cppファイルから参照できます。
  • コンパイラは、ビルドプロセスに標準ファイル(などmain.cpp)を追加します
  • ビルドプロセスは、上記のすべてのファイルをオブジェクトファイルにコンパイルします。
  • コンパイル段階が成功した場合、それらはAVR標準ライブラリと一緒にリンクされます(例えば、あなたに与えるstrcpyなど)

このすべての副作用は、メインスケッチ(.inoファイル)がすべての意図と目的に対してC ++であると見なせることです。ただし、関数プロトタイプの生成では、注意しないと不明瞭なエラーメッセージが表示される可能性があります。


プリプロセッサの癖を回避する

これらの特異性を回避する最も簡単な方法は、メインスケッチを空白のままにすることです(他の.inoファイルは使用しません)。次に、別のタブ(.cppファイル)を作成し、次のようにそこにアイテムを配置します。

#include <Arduino.h>

// put your sketch here ...

void setup ()
  {

  }  // end of setup

void loop ()
  {

  }  // end of loop

を含める必要があることに注意してくださいArduino.h。IDEはメインスケッチに対してそれを自動的に行いますが、他のコンパイルユニットについては、それを行う必要があります。そうしないと、String、ハードウェアレジスタなどのことを認識できません。


セットアップ/メインパラダイムの回避

セットアップ/ループの概念で実行する必要はありません。たとえば、.cppファイルは次のようになります。

#include <Arduino.h>

int main ()
  {
  init ();  // initialize timers
  Serial.begin (115200);
  Serial.println ("Hello, world");
  Serial.flush (); // let serial printing finish
  }  // end of main

強制的にライブラリを含める

「空のスケッチ」の概念で実行する場合、プロジェクトの別の場所、たとえばメイン.inoファイルで使用されるライブラリを含める必要があります。

#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>

これは、IDEがライブラリの使用状況についてメインファイルのみをスキャンするためです。効果的には、メインファイルを、使用中の外部ライブラリを指定する「プロジェクト」ファイルと見なすことができます。


命名の問題

  • メインスケッチに「main.cpp」という名前を付けないでください。IDEには独自のmain.cppが含まれているため、重複すると重複します。

  • メインの.inoファイルと同じ名前で.cppファイルに名前を付けないでください。.inoファイルは事実上.cppファイルになるため、これにより名前の衝突も発生します。


同じ単一の.inoファイルでC ++スタイルのクラスを宣言します(聞いたことはありますが、動作することはありません-それは可能ですか?)。

はい、これは問題なくコンパイルします:

class foo {
  public:
};

foo bar;

void setup () { }
void loop () { }

ただし、通常の慣行に従うことをお勧めします。宣言を.hファイルに、定義(実装)を.cpp(または.c)ファイルに入れます。

なぜ「おそらく」?

私の例が示すように、すべてを1つのファイルにまとめることができます。大規模なプロジェクトの場合は、組織化することをお勧めします。最終的には、中規模から大規模のプロジェクトの段階に到達します。そこでは、物事を「ブラックボックス」に分割します。可能な限り)。

このクラスをプロジェクト内の他の複数のファイルで使用する場合、個別のファイル.h.cppファイルが使用されます。

  • .hファイルには、宣言したクラスを-それはどのような機能、それが持っている、そしてそれらがどのように呼ばれて、それが何を知っている他のファイルのための十分な詳細を提供するものであること。

  • この.cppファイル、クラスを定義(実装)します。つまり、実際にクラスに機能を実行させる関数と静的クラスメンバーを提供します。一度だけ実装したいので、これは別のファイルにあります。

  • .hファイルには、他のファイルに含まれますものです。.cppファイルは、クラスの機能を実装するためにIDEによって一度にコンパイルされます。

図書館

このパラダイムに従えば、クラス全体(.hおよび.cppファイル)を非常に簡単にライブラリーに移動できます。その後、複数のプロジェクト間で共有できます。必要とされるすべてのフォルダ(例を。作ることですmyLibrary)と置く.h.cpp(例えば。そこにファイルをmyLibrary.hしてmyLibrary.cpp)、その後、あなたの内側にこのフォルダを置くlibrariesあなたのスケッチが保存されているフォルダ(スケッチブックフォルダ)内のフォルダ。

IDEを再起動すると、IDEはこのライブラリを認識します。これは実に簡単なことで、このライブラリを複数のプロジェクトで共有できるようになりました。私はこれをたくさんします。


ここでもう少し詳しく。


いい答えだ。しかし、最も重要なトピックの1つはまだ明確ではありませんでした。なぜ、誰もが「おそらく、通常のプラクティスに従うのが最善です。.h+ .cpp」と言うのでしょうか。なぜそれが良いのですか?なぜそうなるのでしょうか?そして最も重要なのは、どうすればそれができない、つまり、同じ単一の.cppファイルにインターフェイス実装(つまり、クラスコード全体)の両方を含めることができないのかということです。どうもありがとうございました!:o)
heltonbiker

別のファイルを用意する必要がある理由を「おそらく」答える別の段落を追加しました。
ニックギャモン

1
どうしてやらないの?私の答えに示されているように、それらをすべてまとめてください。ただし、プリプロセッサがあなたに対して動作することがあります。一部の完全に有効なC ++クラス定義は、メインの.inoファイルに配置すると失敗します。
ニックギャモン

また、2つの.cppファイルに.Hファイルを含め、その.hファイルにコードが含まれている場合も失敗します。これは一般的な習慣です。そのオープンソースは、自分で修正するだけです。あなたがそれをやるのが苦手なら、おそらくオープンソースを使うべきではないでしょう。@Nick Gammonの美しい説明は、これまで見てきた何よりも優れています。

@ Spiked3私が最も快適なものを選ぶことはそれほど問題ではありません。今のところ、そもそも何が選べるかを知ることです。自分のオプションが何であるかさえ分からず、各オプションがそうである理由がわからない場合、どうすれば賢明な選択をすることができますか?私が言ったように、私は以前にC ++の経験がなく、ArduinoのC ++はこの答えに示すように特別な注意を必要とするようです。しかし、私は最終的にそれを把握し、車輪を再発明することなく私の
仕事を完了すると確信しています

6

私のアドバイスは、物事の典型的なC ++の方法に固執することです:各クラスの.hファイルと.cppファイルにインターフェイスと実装を分離します。

いくつかのキャッチがあります。

  • 少なくとも1つの.inoファイルが必要です-クラスをインスタンス化する.cppファイルへのシンボリックリンクを使用します。
  • Arduino環境が期待するコールバック(setu、loopなど)を提供する必要があります
  • 場合によっては、Arduino IDEを通常のものと区別する非標準の奇妙なことに驚かされることがあります。たとえば、特定のライブラリを自動的に含めるが、他のライブラリは含めないということです。

または、Arduino IDEを捨ててEclipseを試すこともできます。私が言ったように、初心者を助けることになっているもののいくつかは、より経験豊富な開発者の邪魔になる傾向があります。


スケッチを複数のファイル(タブまたはインクルード)に分離すると、すべてが独自の場所に配置されるのに役立つと感じていますが、同じこと(.hと.cpp)を処理するために2つのファイルが必要なように感じます不要な冗長性/複製。クラスが2回定義されているように感じます。1つの場所を変更するたびに、他の場所を変更する必要があります。これは、特定のヘッダーの実装が1つだけで、単一のスケッチで1回しか使用されない、私のような単純なケースにのみ適用されることに注意してください。
heltonbiker

これにより、コンパイラ/リンカーの作業が簡素化され、クラスの一部ではないが一部のメソッドで使用される要素を.cppファイルに含めることができます。また、クラスに静的メンバーがある場合、それらを.hファイルに配置することはできません。
イゴールストップパ

.hファイルと.cppファイルの分離は、長い間不要であると認識されてきました。Java、C#、JSはヘッダーファイルを必要とせず、cpp iso標準でさえそれらから離れようとしています。問題は、このような根本的な変更で壊れる可能性があるレガシーコードが多すぎることです。これが、拡張CだけでなくCの後のCPPがある理由です。CPPの後のCPXでも同じことが再び起こると思いますか?

確かに、次のリビジョンがヘッダーなしでヘッダーによって行われるのと同じタスクを実行する方法を思い付くなら...しかし、一方で、ヘッダーなしではできないことはたくさんあります:どのように分散コンパイルが見たいですかヘッダーがなく、大きなオーバーヘッドが発生することもありません。
イゴールストップパ

6

ヘッダーを使用せずに、同じ.cppファイルでクラスを宣言および実装する方法を見つけてテストした後、完全性のために回答を投稿しています。したがって、「クラスを使用するために必要なファイルタイプの数」という質問の正確な表現については、現在の回答では2つのファイルを使用しています。 )クラス、玩具車両の方向転換信号を表します。

Blinker.ino

#include <TurnSignals.cpp>

TurnSignals turnSignals(2, 4, 8);

void setup() { }

void loop() {
  turnSignals.run();
}

TurnSignals.cpp

#include "Arduino.h"

class TurnSignals
{
    int 
        _left, 
        _right, 
        _buzzer;

    const int 
        amberPeriod = 300,

        beepInFrequency = 600,
        beepOutFrequency = 500,
        beepDuration = 20;    

    boolean
        lightsOn = false;

    public : TurnSignals(int leftPin, int rightPin, int buzzerPin)
    {
        _left = leftPin;
        _right = rightPin;
        _buzzer = buzzerPin;

        pinMode(_left, OUTPUT);
        pinMode(_right, OUTPUT);
        pinMode(_buzzer, OUTPUT);            
    }

    public : void run() 
    {        
        blinkAll();
    }

    void blinkAll() 
    {
        static long lastMillis = 0;
        long currentMillis = millis();
        long elapsed = currentMillis - lastMillis;
        if (elapsed > amberPeriod) {
            if (lightsOn)
                turnLightsOff();   
            else
                turnLightsOn();
            lastMillis = currentMillis;
        }
    }

    void turnLightsOn()
    {
        tone(_buzzer, beepInFrequency, beepDuration);
        digitalWrite(_left, HIGH);
        digitalWrite(_right, HIGH);
        lightsOn = true;
    }

    void turnLightsOff()
    {
        tone(_buzzer, beepOutFrequency, beepDuration);
        digitalWrite(_left, LOW);
        digitalWrite(_right, LOW);
        lightsOn = false;
    }
};

1
これはjavaに似ており、メソッドの実装をクラスの宣言に平手打ちします。読みやすさの低下-ヘッダーはメソッドの宣言を簡潔な形式で提供します-異常なクラス宣言(静的、友人など)がまだ機能するかどうか疑問に思います。しかし、この例のほとんどは、インクルードが単純に連結されると1回だけファイルをインクルードするため、あまり良くありません。実際の問題は、同じファイルを複数の場所にインクルードし、リンカーから競合するオブジェクト宣言を取得し始めるときに始まります。
イゴールストップパ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.