Pointers are an integral part of the C language and this won’t go into any depth, but to mention a bit of strange syntax…
Any variable, array or function in C takes up some amount of memory. Code can request a pointer to the start of that memory (“address of”) with the ampersand (&
) operator. And a variable that is itself a pointer to something else is declared with an asterisk (*
) operator:
char a[100]; // 100 bytes char *b = &a; // b is a pointer to start of a[] array
That’s simple enough. Where it gets strange is that pointers-to-pointers exist:
char **c = &b; // Pointer to b variable, which is itself a pointer char ***d = &c; // Pointer to c variable, itself a pointer-to-pointer
It’s pointers all the way down! In practical use, I don’t recall ever seeing more than **
pointers-to-pointers (also sometimes called a vector). It’s all a bit obscure, but perfectly valid…one might pass a pointer-to-pointer to a buffer to a library function that may then reallocate it, changing the pointed-to pointer’s value. Don’t be alarmed when you reach that.
Arrow Operator
Classes in C++ (and structures in both C and C++) are a way of grouping related variables into a single encapsulating thing. Classes can also contain functions in addition to variables…but let’s look at a struct first, because it’s simpler:
struct { int month; int day; int year; } apple = { 4, 1, 1976 }; Serial.println(apple.year); // Prints "1976"
Written this way, memory for the structure called “apple” is allocated at compile time, when it’s being declared, because its contents are a known thing initialized from constant values (April 1st, 1976). To access variables within that structure, a period (“.”) is normally used.
Sometimes a program doesn’t know ahead of time what’s going into a structure or class, or whether it’s even going to be used at all. Such code might allocate the struct or class dynamically, using malloc() (for C) or new (for C++). Consider this code that creates two Adafruit_NeoPixel objects…one a static declaration, the other dynamic:
Adafruit_NeoPixel pixels1(42, 5, NEO_GRB); Adafruit_NeoPixel *pixels2 = new Adafruit_NeoPixel(42, 6, NEO_GRB);
Most NeoPixel code is written like the first line. Everything about the NeoPixel strip is known ahead of time…how many pixels there are, what pin it’s connected to, and the color format used.
When it comes time to set a pixel’s color or update the strip, those functions are called with the object name, a period, and the function name:
pixels1.setPixelColor(0, 255, 0, 0); // Set first pixel red pixels1.show(); // Update strip
Trying to do the same with the “pixels2” object would generate errors though, because that object was allocated dynamically…it’s an object pointer. Such items require a slightly different syntax:
pixels2->setPixelColor(0, 0, 0, 255); // Set first pixel blue pixels2->show(); // Update strip
Since pixels2 is really a pointer to an object, not an object itself, the right arrow “points” to where the object was actually allocated.
Why would anyone do this? Well…in the NeoPixel case…suppose you’re producing a lot of something, in multiple configurations. A toy with 10 pixels than animate in one pattern, and a different toy with 20 pixels in another pattern…but you don’t know ahead of time how many of each you’ll be producing. With the static declaration, if you program the wrong number of boards for each design, some will require re-programming with the correct code. With the dynamic declaration…there might be a switch or jumper that’s set during assembly to select which toy this is going inside…and every board can then be programmed with the same code (which checks the jumper and adapts accordingly), no do-overs needed.
Page last edited March 08, 2024
Text editor powered by tinymce.