Constants in C++
Constants in C++ are constant values that may not change while a program is running. These values are set at the time the program is created and hold their value throughout its existence. There are several ways to define constants in C++, including literals, enum enumerations, and the keywords const and constexpr.
Ways to define constants
Literals
A literal is a value that is self-evident. Every literal has a type based on its value and form.
The following are examples of typical literal types:
Integer Literals: These can be decimal, octal, or hexadecimal.
- Non-zero decimal numbers include 109 and 987650.
- 077 and 01234567 are octal constants with a leading zero.
- The 0x14 constant is a hexadecimal constant.
- Types can be indicated by L (long) or U (unsigned), for as 42ULL for unsigned long long.
Floating-Point Literals: Always use decimals, either with a point or exponentially (3.14159, 3.14159E0).
- Floating-point literals are double by default.
- For example, 1E-3F for float and 3.14159L for long double are suffixes that indicate float and long double, respectively.
Character Literals: a single character (such as “a” or “%”) surrounded by single quotations.
- Wide character literals are stored in wchar_t and are preceded by L (e.g., L’x’).
String Literals: “Hello World!” is an example of a string of zero or more characters encapsulated in double quotations.
- Every string literal has a null character (‘\0’) appended by the compiler, increasing its real size by one.
- They are of the array of constant characters type.
- String literals, separated by whitespace, can be used to divide long lines into numerous lines.
Boolean Literals: True and false are literals of the bool type.
Pointer Literal: The null pointer, which is used as an incorrect value for any pointer type, is represented by the pointer literal nullptr.
Example
#include <iostream> // For input/output operations (std::cout, std::endl)
#include <string> // For std::string (though string literals are char arrays)
#include <iomanip> // For std::fixed and std::setprecision for floating-point output
int main() {
std::cout << "--- Integer Literals ---" << std::endl;
// Decimal Integer Literals
int decimal_int = 42;
long long large_decimal_int = 1234567890123LL; // 'LL' suffix for long long
unsigned int unsigned_int = 100U; // 'U' suffix for unsigned
std::cout << "Decimal (int): " << decimal_int << std::endl;
std::cout << "Large Decimal (long long): " << large_decimal_int << std::endl;
std::cout << "Unsigned Decimal (unsigned int): " << unsigned_int << std::endl;
// Octal Integer Literal (prefix 0)
int octal_int = 012; // Represents decimal 10 (1*8^1 + 2*8^0)
std::cout << "Octal (012, decimal 10): " << octal_int << std::endl;
// Hexadecimal Integer Literals (prefix 0x or 0X)
int hex_int_A = 0xA; // Represents decimal 10
int hex_int_FF = 0xFF; // Represents decimal 255
std::cout << "Hex (0xA, decimal 10): " << hex_int_A << std::endl;
std::cout << "Hex (0xFF, decimal 255): " << hex_int_FF << std::endl;
// Binary Integer Literal (prefix 0b or 0B, C++14 onwards)
int binary_int = 0b1010; // Represents decimal 10 (1*2^3 + 0*2^2 + 1*2^1 + 0*2^0)
std::cout << "Binary (0b1010, decimal 10): " << binary_int << std::endl;
std::cout << "\n--- Floating-Point Literals ---" << std::endl;
// Double (default for floating-point literals)
double pi_double = 3.14159;
double scientific_double = 1.23e5; // 1.23 * 10^5 = 123000.0
// Float (suffix F or f)
float half_float = 0.5f;
// Long Double (suffix L or l)
long double precise_pi_long_double = 3.141592653589793238L;
// Use std::fixed and std::setprecision for clearer floating-point output
std::cout << std::fixed << std::setprecision(10);
std::cout << "Double Pi: " << pi_double << std::endl;
std::cout << "Double Scientific (1.23e5): " << scientific_double << std::endl;
std::cout << "Float Half: " << half_float << std::endl;
std::cout << "Long Double Precise Pi: " << precise_pi_long_double << std::endl;
std::cout << std::noboolalpha; // Reset precision for subsequent output
std::cout << "\n--- Character Literals ---" << std::endl;
char simple_char = 'X';
char newline_char = '\n'; // Newline escape sequence
char tab_char = '\t'; // Tab escape sequence
char backslash_char = '\\'; // Backslash escape sequence
char single_quote_char = '\''; // Single quote escape sequence
// Wide Character Literal (L prefix)
wchar_t euro_symbol = L'€';
// C++11 Unicode Character Literals
char16_t utf16_char = u'é'; // UTF-16 character
char32_t utf32_char = U'😎'; // UTF-32 character (an emoji!)
char utf8_char = 'g'; // UTF-8 character (type char)
std::cout << "Simple Character: " << simple_char << std::endl;
std::cout << "Newline Character (produces newline):" << newline_char;
std::cout << "Tab Character (produces tab):\t" << tab_char << "END" << std::endl;
std::cout << "Backslash Character: " << backslash_char << std::endl;
std::cout << "Single Quote Character: " << single_quote_char << std::endl;
// Printing wide/Unicode characters directly might require specific console/locale settings
std::wcout << L"Wide Character (Euro): " << euro_symbol << std::endl;
// For char16_t/char32_t, direct cout is not supported without conversion
// std::cout << "UTF-16 char: " << static_cast<int>(utf16_char) << std::endl; // Shows integer value
// std::cout << "UTF-32 char: " << static_cast<int>(utf32_char) << std::endl; // Shows integer value
std::cout << "UTF-8 char: " << utf8_char << std::endl; // Should print correctly if console supports UTF-8
std::cout << "\n--- String Literals ---" << std::endl;
// Basic String Literal (C-style string)
const char* greeting_cstr = "Hello, C++ Literals!";
std::string greeting_stdstr = "Modern C++ uses std::string.";
// String Literal with Escape Sequences
const char* path_with_escapes = "C:\\Users\\Public\\Document.txt";
const char* dialogue_quote = "He said, \"Literals are fun!\"";
// Raw String Literal (C++11 onwards, R"del(string content)del")
const char* raw_path = R"(C:\Users\Public\Another Document.txt)";
const char* json_snippet = R"({"name": "Alice", "age": 30})"; // No need to escape "
// Wide String Literal (L prefix)
const wchar_t* wide_string_greeting = L"Привет, мир!"; // Russian "Hello, world!"
// C++11 Unicode String Literals
const char16_t* utf16_japanese = u"こんにちは"; // Japanese "Hello" (UTF-16)
const char32_t* utf32_emojis = U"🌍🚀"; // Emojis (UTF-32)
const char* utf8_chinese = u8"你好"; // Chinese "Hello" (UTF-8)
std::cout << "C-style string: " << greeting_cstr << std::endl;
std::cout << "std::string: " << greeting_stdstr << std::endl;
std::cout << "Path with escapes: " << path_with_escapes << std::endl;
std::cout << "Dialogue quote: " << dialogue_quote << std::endl;
std::cout << "Raw path: " << raw_path << std::endl;
std::cout << "Raw JSON snippet: " << json_snippet << std::endl;
std::wcout << L"Wide string: " << wide_string_greeting << std::endl;
std::cout << "UTF-8 string: " << utf8_chinese << std::endl; // Should print correctly if console supports UTF-8
std::cout << "\n--- Boolean Literals ---" << std::endl;
bool is_active = true;
bool has_errors = false;
std::cout << "Is Active: " << is_active << std::endl; // Prints 1
std::cout << "Has Errors: " << has_errors << std::endl; // Prints 0
// To print 'true'/'false' instead of 1/0
std::cout << std::boolalpha;
std::cout << "Is Active (boolalpha): " << is_active << std::endl;
std::cout << "Has Errors (boolalpha): " << has_errors << std::endl;
std::cout << std::noboolalpha; // Reset output format
std::cout << "\n--- Pointer Literal ---" << std::endl;
int* my_ptr = nullptr; // A null pointer
if (my_ptr == nullptr) {
std::cout << "my_ptr is a null pointer (nullptr)." << std::endl;
}
return 0;
}
Output
--- Integer Literals ---
Decimal (int): 42
Large Decimal (long long): 1234567890123
Unsigned Decimal (unsigned int): 100
Octal (012, decimal 10): 10
Hex (0xA, decimal 10): 10
Hex (0xFF, decimal 255): 255
Binary (0b1010, decimal 10): 10
--- Floating-Point Literals ---
Double Pi: 3.1415900000
Double Scientific (1.23e5): 123000.0000000000
Float Half: 0.5000000000
Long Double Precise Pi: 3.1415926536
--- Character Literals ---
Simple Character: X
Newline Character (produces newline):
Tab Character (produces tab): END
Backslash Character: \
Single Quote Character: '
Wide Character (Euro): �
UTF-8 char: g
--- String Literals ---
C-style string: Hello, C++ Literals!
std::string: Modern C++ uses std::string.
Path with escapes: C:\Users\Public\Document.txt
Dialogue quote: He said, "Literals are fun!"
Raw path: C:\Users\Public\Another Document.txt
Raw JSON snippet: {"name": "Alice", "age": 30}
Wide string: @825B, <8@!
UTF-8 string: 你好
--- Boolean Literals ---
Is Active: 1
Has Errors: 0
Is Active (boolalpha): true
Has Errors (boolalpha): false
--- Pointer Literal ---
my_ptr is a null pointer (nullptr).
The const keyword
When defining objects (variables) that might not be altered once initialized, the const keyword is utilized as a type qualifier. A compiler error will appear if you try to change a const object’s value after it has been created.
Crucial elements of const:
Initialization: Since the value of a const object cannot be altered after it is defined, it must be initialized at the time of definition.
Advantages: Using const improves optimization, helps the compiler detect unintentional changes at compile time, and clarifies the purpose of the code for other programmers. Its main purpose is to define interfaces, which show that information may be sent to functions without worrying about being changed.
Scope (Local to File by Default): Const objects are by default local to the file in which they are specified when initialized using a compile-time constant. A const object needs to be specified as extern in order to be shared across several files. In contrast to the #define preprocessor directive, which replaces text, the C++ compiler checks the type of const variables.
const with Pointers: To change either the pointer or the object it points to, use the const keyword.
- Pointer to const: Const before the asterisk (*) indicates that while the pointer itself can be reassigned to point to another location, the data it points to cannot be altered.
- const Pointer: Const following the asterisk (*) indicates that the data it points to can be changed (if the data is not const), but the pointer itself cannot be reallocated to go to a different address.
- Both the pointer and the data it points to are immutable when they are const pointers to const objects.
const with References (Reference to const): When a reference to const is used, it basically becomes read-only because it cannot be used to modify the object to which it is tied.
This is frequently used for function arguments to ensure that they are not altered while avoiding copying huge objects.
const Member Functions: A const member function ensures that it will never change any of the data members of its class. Because only const methods can be invoked on objects supplied via const reference, this is crucial.
const Data Members: To guarantee that data members of a class maintain their initial values during the course of the object’s existence, they can be declared const. They need to be initialised in the initializer list of the constructor.
Example
#include <iostream>
const double PI = 3.14159; // Global constant
const int MAX_USERS = 100; // Global constant
class Circle {
public:
double radius;
// const member variable (must be initialized in constructor initializer list)
const double AREA_FACTOR;
Circle(double r) : radius(r), AREA_FACTOR(PI) {} // Initialize AREA_FACTOR
double calculateArea() const { // const member function
return AREA_FACTOR * radius * radius;
}
};
int main() {
const int AGE = 30; // Local constant
// AGE = 31; // Error: cannot modify a const variable
std::cout << "PI: " << PI << std::endl;
std::cout << "Max Users: " << MAX_USERS << std::endl;
std::cout << "Age: " << AGE << std::endl;
Circle c(5.0);
std::cout << "Circle Area: " << c.calculateArea() << std::endl;
return 0;
}
Output
PI: 3.14159
Max Users: 100
Age: 30
Circle Area: 78.5397
The constexpr keyword
Variables and functions that can be evaluated at build time are declared using the constexpr keyword.
Implicitly const: Implicitly, variables that are declared as constexpr are const.
Compile-time Evaluation: Constexpr’s main function is to enable and guarantee compile-time evaluation, which is helpful for case labels, array sizes, and performance enhancement.
Types of Literality: A constexpr function must have a literal type for both its return type and each of its parameters. Literal types include arithmetic, reference, and pointer types. If all of the data members are of literal types and the user-defined types have constexpr constructors, they can also be literal types.
Functions of the constexpr: A constexpr function should have no side effects and only one return statement so that the compiler can evaluate it at compile time.
Example
#include <iostream>
// Compile-time constant
constexpr int ARRAY_SIZE = 10;
// Function that can be evaluated at compile time if arguments are compile-time constants
constexpr int multiply(int x, int y) {
return x * y;
}
int main() {
int arr[ARRAY_SIZE]; // Array size determined at compile time
constexpr int result = multiply(2, 5); // Evaluated at compile time
std::cout << "Array Size: " << ARRAY_SIZE << std::endl;
std::cout << "Compile-time multiplication result: " << result << std::endl;
int num1 = 3;
int num2 = 4;
int runtime_result = multiply(num1, num2); // Evaluated at runtime
std::cout << "Runtime multiplication result: " << runtime_result << std::endl;
return 0;
}
Output
Array Size: 10
Compile-time multiplication result: 10
Runtime multiplication result: 12
Enumerations ()
Sets of integral constants can be grouped together using enumerations. A new type is defined by each enumeration.
Defining Constants: The enum keyword can be used to specify a collection of integer constants.
- Each enumerator is a constant expression since they are const and their initialisers need to be constant expressions.
Scoped vs. Unscoped Enumerations
- Unscoped enumerations (enum) make their enumerators are accessible within the enclosing scope.
- Introduced in C++11, scoped enumerations are defined using an enum class (or enum struct), and their enumerators are local to the scope of that class or enum to avoid name collisions.
Example
#include <iostream>
// C-style enum
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
// C++11 scoped enum class
enum class State {
OFF,
ON,
PENDING
};
int main() {
Color favoriteColor = GREEN;
std::cout << "Favorite Color (C-style enum): " << favoriteColor << std::endl; // Outputs 1
State currentState = State::ON;
// int s = currentState; // Error: cannot implicitly convert enum class to int
int s = static_cast<int>(currentState); // Explicit conversion needed
std::cout << "Current State (enum class): " << s << std::endl; // Outputs 1
if (currentState == State::ON) {
std::cout << "The state is ON." << std::endl;
}
return 0;
}
Output
Favorite Color (C-style enum): 1
Current State (enum class): 1
The state is ON.
Comparison with #define: In contrast to #define, which only replaces text without providing type information, enum constants are a component of a declaration and have a valid range, allowing for class-specific or namespace-specific constants.
In conclusion, C++ offers a number of constant definition procedures, each with unique benefits and applications. Values are directly represented by literals. The const keyword offers type safety and compiler optimisation by guaranteeing that variables cannot be changed after initialisation. Strong compile-time computations are made possible by the constexpr keyword, which ensures compile-time evaluation for expressions and functions. A systematic method of defining sets of named integral constants is using enumerations; scoped enumerations are preferred in current C++ for improved organisation and to prevent name clashes.