Introduction
Seeing if (status == 3) tells a reader nothing, and changing a limit that was typed in twenty places is a recipe for mistakes. C gives you three ways of naming fixed values. Each has its own purpose, and the differences matter.
How it works
const makes a variable that cannot be changed. It has a type and an address, and the compiler checks how it is used.
#define is handled by the preprocessor, a stage that runs before the compiler. It replaces each use of a name with the text you gave. #include is also a preprocessor command: it pastes in the contents of a header file.
#define MAX 100
No semicolon is used, because the whole rest of the line becomes the replacement.
Macros with arguments look like functions but are still only text replacement. That leads to a classic trap. With #define SQUARE(x) x * x, the use SQUARE(2 + 3) becomes 2 + 3 * 2 + 3, which is 11 instead of 25. The fix is brackets around each argument and around the whole thing: #define SQUARE(x) ((x) * (x)).
A macro also repeats its argument, so SQUARE(i++) would add to i twice. A normal function does not have that problem, and it should be your first choice.
enum creates a set of named whole numbers, starting from 0 unless you say otherwise. It suits a small set of choices, such as days or states:
#include <stdio.h>
typedef enum { RED, YELLOW, GREEN } Light;
const char *name_of(Light l) {
switch (l) {
case RED:
return "red";
case YELLOW:
return "yellow";
default:
return "green";
}
}
int main(void) {
Light now = RED;
for (int i = 0; i < 4; i++) {
printf("%s\n", name_of(now));
now = (now + 1) % 3;
}
return 0;
}| Tool | Handled by | Has a type? | Best for |
|---|---|---|---|
const | the compiler | yes | fixed values in code |
#define | the preprocessor | no | array sizes in older code, conditional compiling |
enum | the compiler | yes, integer | a set of related choices |
Syntax
#define SIZE 10
#define MAX(a, b) ((a) > (b) ? (a) : (b))
const double RATE = 0.15;
enum colour { RED, GREEN = 5, BLUE }; // BLUE is 6
Example
#include <stdio.h>
#define MAX_STUDENTS 3
#define SQUARE(x) ((x) * (x))
enum day { MON, TUE, WED };
int main(void) {
const int limit = 10;
int marks[MAX_STUDENTS] = {70, 80, 90};
enum day today = WED;
printf("%d %d\n", SQUARE(2 + 3), limit);
printf("%d %d\n", marks[MAX_STUDENTS - 1], today);
return 0;
}Line by line
MAX_STUDENTSis replaced by 3 everywhere, before the compiler sees the code.SQUAREis safe, because of its brackets.enum daygivesMON,TUEandWEDthe values 0, 1 and 2.SQUARE(2 + 3)becomes((2 + 3) * (2 + 3)), which is 25.marks[MAX_STUDENTS - 1]is the last element, andtodayprints as the number 2.
Expected result
25 10
90 2
In the real world
Header files are full of macros and enums, such as EOF and the error codes in errno.h. Hardware registers are often given names with #define, and state machines in devices are usually built around an enum and a switch.
Common mistakes
- A semicolon at the end of a
#define, which then gets pasted into every use. - Macros without brackets, which give wrong answers inside larger expressions.
- Using a macro with an argument that has a side effect, such as
SQUARE(i++). - Forgetting that an enum is only a number, so
%dprints 2 instead of a name.
Best practices
- Replace every unexplained number with a name.
- Prefer
constandenumto#definewhere you can. - Write constants in capitals, so that they stand out.
Summary
const makes a typed value that cannot change. #define makes the preprocessor replace text, so macros need careful brackets. enum names a set of related whole numbers, and it works well with switch.
Quick check
Key takeaways
- Names make numbers meaningful and easy to change.
- A macro is text replacement, so use plenty of brackets.
- An enum names a small set of related choices.
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.