Module 3 · Constants, Qualifiers, Input and Output
Constants and Literals: Values With a Name
In this lesson
- Write an integer, floating, character and string literal with the type you actually meant.
- Give a value a name with
#defineorconst, and say which one to reach for first. - Spot a magic number, and read
010as the eight it really is.
Maria's shop till charges 7.5 percent VAT. The number 7.5 is typed into her program in eleven places. The government moved the rate to 15, she edited ten of the eleven, and for three weeks one kind of receipt was quietly wrong.
Nothing in that story is a hard bug. It is a filing problem, and C has two tools for it. This lesson is about the values you write straight into your code. It is also about naming the important ones, so that there is only ever one of them.
A literal is a value with no name
Every bare value you type into a program is a literal. The 42 in int marks = 42; is an integer literal. The "Progsity" in a printf is a string literal.
A literal has a type, even though you never wrote one down. The compiler picks it from how the literal is written, and that choice is not always the one you expected.
The literal families, and the type each one gets
42 int
42U unsigned int U or u
42L long L or l
42LL long long LL or ll
42ULL unsigned long long the two suffixes together
3.14 double this is the default for decimals
3.14f float f or F
3.14L long double L or l
1.5e3 double, the value 1500.0 e means times ten to the power
'A' int, holding 65 one character in single quotes
'\n' int, holding 10 an escape sequence is one character
"Progsity" an array of char double quotes, a different thing
0x1F int, the value 31 base 16, the 0x prefix
037 int, the value 31 base 8, a leading zero
0b11111 int, the value 31 base 2, C23 or a compiler extension
- The suffix is part of the literal, not part of the variable it is going into.
3.14is adouble. Writing it into afloatvariable narrows it, which is fine and costs you digits.- A character constant is an
intin C, not achar. Module 2 lesson 4 explained why it is a number at all.
So the shortest useful rule is: whole number means int, decimal means double, and a suffix is how you say otherwise.
#include <stdio.h>
int main(void)
{
printf("int literal : %d\n", 42);
printf("long long : %lld\n", 42LL);
printf("unsigned : %u\n", 42U);
printf("double literal : %.2f\n", 3.14);
printf("float literal : %.2f\n", 3.14f);
printf("char literal : %c and %d\n", 'A', 'A');
printf("string literal : %s\n", "Progsity");
return 0;
}
int literal : 42
long long : 42
unsigned : 42
double literal : 3.14
float literal : 3.14
char literal : A and 65
string literal : Progsity
Seven literals, seven specifiers, one program. Notice the sixth line: one literal, printed twice, once as a shape and once as the number underneath it.
A leading zero means base eight
C lets you write the same number in four ways. Base 10 is the plain one. Base 16 starts with 0x, base 8 starts with a single 0, and base 2 starts with 0b.
Three of those are harmless. The fourth is the oldest trap in the language, because a leading zero is something people add for neatness.
#include <stdio.h>
int main(void)
{
int plain = 31;
int hex = 0x1F;
int octal = 037;
int looks_like_ten = 010;
printf("31 is %d\n", plain);
printf("0x1F is %d\n", hex);
printf("037 is %d\n", octal);
printf("010 is %d\n", looks_like_ten);
return 0;
}
31 is 31
0x1F is 31
037 is 31
010 is 8
The first three lines are the same value written three ways. The fourth is a number nobody typed on purpose.
A reader writing a list of product codes as 001, 007, 010 gets 1, 7 and 8. The compiler says nothing, at either command line, because all three are legal octal.
Base 2 is newer. 0b11111 has been a GCC extension for years and became standard C only in C23, so this track writes hex where a bit pattern matters.
So the rule is: a leading zero is never decoration in C, and if you want alignment, use a width in your printf instead.
#define is find and replace, before the compiler looks
A #define is a preprocessor instruction. The preprocessor runs before the compiler, reads your file as text, and swaps every occurrence of the name for the replacement.
It has no type, it takes no semicolon, and it is gone by the time the compiler starts. That is its strength and every one of its problems.
#include <stdio.h>
#define VAT_PERCENT 15
#define SHOP_NAME "Maria General Store"
int main(void)
{
long long bill_paisa = 36000;
printf("%s\n", SHOP_NAME);
printf("VAT is %d percent\n", VAT_PERCENT);
printf("VAT on 360.00 is %lld paisa\n", bill_paisa * VAT_PERCENT / 100);
return 0;
}
Maria General Store
VAT is 15 percent
VAT on 360.00 is 5400 paisa
By the time the compiler reads that program, the word VAT_PERCENT does not exist. It sees the digits 15 in both places, exactly as if Maria had typed them.
Because the swap is textual, a macro also has no scope. A #define at the top of a file is in force to the bottom of it. It will replace the name inside a function, inside a declaration, and anywhere else the name appears.
const is a variable the compiler refuses to let you change
A const is an ordinary variable with one extra word. It has a type, it lives in memory, and it obeys the scope it was declared in. Assigning to it is an error the compiler reports.
#include <stdio.h>
int main(void)
{
const double PI = 3.14159265358979;
const int SEATS_PER_ROW = 8;
double radius = 2.5;
printf("area of the mat : %.2f\n", PI * radius * radius);
printf("seats in 12 rows : %d\n", SEATS_PER_ROW * 12);
return 0;
}
area of the mat : 19.63
seats in 12 rows : 96
Both constants are used and neither is changed. Add the line PI = 3.0; anywhere below them and the program stops building, which is the whole service const performs.
The exact message, and the four other things the compiler starts refusing, are the next lesson. Here the point is narrower: const is a variable with a promise attached, and a #define is not a variable at all.
const or #define, and which one first
Reach for const first. Reach for #define when you need something a variable cannot be.
| Question | const | #define |
|---|---|---|
| Who handles it | the compiler | the preprocessor, before the compiler |
| Does it have a type | yes | no, it is text |
| Is it type checked | yes | nothing to check |
| Can it be scoped to a block | yes | no, file wide from that line |
| Visible in a debugger | yes, by name | no, already replaced |
| Usable as an array size | not as a true constant, see below | yes |
| Takes a semicolon | yes | no, and one will be swapped in too |
The array size row is the one real gap. In C, a const int is a variable whose value you promised not to change, and it is not a constant expression.
Writing int marks[limit]; with a const int limit compiles under C17, but you have made a variable length array rather than a fixed one. Module 9 says what that means and when it matters.
In C++ the same line is a plain fixed array, which is why advice copied from a C++ page can be quietly wrong here.
enum, in one paragraph
There is a third way to name numbers, and it is the right one for a related set. An enum gives readable names to a run of whole numbers, starting at 0 and counting up unless you say otherwise.
#include <stdio.h>
int main(void)
{
enum status { pending, running, passed, failed };
enum status verdict = passed;
printf("verdict = %d\n", verdict);
return 0;
}
verdict = 2
Unlike a const int, an enum name really is a constant expression, so it can size an array. Module 12 builds on this properly alongside struct, and until then treat it as a preview.
A magic number is a number nobody can explain
A magic number is a bare literal in the middle of a calculation whose meaning lives only in the author's head. Six months later the author is a different person.
Here is Maria's till as she first wrote it, and then rewritten with names. The two programs print the same bytes.
Every number here is correct. Try telling somebody what the 75 and the 1000 are without reading the rest of the line.
#include <stdio.h>
int main(void)
{
long long rice = 12000;
long long oil = 45000;
printf("Rice : %lld\n", rice + rice * 75 / 1000);
printf("Oil : %lld\n", oil + oil * 75 / 1000);
printf("Total : %lld\n", rice + oil + (rice + oil) * 75 / 1000);
return 0;
}
Rice : 12900
Oil : 48375
Total : 61275
The rate appears three times. A change to it is three edits, and the compiler cannot tell you when you have done two.
Run in CompilerTwo declarations, and the rate now exists in exactly one place.
#include <stdio.h>
int main(void)
{
const long long VAT_PER_MILLE = 75;
const long long PER_MILLE = 1000;
long long rice = 12000;
long long oil = 45000;
long long both = rice + oil;
printf("Rice : %lld\n", rice + rice * VAT_PER_MILLE / PER_MILLE);
printf("Oil : %lld\n", oil + oil * VAT_PER_MILLE / PER_MILLE);
printf("Total : %lld\n", both + both * VAT_PER_MILLE / PER_MILLE);
return 0;
}
Rice : 12900
Oil : 48375
Total : 61275
Byte for byte the same output. The machine gained nothing and the next reader gained everything.
Per mille rather than per cent, because 7.5 percent is not a whole number and this till keeps everything in whole units. Module 2 lesson 3 is why.
Run in CompilerA #define for the thing that is never a number, a const for everything that is.
#include <stdio.h>
#define SHOP_NAME "Maria General Store"
int main(void)
{
const long long VAT_PER_MILLE = 75;
const long long PER_MILLE = 1000;
const long long PAISA_PER_TAKA = 100;
long long bill = 57000;
long long vat = bill * VAT_PER_MILLE / PER_MILLE;
long long total = bill + vat;
printf("%s\n", SHOP_NAME);
printf("bill : %lld.%02lld\n", bill / PAISA_PER_TAKA, bill % PAISA_PER_TAKA);
printf("vat : %lld.%02lld\n", vat / PAISA_PER_TAKA, vat % PAISA_PER_TAKA);
printf("total : %lld.%02lld\n", total / PAISA_PER_TAKA, total % PAISA_PER_TAKA);
return 0;
}
Maria General Store
bill : 570.00
vat : 42.75
total : 612.75
The % in those last three lines is the remainder operator, and Module 4 introduces it properly. It is here because a receipt is the natural home for it, and Module 4 is two lessons away.
Where this is used
- The standard library you are already using.
BUFSIZ,EOFandNULLare macros in<stdio.h>. When you writeEOFin Module 3 lesson 5, the preprocessor swaps in -1 before the compiler sees it. - File permissions on Linux.
chmod 0755is an octal literal, and the C call behind it takes the same number. The leading zero there is deliberate and load bearing, which is exactly why C reads it that way. - Colours in a graphics program. Every game engine and image tool writes a colour as
0xFF8800, because each pair of hex digits is one byte of red, green or blue. Decimal would hide that structure. - A payroll batch job. Tax bands, the round-off unit and the pay period sit in named constants at the top of one file. The year's rule change is then one edit a reviewer can see.
Common mistakes
1. Padding a number with a zero for tidiness.
int product_code = 010;
printf("%d\n", product_code);
No message at either command line, and it prints 8. Every digit is legal octal, so there is nothing for the compiler to object to. If you want a code to print as 010, that is a width and a zero flag in the printf. Lesson 3 of this module is where you get them.
2. Putting a semicolon after a #define.
#define PI 3.14159;
double area = PI * 2.0 * 2.0;
The Playground's GCC 12 says error: invalid type argument of unary '*' (have 'double'), and the line it points at is the one that uses PI, not the one that defined it. The semicolon was swapped in too, so the compiler read 3.14159; * 2.0. A macro is text, and every character of the text goes in.
3. Leaving the parentheses off a macro parameter.
#define SQUARE(x) x * x
printf("%d\n", SQUARE(2 + 3));
No message, and it prints 11. The text swap gives 2 + 3 * 2 + 3, and multiplication goes first. Wrap the whole body and every parameter: #define SQUARE(x) ((x) * (x)). A const cannot have this problem, which is one more reason to prefer it.
4. Reusing a #define name as a variable.
#define MAX_STUDENTS 60
int main(void)
{
int MAX_STUDENTS = 30;
return 0;
}
GCC 12 says error: expected identifier or '(' before numeric constant and adds note: in expansion of macro 'MAX_STUDENTS'. It read int 60 = 30;. A macro has no scope, so it reaches inside your function and replaces the name there too. This is why macro names are conventionally shouted in capitals.
Maria's till, rewritten so that the VAT rate lives in exactly one line. Read a quantity and a unit price in paisa, and print the subtotal, the VAT and the total.
Input. One line with two integers: the quantity, then the unit price in paisa.
Output. Three lines, each a word then an amount as whole taka, a full stop, and two digits of paisa.
Constraints. 1 <= quantity <= 100000, 1 <= unit price <= 10000000, VAT is 15 percent with the fraction of a paisa dropped. No double.
Sample. Input 3 12000 gives Subtotal 360.00, VAT 54.00, Total 414.00.
#include <stdio.h>
int main(void)
{
const long long VAT_PERCENT = 15;
const long long PAISA_PER_TAKA = 100;
const long long PERCENT_WHOLE = 100;
long long quantity = 0;
long long unit_paisa = 0;
scanf("%lld %lld", &quantity, &unit_paisa);
/* Three lines. %02lld is what makes five paisa print as 05. */
return 0;
}
Graded as named-receipt. The two hundreds mean different things, so they are two constants and not one.
Amara's config file writes a permission in base 8 and a colour in base 16. Print both in decimal.
Input. One line with a hexadecimal number then an octal number, with no prefixes.
Output. One line with both in decimal, one space between them.
Constraints. Hex 0 to FFFFFF in either case, octal 0 to 7777777.
Sample. Input 1F 755 gives 31 493.
#include <stdio.h>
int main(void)
{
unsigned int from_hex = 0;
unsigned int from_octal = 0;
scanf("%x %o", &from_hex, &from_octal);
/* One printf. Both boxes already hold ordinary numbers. */
return 0;
}
Graded as base-decoder. Lesson 4 of this module explains the scanf line; here it is written for you.
Zara is cutting circular mats and needs the area and the edge length of each one, from one radius and one named value of pi.
Input. One line with one decimal number, the radius.
Output. Two lines: the area, then the circumference, each to exactly two decimal places.
Constraints. Use const double PI = 3.14159265358979; and write the digits once.
Sample. Input 2.5 gives 19.63 then 15.71.
#include <stdio.h>
int main(void)
{
const double PI = 3.14159265358979;
double radius = 0.0;
scanf("%lf", &radius);
/* Two printf calls, both using PI, neither repeating its digits. */
return 0;
}
Not graded in this module. Then do it a second time with #define PI 3.14159265358979 and check that the output is identical. The difference between the two is about people, not about arithmetic.
Common doubts
Why are constant names written in capitals?
Habit, and a useful one. It started with macros, where a name reaching into the wrong place is a real hazard. It spread to
constbecause a reader then knows at a glance that the value never moves.Does a
constcost memory that a#definedoes not?In principle yes, in practice almost never. Every compiler at any optimisation level puts a small
conststraight into the instruction that uses it, exactly as a macro would.Is
3.14fless accurate than3.14?Yes. The
fsuffix makes it afloat, which carries about 7 digits against adouble's 15. Module 2 lesson 3 showed what that costs, and the answer there was to usedoubleunless you have a reason.Can I write
conston something I do change later?No, and that is the point of it. The compiler refuses the assignment, which is the next lesson.
Are
#defineandconstthe only ways to name a value?No.
enumis the third, and it is the best of the three for a set of related whole numbers. Module 12 gives it a lesson of its own.
Key takeaways
- Every bare value is a literal, and the way it is written decides its type.
- A suffix says you meant something other than
intordouble. - A leading zero means base 8, silently, and
010is 8. #defineis a text swap done before the compiler, with no type and no scope.constis a typed, scoped variable the compiler will not let you assign to; reach for it first.- A magic number is a value whose meaning nobody can recover, and naming it costs nothing at run time.
Next you find out what the compiler actually refuses once you have written const, and meet the one qualifier that asks it to stop being clever.
End of lesson 1
Mark it done, and your progress moves with you.
Next: const and volatile: Promises You Make to the Compiler