At the risk of being accused of trolling, here is a wall of text, in an attempt to describe the two main patterns used in C (as opposed to C++) relevant to this question. This message contains the first part, function pointers.
Function pointers
In C, there are two types of pointers: object pointers that point to data, and function pointers that point to functions and behave like functions. For example:
#include <stdlib.h>
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int main(void)
{
int (*op)(int, int); /* Function pointer named 'op', points to a function that returns an int, and takes two ints as arguments */
op = add;
printf("op = add; op(5, 3) = %d, add(5, 3) = %d.\n", op(5, 3), add(5, 3));
op = sub;
printf("op = sub; op(6, 2) = %d, sub(6, 2) = %d.\n", op(6, 2), sub(6, 2));
op = mul;
printf("op = mul; op(7, 4) = %d, mul(7, 4) = %d.\n", op(7, 4), sub(7, 4));
return EXIT_SUCCESS;
}
Many C compilers allow assignment between void object pointers and function pointers, because this is required by the POSIX (https://en.wikipedia.org/wiki/POSIX) standard (via e.g. dlsym()) function. However, C itself does not require that, so for example, in the above code op = NULL; would not be strictly conforming C code. Of course, even when allowed, calling op(0, 0); (with any parameters) would still dereference a NULL function pointer, and crash the program.
I have written an example command-line Reverse Polish Calculator example here (https://stackoverflow.com/a/43344703), showing both how a function pointer in a structure is used to keep track of the operators, and how to dynamically load plugins at run time adding new operators. It should compile and run on all POSIXy operating systems (Linux, Mac OS, *BSDs, many Unix systems).
While function pointers are commonly said to be the way to implement object-oriented programming (https://en.wikipedia.org/wiki/Object-oriented_programming) in C (and the GTK+ (https://www.gtk.org/) widget toolkit is possibly one of the best examples on how it is done), in practice function pointers are much more useful for event-driven programming (https://en.wikipedia.org/wiki/Event-driven_programming).
In microcontroller projects, user interfaces are often implemented with event-driven programming. On a small PIC with an LCD, and a potentially large/complex/deep menu system, we can use function pointers (and object pointers for the menu structure) in a struct to describe the action in each menu entry, and keep it in ROM/FLASH, minimizing the amount of RAM needed.
For example, let's say we have a 2×20 alphanumeric display (or 128×32 OLED) or a similar limited display. Let's say we have four menu traversal buttons, Back, Enter, Prev, Next; and a rotary encoder for changing the displayed value. We have both integer and decimal numeric values (the encoder button switching between the integer and decimal part, for example), some different strings to choose from, and so on. Let's assume we have a preprocessor macro FLASHMEM we can use as a type attribute. Let each menu view be described by a struct:
struct menu_view {
/* Menu navigation */
const struct menu_view FLASHMEM *back;
const struct menu_view FLASHMEM *enter;
const struct menu_view FLASHMEM *prev;
const struct menu_view FLASHMEM *next;
/* Name or title of this menu entry */
const char FLASHMEM *title;
/* RAM data for this menu entry, if any -- can be any type */
void *data;
/* Function to draw the rest of the display (except title); last parameter is dwell time in milliseconds */
void (*update)(const struct menu_view FLASHMEM *, void *, unsigned int);
/* Events; last parameter is the time since last event in microseconds */
void (*increment)(const struct menu_view FLASHMEM *, void *, unsigned int);
void (*decrement)(const struct menu_view FLASHMEM *, void *, unsigned int);
void (*press)(const struct menu_view FLASHMEM *, void *, unsigned int);
void (*release)(const struct menu_view FLASHMEM *, void *, unsigned int);
}
The idea is that you first declare the functions you need to implement each menu view, and then a set of struct menu_view in ROM/flash, and then populate those structs to implement the menu views. While the structures could be made smaller -- for example, one could use an array instead, and bytes (for up to 256 menu views) or shorts (for up to 32768 menu views) instead of object pointers for the menu structures --, only the menu values (say, index to an array of strings for string entries) need to reside in RAM.
The same common code will handle menu navigation, and a single set of event functions can handle all menu entries of the same type.
In practice, designing such menu systems is best started from the desired menu views themselves. To a programmer, it might sound bass-ackwards, but really, knowing what to implement is half the battle. This approach also ensures that you know all required members and functionality from the start of programming, so you can consolidate common code to simple functions, and provide all needed functionality with the least amount of resources used: a big win when talking about 8-bit microcontrollers!
Second part of the Wall-Of-Text started a couple of messages earlier.
Library function naming, and self/this initial member
Standard C does not have a mechanism to split interfaces from different libraries used in a program different namespaces (https://en.wikipedia.org/wiki/Namespace). Symbols can be declared static, in which case they are only visible in that file scope (as opposed to global -- accessible from other file scopes, separately compiled binaries linked to the same program -- or local), but that's about it. Oh, and some names are strictly speaking reserved for the standard C library; but when programming microcontrollers, we typically use a freestanding environment (https://en.wikipedia.org/wiki/C_standard_library#Detection) (without the standard C library, and some "custom" libraries like Hardware Abstraction Layers of some sort instead), and not a standard "hosted environment" C programmers are used to.
To avoid problems with different libraries using the same names, many of them prepend their name to all public (or global) variables or functions they export. For example, all functions provided by the GNU Scientific Library (https://www.gnu.org/software/gsl/doc/html/index.html) begin with the prefix gsl_.
In C++, class member functions are provided with a pointer to the object they were called using, this (https://en.wikipedia.org/wiki/This_%28computer_programming%29). In Python, this reference is self. In general, some variant of this/self is provided by all object-oriented programming languages. Because C does not have it, we need to pass it explicitly. Typically, we pass it as the first parameter, because that is easy to remember for us humans, and it allows preprocessor magick if one so desires.
Because I work a lot with numerical data, and occasionally matrices, and the current scientific library interfaces are, uh, rather arcane (see e.g. LAPACK (https://en.wikipedia.org/wiki/LAPACK) six-letter function naming), and I am most interested in providing tools for other people to implement their ideas with, I developed a very simple interface for matrix operations. I've shown a compilable example here (https://stackoverflow.com/a/34862940).
Essentially, there is a single matrix object type, matrix, that describes both first-class rectangular matrices, and "views" to existing matrices. The actual data exposed by a matrix exists within a reference-counted struct owner. This means that you can expose the same data in any number of different matrices, transposed or reflected or even just picking an arbitrary regular submatrix, and the change in one is immediately visible in all matrices using the same struct owner. As long as one remembers to call matrix_free(&m) on each matrix m they no longer need, the data in struct owners is managed and freed only when no longer needed.
(A secondary deletion scheme is also possible, allocation pools. This means that instead of tracking every single matrix, you create a new allocation pool that you destroy when you are done, destroying all matrices and owner structs in one go. Unfortunately, I discovered that this model is somehow too easy to break by new programmers, so I have dropped it for now. I just wanted to point it out in case anyone wonders about it.)
The important point to notice is that each function is named with a matrix_ prefix. (Naming this library something less generic, and using that name instead of such a generic name, would be even better, as it would reduce the possibility of name collisions. Who knows, someone might have written another matrix library, and someone might wish to use them both at the same time.)
The first parameter is always a pointer to the matrix to be operated on. While these particular matrices are typically declared as matrix m;, we cannot pass the matrix itself by value, because then any changes to m would be only visible within the function, and not to the caller! In C, if we want to modify m in a function with the change visible in the caller, we must pass a pointer to it. (Note that matrix *const m means that m is a const pointer to a matrix; i.e. that the pointer m will not be modified, but the matrix it points to may be modified. This notation may seem odd at first, but I use it because it tells us human programmers how the function intends to access the pointer. Current compilers can usually infer that kind of "const"-ness automatically, but at least in theory, it can also help the compiler to generate better code.)
What does this mean in practice, then?
There are a number of libraries with both C++ and C language bindings (https://en.wikipedia.org/wiki/Language_binding). If the C++ interface looks something like
LCD mydisplay(parameters...);
mydisplay.print("Hello world!");
then, according to my description of function naming and passing the "object" as the first parameter in C, the C interface is probably either
lcd *mydisplay = lcd_init(parameters...);
lcd_print(mydisplay, "Hello world!");
or
lcd mydisplay;
lcd_init(&mydisplay, parameters...);
lcd_print(&mydisplay, "Hello world!");
depending on whether the LCD/lcd type is intended to be dynamically allocated or not; it is basically an arbitrary choice for the library developer to make.
In a lot of C code, fully uppercase names are "reserved" for preprocessor macros. It is just a programmer convention, but a common one; and that is the only reason I used lowercase lcd for the type name. In other words, it is a common stylistic detail that might help in understanding existing code.
Obviously, not every library has both C++ and C bindings. In fact, only libraries originally written in C tend to have both C++ and C bindings, because C++ is a higher-level language and requires a runtime component anyway.
Unfortunately, this also means that you cannot just take a C++ Arduino library, and expect it to magically work in C. The two are completely different languages. For someone who can program in both languages, it may be anything from trivial to impossible to port a library between the two languages. Fortunately, Arduino libraries, especially display management libraries, tend to fall on the "easy/straightforward" sector, as only a small subset of C++ features are used in Arduino.
My OP was a simple inquiry about the described technique used in the Arduino. With a couple clues given here I was able to go back and figure out how it was done as indicated above.
Right. Note that I write my walls of text answers in the hopes that not only you might get something out of them, but in case it helps someone else reading this thread afterwards. So, I try to be thorough, and not make assumptions on how much you might know. My intention is not to offend, just be thorough, but it seems many feel this approach is "trolling"; that's why the warning label in the first message.
If you look at the C++ header code, you'll see the class definition. (The : public Print part means that it is a derivative of the Print class; if this class implements a write function, the Print class will provide print() and println() functions.)
In the C++ code, LiquidCrystal_I2C::LiquidCrystal_I2C defines the constructor function, that initializes the object fields when it is instantiated.
Member functions are defined in a similar way, with the class name prefixed to the member function name.
All public and private variables declared in the class definition are directly accessible in the constructor and member functions. There is also a special this reference to the object itself.
If we look at the constructor function,
class LiquidCrystal_I2C {
public:
LiquidCrystal_I2C(uint8_t lcd_addr, uint8_t lcd_cols, uint8_t lcd_rows, uint8_t charsize = LCD_5x8DOTS);
private:
uint8_t _addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _cols;
uint8_t _rows;
uint8_t _charsize;
uint8_t _backlightval;
};
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_addr, uint8_t lcd_cols, uint8_t lcd_rows, uint8_t charsize)
{
_addr = lcd_addr;
_cols = lcd_cols;
_rows = lcd_rows;
_charsize = charsize;
_backlightval = LCD_BACKLIGHT;
}
In C++, the class is instantiated (an object of that class created) using
LiquidCrystal_I2C mydisplay(addr, cols, rows, charsize);
or, because the header file defines the default value for charsize, using
LiquidCrystal_I2C mydisplay(addr, cols, rows);
which works as if a fourth argument with that default value, LCD_5x8DOTS, was passed.
Ported to C, the same code would look like e.g.
typedef struct lcd_i2c {
uint8_t _addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _cols;
uint8_t _rows;
uint8_t _charsize;
uint8_t _backlightval;
} lcd_i2c;
#define LCD_I2C_INIT(addr, cols, rows, charsize) { addr, 0, 0, 0, cols, rows, charsize, LCD_BACKLIGHT }
void lcd_i2c_init(lcd_i2c *display, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t charsize)
{
display->_addr = addr;
display->_displayfunction = 0;
display->_displaycontrol = 0;
display->_displaymode = 0;
display->_cols = cols;
display->_rows = rows;
display->_charsize = charsize;
display->_backlightval = LCD_BACKLIGHT;
}
The preprocessor macro is not very common, but it is useful when it is defined. It allows one to create the object in C using
lcd_i2c mydisplay = LCD_I2C_INIT(addr, cols, rows, charsize);
but the more common way is to use the initializer function,
lcd_i2c mydisplay;
lcd_i2c_init(&mydisplay, addr, cols, rows, charsize);
You might wish to look at some of the C++ tutorials, for example here (https://www.cplusplus.com/doc/tutorial/classes/) (do a web search to find one that suits you best). Generally speaking, class/object features (including inheritance), and some rare template cases, are the main differences to C in Arduino C++ code. (The Arduino environment uses a strict subset of C++ features, to keep the runtime to a minimum. Exceptions aren't used, for example.)
To repeat, do not take the tone of my writing as condescending, because it is not. I just have a style of "answering" that tries to help not just the immediate asker, but all those who find the thread later on, through a web search or something. Why answer a single question, when you can answer a slew of similar questions at the same go?
I'm older, late-mid career, and although a lifelong electronics hobbyist, I don't write software for a living. Nonetheless, you're never too old to learn.
Fully agreed!