Arduinoで使用している言語は,C ではなく C++ (C++11)です.つまり,クラス,constexpr,bool 型,string 型など相当幅広い機能が使用できるのです.たまに解説記事でも「C 言語ではこういう書き方をします」などと言われていますが,C++ においては正しくない事があります.C++ は C 言語に比べて遙かにたくさんの機能を持っているため,C 言語的な書き方は推奨されないことも多くあるのです.
さて,Arduino はプログラミングをあまりしない人がプロトタイピングに使うことも多くあります.わかる人には「C++ が使えるよ」と言えば一瞬ですが,そうではない人の方が多いのです.ここではそのような,プログラミング,特に C++ をよく知らない人向けにどのような機能が使えるのかを簡単に説明していきます.
クラス
当然 C++ ですから,クラスが使えます.継承もインターフェースも実装できます.
class Foo { int value = 0; public: void add(int addition) { value += addition; } }; void setup() { Foo f; f.add(10); }
また,同じフォルダにヘッダファイル (.h) とクラスファイル (.cpp) を追加すると,自動で読み込まれて使用できます.
Template
Templateは「違う型に対して一つのコードで記述する」ための機能です.Java や C# におけるジェネリクスという言い方が分かりやすいかもしれません.クラスを色々と使う場合には割とありがたさが出てきます.
// write して println する template<class T, class U> void printAndWrite(T &obj, U value) { obj.write(value); Serial.println(value); } Servo servo; servo.attach(5); printAndWrite(servo, 10); // write が使えるなら何を渡してもよい printAndWrite(Serial, "test"); // serial でもまったく同じコードが使用される
演算子のオーバーロード
プログラムの中の +
や <<
のような計算に使用する記号を演算子と呼びます.そしてこの演算子は上書きすることができます.これを用いれば,例えばサーボモータを回転させるときに servo + 10
で10°動かすようにもできます.
便利なような,便利でないような.自分でいろいろクラスを書くときには便利になるのですが,やり過ぎると返ってわかりづらくなります.
// + の演算子をオーバーロードして書き換え inline Servo &operator +(Servo &obj, T degree) { obj.write(degree); return obj; } Servo s; void setup() { Foo f = Foo(); s.attach(5); s.write(10); // 10°回転 s + 10; // これでも10°回転できる }
foreach
他の多くの言語と同様に,foreach構文が使えます.
int foo[] = {1,2,3,4,5}; // 普通の繰り返し for (auto i = 0; i < sizeof(foo); i++) { Serial.println(i); } // foreach for (auto i : foo) { Serial.println(i); }
constexpr
constexpr は定数式です.引数の変数だけで返り値が決まるメソッドは,頭にconstexpr を付けると高速になったり副作用がないことを保証したりと色々利点があります.C++14では色々複雑に書けるのですが,C++11 なので return 文一つしか書けません.
constexpr int foo(int a, int b) { return a < b ? a : b; } void setup() { foo(1, 3); // コンパイル時には単に 3 に置き換わるので高速 }
また,定数も constexpr で書けます.Ardino のコードサンプルでは#define
が多用されていますが,こちらを使った方が安全でしょう.
constexpr string bar = "Hello."; void setup() { constexpr int foo = 100; for (auto i = 0; i < foo; i++) { // ここでは foo が使える Serial.println(i); } } void loop() { // Serial.println(foo); // ここではfooが使えないのでコンパイルエラー Serial.println(bar); // これは使える }
- constexpr関数はコンパイル時処理.これはいい.実行時が霞んで見える.cpuの嬌声が聞こえてきそうだ
- constexpr を使うべき5の理由 – まとめ&リンク集 – ボレロ村上 – ENiyGmaA Code
- C++0x が底辺 C++er にもたらすメリット – 偏見プログラマの語り!
- #define で数値を定義する代わりに constexpr を使う | dendrocopos開発室
- それC++なら#defineじゃなくてもできるよ | Tosainu Lab
string
C++なので文字列が使えます.Serial.read()
で1バイトずつ読み出すのではなく,Serial.readString()
やSerial.readStringUntil(char)
を使えばstringで一発で取れます.
なお,readString
は通信が完了したのか確認するために 1 秒間待ってしまうので,readStringUntil
を使った方が高速です.
- Arduino readString(); code runs slow – Stack Overflow
- 【prog】Arduino・シリアルで文字列受信するには:ゆうがたの「特にコレと言って」 – ブロマガ
auto
auto
が使えます.毎回型を調べて書く必要がなくなります.millis()
をlong
ではなくint
で受けてしまってバグが発生する,というミスもなくなります.
これは型推論なので,変なコードを書いたときはちゃんとエラーになります.JavaScriptのように,バグがあるのに走り続けるということがありません.
無名関数
ラムダ式が使えます.
auto func = [](int a, int b){return a * b;}; func(2, 3); // 3 が返る
これを利用すれば,割り込みなどの時に関数をいちいち書く必要がなくなります.
#include void foo() { digitalWrite(5, HIGH); } void setup() { MsTimer2::set(100, foo); // 関数をいちいち手前に書かなくてはいけないので面倒 MsTimer2::set(100, []{digitalWrite(5, HIGH);}); // 一行で書ける MsTimer2::start(); attachInterrupt(digitalPinToInterrupt(3), []{digitalWrite(5, HIGH);}, RISING); }