Some values in your program never change: the tax rate, the maximum number of items a cart can hold, the currency symbol on every price tag. C++ gives you two keywords (const and constexpr) for making those values immutable, plus a rich set of literal forms for writing numbers, characters, and strings directly in source code. This lesson covers both.
When a value should not change after you set it, encoding that into the type system saves you from a whole class of bugs. Consider a checkout function that accidentally overwrites the tax rate halfway through computing a total. With a regular variable, the compiler does nothing. With a const, the compiler refuses to build.
The first assignment compiles. The commented-out one would not. That single line of compiler protection is the entire point of const: lock down values that should not move, and let the compiler enforce it for you.
const Variablesconst marks a variable as read-only after initialization. Two rules govern it:
const variable must be initialized at the point of declaration. You cannot declare it now and assign later.Try to break either rule and the compiler complains. What each violation looks like:
The first error happens because const without an initializer leaves the variable with no defined value, and the compiler will never let you "fix" it later. The second error is the whole point: write-protection.
Most codebases write const names in UPPER_SNAKE_CASE to signal "this will not move." It is a convention, not a language rule. The compiler treats MaxItems, maxItems, and MAX_ITEMS identically as long as you are consistent. The Naming Conventions lesson later in this section covers style choices in more detail.
const With Runtime ValuesA const does not have to know its value at compile time. It just has to be set when the variable is created and never changed afterward. This works fine:
Sample Output:
The compiler does not know what LOCKED_STOCK will be when it builds the program. It only knows that whatever value gets assigned during initialization is the value that stays. This is the key difference between const and constexpr, covered next.
constexpr Variablesconstexpr, introduced in C++11, marks a value as known at compile time. The compiler evaluates the expression while building the program, bakes the result into the binary, and treats the name as a literal value everywhere it is used.
So far this looks identical to const. The difference shows up when you need a value at a place where the language requires a compile-time constant.
constexpr Buys You SomethingC++ has a handful of contexts that demand a compile-time integer. The two most common are array sizes and template arguments:
The line double prices[CART_CAPACITY] only compiles because CART_CAPACITY is a constexpr. A plain const int works here too in many cases, but constexpr makes the intent explicit and works reliably across more contexts. If you tried this with a regular int, the compiler would reject it because the size of a built-in array must be fixed at compile time.
std::array (covered in Arrays & Strings) takes its size as a template argument, which also requires a compile-time value:
The compiler stamps out a std::array<double, 3> type while building. To do that, it needs CART_SIZE to be a value it can read at compile time. constexpr is the cleanest way to give it one.
None at runtime. A constexpr value is computed once during compilation and substituted directly into the binary, so there is no lookup or memory access when the program runs.
const vs constexprBoth make a variable read-only. The difference is when the value gets fixed.
| Feature | const | constexpr |
|---|---|---|
| Initialization required | Yes | Yes |
| Value can change after init | No | No |
| Value can be a runtime expression | Yes | No, must be compile-time |
| Usable as array size | Sometimes (integral types only) | Yes |
| Usable as template argument | Sometimes | Yes |
| Introduced in | Original C++ | C++11 |
| Intent signal | "Don't modify this" | "This is known at compile time" |
The rule of thumb: if the value is known at compile time and you would benefit from it being a true compile-time constant (array sizes, template parameters, performance), use constexpr. Otherwise, use const.
A std::string cannot be made constexpr until C++20, because the type's constructor only became constexpr then. For string-like data, prefer const in C++17 code, or use a const char* for compile-time storage:
consteval and constinit (Brief Mention)C++20 adds two more keywords in the same family: consteval (forces compile-time evaluation for functions) and constinit (forces compile-time initialization for variables with static or thread storage duration). They both refine cases where constexpr is too permissive. Be aware they exist; do not reach for them yet.
A literal is a value written directly in source code: 42, 3.14, 'A', "hello", true, nullptr. C++ classifies them into a handful of categories, and each category has its own forms and rules.
The diagram lays out the six families the rest of this lesson walks through. Each family has its own syntax, and most of them have suffixes that let you fine-tune the type.
Integer literals can be written in four different bases. The base is determined by the prefix:
| Form | Prefix | Example | Decimal value |
|---|---|---|---|
| Decimal | none | 42 | 42 |
| Octal | 0 | 052 | 42 |
| Hexadecimal | 0x or 0X | 0x2A | 42 |
| Binary | 0b or 0B | 0b101010 | 42 |
Binary literals were added in C++14. The other three have been in C++ from the start.
All four print as 42 because std::cout always formats integers as decimal by default. The prefix changes how the compiler reads the literal in source code, not how it stores or prints the value.
The leading-zero rule for octal causes confusion. 010 is not 10, it is 8. If you accidentally pad a literal with zeros to line up columns, you have changed its meaning. Stick to decimal unless you specifically want octal or have a reason to use hex or binary.
Without a suffix, an integer literal is int by default. Suffixes promote it to a larger or unsigned type:
| Suffix | Type | Example |
|---|---|---|
u or U | unsigned | 42u |
l or L | long | 42L |
ll or LL | long long | 42LL |
ul, lu, UL, etc. | unsigned long | 42UL |
ull, llu, ULL, etc. | unsigned long long | 42ull |
Output (typical 64-bit Linux):
The actual sizes are implementation-defined, but the relative ordering is fixed: long long is at least as big as long, which is at least as big as int. Suffixes only let you ask for "at least this size."
Order does not matter for combinations. 42ull, 42ULL, 42llu, and 42uLL all mean the same thing. Mixed case is legal but reads poorly, so most teams stick to either all-lowercase or all-uppercase.
Since C++14, you can drop apostrophes between digits to make long literals readable. They are purely visual; the compiler ignores them.
The apostrophe can go between any two digits, in any base. Group them however reads best: threes for decimal, fours for binary, pairs for hex. The compiler does not care, but human readers do.
Floating-point literals have a decimal point, an exponent, or both. All of these are valid:
| Literal | Value |
|---|---|
3.14 | three point one four |
3.14e2 | 314.0 |
3.14E-2 | 0.0314 |
.5 | 0.5 |
3. | 3.0 |
1e10 | 10,000,000,000.0 |
The decimal point or the exponent (the e/E) is what tells the compiler "this is floating-point." Without either one, 3 is an integer.
Without a suffix, a floating-point literal is double. Two suffixes change that:
| Suffix | Type | Example |
|---|---|---|
f or F | float | 3.14f |
l or L | long double | 3.14L |
| (none) | double | 3.14 |
Output (typical 64-bit Linux):
The f suffix matters more than it looks. Without it, 0.07 is a double, and assigning a double to a float triggers a narrowing conversion that some compilers warn about. Writing 0.07f says "I meant a float from the start, no narrowing happening."
A character literal is a single character in single quotes. It has type char and represents one byte (on every system you are likely to use).
The last line is important: '7' is the character "7", not the number 7. Its underlying integer value is the ASCII code for 7, which is 55. The Type Casting lesson covers conversions in detail.
Some characters cannot be typed directly (newline, tab) or would confuse the parser (quote, backslash). Escape sequences solve both problems:
| Escape | Meaning | ASCII |
|---|---|---|
\n | Newline | 10 |
\t | Tab | 9 |
\\ | Backslash | 92 |
\' | Single quote | 39 |
\" | Double quote | 34 |
\0 | Null character | 0 |
\r | Carriage return | 13 |
\b | Backspace | 8 |
\xHH | Hex byte value (e.g., \x41 = 'A') | varies |
\NNN | Octal byte value (e.g., \101 = 'A') | varies |
The null character '\0' is special: it is how C-style strings mark their end. The C-Strings lesson in Arrays & Strings goes into detail. Treat '\0' as "byte value zero," distinct from the digit character '0' (whose value is 48).
A string literal is a sequence of characters in double quotes. Its type is "array of const char" with one extra byte at the end for the null terminator '\0'.
The escape sequences from the character section all work inside strings too:
Two string literals written next to each other (separated only by whitespace, including newlines) are concatenated by the compiler into a single literal. This is purely a compile-time feature and happens before the program runs.
This works because the three quoted pieces are all string literals sitting next to each other, and the compiler glues them. There is no + between them. Trying to add a + would not work for plain string literals (they are not std::string objects yet at that stage).
This is useful for long messages that would otherwise need a very wide line. It also lets you mix regular and raw string literals (covered next).
Raw string literals start with R"( and end with )". Inside them, escape sequences are not interpreted. Backslashes are just backslashes.
Raw strings are useful when the text contains lots of backslashes (Windows file paths, regular expressions) or lots of quotes (JSON, HTML, embedded SQL).
What if the string itself needs to contain )"? Use a custom delimiter between the R" and the (:
The delimiter between R" and ( can be any short identifier (up to 16 characters, no spaces). The same identifier must reappear in )delim". The compiler scans for ) followed by your delimiter followed by " to find the end.
Raw strings also preserve line breaks literally, so multi-line strings stay multi-line without \n:
String literal concatenation and raw string parsing happen at compile time. The resulting string sits in read-only memory just like any other string literal.
Two values: true and false. They are keywords, not just names defined somewhere, so you do not need to include any header to use them.
By default, std::cout prints booleans as 1 and 0. Stream the manipulator std::boolalpha once, and it switches to the words true and false for the rest of that stream's lifetime. Stream Manipulators in the File I/O section covers this and related toggles.
nullptrnullptr is the literal used to represent a null pointer, introduced in C++11. It replaces the older NULL macro (which expanded to 0 or ((void*)0) depending on the platform) and the bare 0. Both predecessors had subtle problems with overload resolution and template argument deduction. nullptr has its own type (std::nullptr_t) and converts cleanly to any pointer type.
Treat nullptr as the right way to say "this pointer points to nothing."
C++11 lets you define your own literal suffixes. For example, you can write 42_kg and have it construct a Mass object. Library code uses this for things like std::chrono::seconds:
You can write your own user-defined literals too, but the rules and corner cases are large enough to fill another lesson. For everyday code, the standard library's pre-built suffixes (chrono, complex numbers, string view) cover almost every reasonable use.
A small e-commerce snippet that ties the lesson's material into one program:
Every constant, every literal form, every escape rule from earlier in the lesson shows up here. Reading this program once you understand the pieces is a good test of whether the material has settled.
10 quizzes