Die Verwendung zeitgenössischer C ++ – Methoden mit Arduino

C ++ hat sich in den letzten Jahren schnell modernisiert. Beginnend mit der Einführung von C ++ 11 hat die Sprache einen großen Schritt voraus, und die Dinge haben sich unter der Kapuze verändert. Zum typischen Arduino-Benutzer ist ein paar davon irrelevant, vielleicht viele davon, aber die Sprache bietet uns jedoch immer noch einige gute Funktionen, die wir nutzen können, wenn wir unsere Mikrocontroller programmieren können.

Modern C ++ ermöglicht es uns, mit einem saubereren, viel prägnanten Code zusammenzubauen, und den Code erstellen wir viel wiederverwenderer. Die Einhaltung der Einhaltung sind einige Methoden, die neue Funktionen von C ++ verwenden, die keinen Speicherübergang hinzufügen, die Geschwindigkeit verringern oder die Größe erhöhen, da sie alle vom Compiler behandelt werden. Wenn Sie diese Funktionen der Sprache verwenden, müssen Sie sich nicht mehr darauf bedacht, eine 16-Bit-Variable anzugeben, indem Sie die falsche Funktion mit NULL anrufen oder Ihre Konstruktoren mit Initialisierungen pfefferieren. Die alten Methoden werden immer noch angeboten, wie Sie sie immer noch verwenden können, jedoch aufheben, jedoch nach dem Lesen, nachdem Sie dies den neueren Funktionen bewusst sind, wie wir anfangen, sie in Arduino-Code herauszuspielen.

Wie groß sind deine Ganzzahlen?

C ++ 11 stellte eine Reihe von Festtypen mit festen Breitenarten ein, die Ihnen die Anzahl der Bits (in Multiples von 8) bieten, die Sie möchten. Es gibt sowohl signiert als auch unsignierte Versionen. Wenn Sie Int16_T für eine Variable verwenden, verstehen Sie die 16 Bits, unabhängig davon, ob Arduino angerufen wird.

1.
2.
3
4.
INT8_T / UINT8_T – ein unterschriebener / unsignierter Typ, der genau 8 Bit in der Größe ist.
INT16_T / UINT16_T – ein unterschriebener / unsignierter Typ, der genau 16 Bit in der Größe ist.
INT32_T / UINT32_T – ein unterschriebener / unsignierter Typ, der genau 32 Bit in der Größe ist.
INT64_T / UINT64_T – ein unterschriebener / unsignierter Typ, der genau 64 Bit in der Größe ist.

Dies sind Aliase der zugrunde liegenden Typen, so dass in ARDUINO UNO int8_t der genaue Typ als Zeichen ist, und uint16 ist die genaue Größe der gleichen Größe wie ein unsignierter Int. Eine Notiz, wenn Sie eine ‘.cpp’-Datei bearbeiten, müssen Sie jedoch #include in einer ‘.ino’ -Datei verwenden, nicht.

Anonyme Namespaces.

Die Idee eines anonymen Namensraums ist seit einiger Zeit in C ++, jedoch betraf jedoch die Prominenz mit der Einführung von C ++ 11. In C ++ 11 ist der anonyme Namespace nun die bevorzugte Methode, um eine Variable, eine Funktion, eine Klasse anzugeben, die nur in den vorliegenden Daten erreichbar ist (dh sie verfügen nicht, anstatt extern, verbindend). Es ist ebenfalls eine gute Methode, um Dinge zu konsolidieren, die nur in der vorliegenden Datei aufgerufen werden müssen. Anonymer Namespace werden ebenfalls als “Unbenannte Namespaces” bezeichnet, da sie, wie der Name schon sagt, ohne Namen definiert:

1.
2.
3
Namespace {

}

Anonyme Namespaces nehmen den Ort, um die Dinge statisch zu erklären. Alles, was Sie als statische oder consted erklärt haben oder als #define geschrieben haben, können jetzt in einen anonymen Namensraum eingesetzt werden, sowie den gleichen Auswirkungen haben – alles, was in der Innenseite definiert ist, kann nicht außerhalb der “.cpp” -Daten aufgerufen werden, die der Namespace enthält ist in. In der Arduino-IDE, wenn Sie jedoch eine Registerkarte hinzufügen sowie die neue Registerkarte einen Namen geben, der nicht in ‘.cpp’ oder ‘.h’ endet, werden die Daten eine Erweiterung von ‘.ino . ‘Wenn Sie auf die Schaltfläche “Verify” klicken, werden alle “.ino” -Daten in einer einzelnen’ .cpp ‘-Datei verkettet. Dies bedeutet, dass alles, was Sie als statischer oder kauf oder in einem anonymen Namensraum in jedem “.ino” -Daten angeboten werden Dies ist typischerweise kein Thema für kleine Arduino-Programme, jedoch ist es großartig, sich dessen bewusst zu sein.

Okay, so verstehen wir jetzt über Inneneinrichtung sowie außerhalb der Verknüpfungen sowie genau, wie Daten vor dem Zusammenstellen verkettet werden. Wie hilft uns dies genau in unserem Code? Nun, wir verstehen jetzt genau, wie Sie Variablen definieren können, um sicherzustellen, dass sie nicht in Bereiche auslaufen, die sie nicht erwartet werden.

anstatt zu schreiben:

1.
2.
3
4.
5.
6.
// Dies sollte trotzdem sparsam eingesetzt werden
#Define einige_var 1000.
// Statische Variablen, die in einem Daten deklariert sind, sind regional in der Datei
static int16_t count = 0;
// const-Variablen, die in einem Daten deklariert sind, sind auch regional an den Daten
const int16_t nummers = 4;

Sie können jetzt schreiben:

1.
2.
3
4.
5.
6.
7.
8.
9.
Namespace {
const int16_t einiger_var = 1000; // Jetzt ist es type-sicher
int16_t count = 0; // Keine Anforderung, statische Nutzung zu nutzen
const int16_t nummers = 0; // immer noch const.

Klasse thisclasshasacommonname {

};
}

Alles ist zu Beginn der Datei innerhalb des anonymen Namespaces enthalten. Der Compiler wird nicht verwirrt, wenn in einer anderen Datei eine weitere Dateien, zählen oder nummeriert ist. Neben statischen statischen, in einem anonymen Namespace deklarierten Klassen sind auch regional zu den Daten.

Automatik für die Menschen

Das in C ++ 11 hinzugefügte Keyword, der in C ++ 11 hinzugefügt wird, können Sie eine Variable definieren, ohne den Typ zu verstehen. Wenn der Typ definiert ist, wie andere Variablen, kann der Typ jedoch nicht geändert werden, genau wie routine C++ variables. The C++ compiler utilizes deduction figure out the variable’s type.

1.
2.
3
auto i = 5;          // i is of type int
auto j = 7.5f;       // j is of type float
auto k = GetResult();  // k is of whatever type GetResult() returns

in purchase for you to specify that you want a reference, you can do this:

1.
2.
auto& temp1 = myValue;
const auto& temp2 = myValue;

The appropriate type is deduced for tips as well:

1.
2.
int myValue = 5;
auto myValuePtr = &myValue; // myValuePtr is a tip to an int.

Auto is a fantastic shorthand for those particularly long, challenging types.  It works fantastic for defining iterator instances!

Using using

There have been a couple of methods to produce aliases in C++: The lowly (and dangerous) #define as well as the less harmful typedef. The utilize of typedef is favored to a #define, however they do have a couple of issues. The very first is readability. think about the complying with typedef:

1.
typedef void(*fp)(int, const char*);

At first glance, particularly if you don’t utilize C++ a lot, it can be challenging to identify that this is producing an alias, fp, that is a tip to a function that returns space as well as takes an int as well as a string parameter.  Now, let’s see the C++11 way:

1.
using fp = void(*)(int, const char*);

We can now see that fp is an alias to something, as well as it’s a bit simpler to identify that it’s a function tip that it’s an alias of.

The second difference is a bit much more esoteric, at least in Arduino programming. Usings can be templatized while typedefs cannot.

Nullptr

In C as well as C++98, NULL is really defined as 0. To be backwards compatible, the C++ compiler will enable you to initialize a tip variable using 0 or NULL.

When you begin utilizing auto, though, you’ll begin seeing code like this:

1.
2.
3
auto result = GetResult(…);

if (result == 0) { … }

Just from taking a look at this, you can’t tell if GetResult returns a tip or an integer. However, even among those who still utilize C, not numerous will inspect if result == 0, they’ll inspect if result == NULL.

1.
2.
3
auto result = GetResult(…);

if (result == NULL) { … }

If, later on, you modification GetResult to return an int, there’s no compiler error – the statement is still valid, although it still appears like it ought to be a pointer.

C++11 modifications this by introducing nullptr, which is an actual tip type. Now, you type:

1.
2.
3
auto result = GetResult(…);

if (result == nullptr) { … }

Now you understand that GetResult can only return a pointer. as well as if somebody modifications it on you, then they’ll get a compile error if they don’t likewise modification the if statement on you. The introduction of nullptr likewise implies that it’s safer to overload a technique on an integral type as well as a tip type. Zum Beispiel:

1.
2.
3
4.
5.
void SetValue(int i);     // (1)
void SetValue(Widget* w); // (2)

SetValue(5);    // phone calls 1
SetValue(NULL); // likewise phone calls 1

Because NULL isn’t a pointer, the second contact us to SetValue phone calls the version that takes an integer parameter. We can now phone call the second SetValue correctly by utilizing nullptr:

1.
SetValue(nullptr);  // phone calls 2

This is why it’s typically thought about harmful to overload a function or technique based on an integer parameter as well as a tip (to anything). It’s safer now, however still frowned upon.

Default Initialization

Considering the complying with class:

1.
2.
3
4.
5.
6.
7.
8.
class Foo {
  Foo() : fooString(nullptr) { … }
  Foo(const char* str) : fooString(nullptr) { … }
  Foo(const Foo& other) : fooString(nullptr) { … }

Privat:
    char* fooString;
};

We’ve initialized all the variable with nullptr, which is good. If one more member variable is added to this class we now have to add three much more initializations to the constructors. If your class has a number of variables, you have to add initializers for all of them in all the constructors. C++11 provides you the choice to initialize variables inline with the declaration.

1.
2.
3

Privat:
  char* fooString = nullptr;

With C++11, we can specify a default preliminary value – we can still override this in each constructor if we requirement to, but, if we don’t, it doesn’t matter exactly how numerous constructors we add, we only requirement to set the value in one place. If we’ve separated our class out in to a ‘.h’ data as well as a ‘.cpp’ file, then an added benefit is that we only have to open the ‘.h’ data to add as well as initialize a Variable.

Scoping Your Enums

One of the things that C++ tries to do is enable the programmer to encapsulate things so that, for example, when you name a variable, you’re not unintentionally naming your variable the exact same as something else with the exact same name. C++ provides you tools to enable you to do this, such as namespaces as well as classes. The lowly enum, however, leaks its entrancesinto the surrounding scope:

1.
2.
3
4.
5.
6.
7.
enum Colour {
Weiß,
Blau,
Gelb
};
// Doesn’t compile.  There’s already something in this range with the name ‘white’
auto white = 5; 

The variable ‘white’ can’t be defined since ‘white’ is part of an enum, as well as that enum leaks it’s entrances into the surrounding scope. C++11 introduces scoped enums which enable a couple of things that C++98 didn’t allow. The first, as the name implies, is that the enums are completely scoped. The method to produce a scoped enum is with the utilize of the ‘class’ keyword:

1.
2.
3
4.
5.
6.
7.
8.
9.
10.
enum class Colour {
Weiß,
Blau,
Gelb
};

auto white = 5; // This now works.

Colour c = white; // Error, nothing in this range has been defined called white.
Colour c = Colour::white; // Richtig.

By default, scoped enums have an underlying type: int, so whatever size an int is on your platform, that’s the size that sizeof will return for your enum. before C++11, unscoped enums likewise had an underlying type, however the compiler tried to be wise about it, so it would identify what the underlying type was as well as not tell us – it might enhance it for size as well as produce an underlying type that was the smallest that might in shape the number of entries. Or it might enhance for speed as well as produce a type that was the fastest for the platform. All this implies is that the compiler understood what type an enum was, however we didn’t, so we couldn’t ahead state the enum in a different file. Zum Beispiel,

1.
2.
3
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
file1.h:

enum Colour {
Weiß,
Blau,
Gelb
};

file2.h:

enum Colour; // Error, in this file, the compiler doesn’t understand what the type of Colour is.

void SomeFunction(Colour c);

The only method is to #include header1.h in header2.h. For little projects, this is fine, however in bigger projects, adding an entry to the Colour enum will imply a recompilation of anything that includes header1.h or header2.h. Scoped enums have a default size: int, so the compiler always understands what size they are. And, you can modification the size if you wish:

1.
2.
3
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
file1.h:

enum class Colour: std::int8_t {
Weiß,
Blau,
Gelb
};

file2.h:

enum class Colour: std::int8_t;

void SomeFunction(Colour c);

Now any type of data that includes file2.h doesn’t necessarily have to be recompiled (file2.cpp will have to, since you’ll have to #include file1.h in it in purchase get it to compile properly.)

Abschließend

You do have option when moving ahead programming your microcontroller. There’s no reason to utilize any type of of these if you don’t want to. I’ve found, however, that they assist clean up my code as well as assist me catch bugs at compile time rather than at run time. numerous are just syntactic sugar, as well as you may not like the change. Some are just niceties that make working in a challenging language a bit nicer. What’s holding you back from trying them out?

Leave a Reply

Your email address will not be published.