Ive nociced that code often includes the #define statement to set values that are constant in a sketch. I think that it is useful because they are easy to find.
I have 2 questions about this,
Can they be any value? Ie, no type is set as with a variable, can the defined value effectiveley be a float.
Is there really any difference between a #define statement and a const variable?
#defines are simple text replacements. Whatever is in the define is just replaced where it is used. So it can be a float, string, String, any data type but its type is not relevant. If you define as float it will be converted to text.
The value in the define has no type (byte, int, long). The const value retains its type. So it will occupy that types room in memory. A byte const, 2 bytes, an int, 4 bytes, ......
I prefer to not use #define most of the time and never for constant values.
By using a const variable of a known type, you assure yourself and the compiler that you know what type it should be, instead of allowing the compiler to do as it wishes, which can result in code malfunction when you treat the value inappropriately/carelessly. The examples of this occur daily on this forum, so just stay tuned.
That's a start.
ES.31: Don’t use macros for constants or “functions”
Reason
Macros are a major source of bugs. Macros don’t obey the usual scope and type rules. Macros don’t obey the usual rules for argument passing. Macros ensure that the human reader sees something different from what the compiler sees. Macros complicate tool building.
In the define itself it indeed has no type as it’s just a directive for the preprocessor and as explained already every occurrence of the keyword will be replaced textually by the text that was provided.
At that point the C++ rules apply and a type (when appropriate) will be inferred by the compiler. For example if you have #define value 42, wherever this 42 gets injected it will be treated as an int as this is the default type for integral literals (if it fits).
That’s where the proverbial sh*t can hit the fan because of type constrained operations. On a UNO for example an int is on 16 bits and the max value is 32767. If you do
#define oneSecond 1000 // ms
#define oneMinute (60*oneSecond)
The maths in oneMinute will be conducted as 16bits value and 60000 does not fit and you’ll rollover and get a wrong value.
Contrast that with typed constants If you do
constexpr unsigned long oneSecond = 1000; // ms
constexpr unsigned long oneMinute = (60*oneSecond);
Then the compiler knows oneSecond is of type unsigned long and the multiplication is conducted as unsigned long and thus oneMinute has the right value.
(You could use suffix like U UL ULL…) to provide more info on the literal’s type but that does not make the use of #define better. You should just keep that for conditional compile options.)
in your code you use for example D_println(F("this is a debug trace")); and if you defined DEBUG to be 1 then the preprocessor will replace that with Serial.println(F("this is a debug trace")); and you'll see the trace in the serial monitor.
But if you defined DEBUG to be 0, then D_println is defined to be empty and the whole line D_println(F("this is a debug trace")); just goes away when you compile.
âžś that's an easy way to remove debug code from your project once everything is working (really 0 memory will be used)
@iangill
yes, it's called "conditional compilation" for that reason.
Take a browse through many of the .h and .hpp files in the libraries you've loaded, you'll see a plethora of examples of use of the #if, #else, and other valid constructs and how they're used. It's quite illustrative, and you'll find more uses as you note other keywords.