Constants and Type Conversion
Constants are like the value of pi in math — once set, they never change. Type conversion is like currency exchange: different currencies have fixed rates, but watch out for precision loss when converting.
Literals
A literal is a fixed value written directly in your code. It doesn't need to be computed — you can see its value at a glance.
Integer Literals
int a = 42;
int b = 0xFF;
int c = 052;
int d = 0b101010;
| Base | Prefix | Example | Decimal Value |
|---|---|---|---|
| Decimal | None | 42 |
42 |
| Hexadecimal | 0x or 0X |
0xFF |
255 |
| Octal | 0 |
052 |
42 |
| Binary (C99 GNU extension / C23 standard) | 0b or 0B |
0b101010 |
42 |
int x = 010; equals 8, not 10 — a classic beginner bug.
Integer literals default to int. If the value exceeds the int range, it automatically becomes long or long long. You can also specify the type manually:
| Suffix | Meaning | Example |
|---|---|---|
u or U |
unsigned | 42U |
l or L |
long | 42L |
ll or LL |
long long | 42LL |
UL |
unsigned long | 42UL |
L instead of lowercase l, since l is easily confused with the digit 1. 42l looks like 421.
Floating-Point Literals
double a = 3.14;
double b = 2.0e8;
double c = 1.5E-3;
float d = 3.14f;
Floating-point literals default to double. Add the f or F suffix for float, or l/L for long double.
float f = 3.14;
3.14 is a double literal. Assigning it to a float variable causes implicit truncation, and the compiler may warn. The correct way:
float f = 3.14f;
Character Literals
A single character enclosed in single quotes:
char ch = 'A';
char newline = '\n';
char zero = '\0';
The type of a character literal in C is int (not char) — a subtle detail that's easy to overlook:
printf("%zu\n", sizeof('A'));
4
In C, sizeof('A') is 4 (the size of int), while in C++ it's 1 (the size of char). This is a subtle difference between C and C++.
String Literals
A sequence of characters enclosed in double quotes:
printf("Hello, World!\n");
String literals have type char[]. The compiler automatically appends a \0 as a terminator, so "abc" actually occupies 4 bytes (a, b, c, \0).
char *s = "hello";
s[0] = 'H';
The const Modifier
const is a C keyword that makes a variable "read-only" — once initialized, it cannot be modified:
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';
Attempting to modify a const variable causes a compilation error:
const int MAX_SIZE = 100;
MAX_SIZE = 200;
error: assignment of read-only variable 'MAX_SIZE'
const and Arrays
Can a const variable be used to specify an array size?
const int SIZE = 10;
int arr[SIZE];
const int in C is not a compile-time constant (it's merely a runtime read-only variable). C99 introduced variable-length arrays (VLAs), so the code above compiles under C99, but VLAs are an optional feature not supported by all compilers.
If you need a true compile-time constant for an array size, use #define or enum.
const and Pointers
The combination of const and pointers is a classic C challenge. There are three cases:
int value = 42;
const int *p1 = &value;
int * const p2 = &value;
const int * const p3 = &value;
| Declaration | Meaning |
|---|---|
const int *p |
The data pointed to cannot be modified; the pointer itself can point elsewhere |
int * const p |
The pointer itself cannot be modified (fixed address); the data it points to can be changed |
const int * const p |
Neither the pointer nor the data it points to can be modified |
* the const is on. If it's on the left, it modifies the data. If it's on the right, it modifies the pointer.
#define Macro Constants
#define is a preprocessor directive used to define macro constants:
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'
The preprocessor replaces all occurrences of PI with 3.14159 before compilation. It's not a variable — it occupies no memory and has no type checking.
#define vs. const
| Feature | #define |
const |
|---|---|---|
| When processed | Preprocessing stage (text substitution) | Compilation stage |
| Type checking | None | Yes |
| Scope | From definition to end of file | Follows variable scope rules |
| Debugging | Debugger can't see the macro name | Debugger can see the const variable |
| Addressable | No | Yes |
const — it has type checking and scope restrictions, making it safer. Use #define for conditional compilation flags or when you need compile-time constants.
Common Macro Pitfalls
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
You might expect 3 + 3 * 2 = 12, but the macro expands to 3 + 3 * 2 = 9. Macros are pure text substitution — they don't respect operator precedence.
Fix: Add parentheses to the macro:
#define DOUBLE(x) ((x) + (x))
Enum Constants
enum is another way to define integer constants:
enum Color {
RED,
GREEN,
BLUE
};
By default, values start at 0 and increment: RED = 0, GREEN = 1, BLUE = 2. You can also assign values manually:
enum Weekday {
MON = 1,
TUE,
WED,
THU,
FRI,
SAT = 100,
SUN
};
TUE = 2, WED = 3, ..., SUN = 101 (each subsequent value is one more than the previous).
Enum values are true compile-time constants, usable for array sizes and similar scenarios. Compared to #define, enums carry type information and are visible by name in the debugger — prefer them when possible.
Implicit Type Conversion
When different types are mixed in an expression, C automatically performs type conversion. Understanding the implicit conversion rules is key to avoiding bugs.
Integer Promotion
In expressions, char and short are automatically promoted to int before any operation:
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
4
The result of a + b is int (4 bytes), not char (1 byte).
Arithmetic Conversion
When values of different types are combined in an operation, the lower type is automatically converted to the higher type. The conversion hierarchy from low to high:
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double
The rule: before an operation, the operand of the lower rank is automatically converted to the higher rank.
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
2.5
a is automatically converted to double, then floating-point division is performed, yielding 2.5.
Assignment Conversion
During assignment, the right-hand value is automatically converted to the left-hand type:
int x = 3.14;
double y = 42;
3.14 assigned to int is truncated to 3; 42 assigned to double becomes 42.0.
int n = 2.9;
n is 2, not 3.
The Integer Division Trap
This is the most common pitfall for beginners:
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
2.0
You expect 2.5, but get 2.0. The reason: a / b is integer division, yielding 2 (an integer), which is then converted to the double value 2.0.
Fix: Make at least one operand a floating-point number:
double result = (double)a / b;
Or:
double result = a * 1.0 / b;
Or:
double result = 5.0 / 2;
Explicit Type Casting
When implicit conversion doesn't do what you need, you can use an explicit cast to tell the compiler exactly what type you want:
Syntax
(type_name)expression
Example
#include <stdio.h>
int main(void) {
int a = 7;
int b = 2;
printf("Integer division: %d\n", a / b);
printf("With cast: %.2f\n", (double)a / b);
return 0;
}
Integer division: 3
With cast: 3.50
(double)a first converts a to 7.0. Then, when it's combined with b, b is also implicitly promoted to double, and floating-point division is performed.
Example
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 3.7;
printf("Direct truncation: %d\n", (int)x);
printf("Rounding: %d\n", (int)(x + 0.5));
printf("round() function: %.0f\n", round(x));
return 0;
}
Direct truncation: 3
Rounding: 4
round() function: 4
(int)(-3.7) results in -3, not -4. This differs from "floor" (rounding toward negative infinity), so be careful.
Common Type Conversion Pitfalls
Mixing Signed and Unsigned
int a = -5;
unsigned int b = 10;
if (a + b > 0) {
printf("Greater than 0\n");
} else {
printf("Less than or equal to 0\n");
}
Greater than 0
You might think -5 + 10 = 5 > 0 is correct, but this code's behavior depends on implicit conversion rules. When int and unsigned int are mixed in an operation, the int is converted to unsigned int. -5 becomes a very large positive number when interpreted as unsigned, so the result is indeed greater than 0 — but this probably isn't the logic you intended.
Precision Loss
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
123456789
123456792
float only has 6-7 significant digits and can't represent a 9-digit integer precisely — the last 3 digits are lost. Be very careful when assigning large integers to float.
Overflow Conversion
char ch = 200;
printf("%d\n", ch);
On platforms where char is signed, 200 exceeds char's range (-128 to 127), resulting in undefined behavior — possibly outputting -56. If you need to store values from 0 to 255, use unsigned char.
Pointer Conversion
Casting between pointers of different types is dangerous:
int value = 0x41424344;
char *p = (char *)&value;
printf("%c\n", *p);
The output depends on the system's byte order (big-endian or little-endian) and is not portable. This kind of conversion is only used in low-level programming and should be avoided by beginners.
❓ FAQ
📖 Summary
- Literals come in four kinds: integer, floating-point, character, and string. Watch for suffixes (f/L/UL) and base prefixes (0x/0/0b)
- const makes a variable read-only; prefer const over #define. #define is a preprocessing text substitution with no type checking
- Implicit conversion follows integer promotion and arithmetic conversion rules — lower types automatically convert to higher types
- Integer division is the most common C pitfall: 5/2 = 2; to get 2.5, at least one operand must be floating-point
- Mixing signed and unsigned integers produces unexpected results — avoid it or use explicit casts
📝 Exercises
- Write a program that outputs the number 255 in decimal, hexadecimal, and octal, and observe the output formats
- Write a program to calculate the area of a circle (radius as int 10, pi defined as const double), being careful to avoid the integer division trap
- Write a program to test the following and record results: the value of (int)3.9, the value of (int)(-3.9), and the %d output after
char ch = 128. Explain each result



