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

C
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
💡 Tip: Accidentally putting a 0 before a number turns it into octal. 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
⚠️ Note: Use uppercase L instead of lowercase l, since l is easily confused with the digit 1. 42l looks like 421.

Floating-Point Literals

C
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.

🔥 Common Mistake:

C
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:

C
float f = 3.14f;

Character Literals

A single character enclosed in single quotes:

C
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:

C
printf("%zu\n", sizeof('A'));
TEXT
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:

C
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).

⚠️ Note: String literals are stored in read-only memory. Attempting to modify one causes undefined behavior (usually a crash):

C
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:

C
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';

Attempting to modify a const variable causes a compilation error:

C
const int MAX_SIZE = 100;
MAX_SIZE = 200;
TEXT
error: assignment of read-only variable 'MAX_SIZE'

const and Arrays

Can a const variable be used to specify an array size?

C
const int SIZE = 10;
int arr[SIZE];
⚠️ Note: In C89, this is not allowed — array sizes must be compile-time constants, and 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:

C
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
💡 Tip: Look at which side of * 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:

C
#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
💡 Tip: Prefer 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

C
#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:

C
#define DOUBLE(x) ((x) + (x))

Enum Constants

enum is another way to define integer constants:

C
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:

C
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:

C
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
TEXT
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:

TEXT
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.

C
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
TEXT
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:

C
int x = 3.14;
double y = 42;

3.14 assigned to int is truncated to 3; 42 assigned to double becomes 42.0.

⚠️ Note: When a floating-point value is assigned to an integer type, the fractional part is simply discarded (not rounded):

C
int n = 2.9;

n is 2, not 3.

The Integer Division Trap

This is the most common pitfall for beginners:

C
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
TEXT
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:

C
double result = (double)a / b;

Or:

C
double result = a * 1.0 / b;

Or:

C
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

C
(type_name)expression

Example

C
#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;
}
▶ Try it Yourself
TEXT
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

C
#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;
}
▶ Try it Yourself
TEXT
Direct truncation: 3
Rounding: 4
round() function: 4
💡 Tip: C99 specifies that floating-to-integer truncation rounds toward zero. So (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

C
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");
}
TEXT
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.

⚠️ Note: Never mix signed and unsigned integers in operations. If you must, use explicit casts.

Precision Loss

C
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
TEXT
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

C
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:

C
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

Q Should I use #define or const?
A Prefer const. It has type checking, follows scope rules, and is debugger-friendly. #define is pure text substitution with no type safety, but it's a compile-time constant that can be used for array sizes and conditional compilation. If a value must be available at compile time (like an array size), use #define or enum; otherwise, use const.
Q Why does 5/2 equal 2 instead of 2.5?
A Because both 5 and 2 are ints, and C specifies that integer division yields an integer — the fractional part is discarded. To get 2.5, make at least one operand a float: 5.0/2 or (double)5/2. This is one of the most common beginner traps in C.
Q Does casting change the original variable's value?
A No. A cast only produces a temporary converted value — the original variable's value and type remain unchanged. For example, (int)3.7 produces the temporary value 3, but 3.7 is still 3.7.
Q Why is mixing signed and unsigned operations so bug-prone?
A Because the C standard says that when mixed, the signed value is implicitly converted to unsigned. A negative number becomes a very large positive value when unsigned, causing comparisons and calculations to produce completely unintuitive results. Avoid mixing them, or use explicit casts before operating.

📖 Summary

📝 Exercises

  1. Write a program that outputs the number 255 in decimal, hexadecimal, and octal, and observe the output formats
  2. 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
  3. 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
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏