Understanding the C++ Keyword ‘const’

1. Concept

The keyword const in C++ is used to define read-only variables or to restrict the modification behavior of objects/functions. Its core function is to protect data from being accidentally modified while enhancing code readability and safety. const can be applied to variables, pointers, function parameters, function return values, class members, and more.

2. Defining Constants (const Variables)

When const modifies a regular variable, that variable becomes a read-only constant, which must be initialized at the time of definition and cannot be modified afterward.

Syntax:

const type variable_name = initial_value; // or type const variable_name = initial_value;  // equivalent (the former is recommended for clarity)

Example:

#include <iostream>using namespace std;int main() {    const int MAX_NUM = 100;  // Define a constant, cannot be modified    // MAX_NUM = 200;  // Error: const variable cannot be assigned a new value    cout << MAX_NUM << endl;  // Output: 100    const double PI = 3.1415926;    cout << PI << endl;  // Output: 3.1415926    return 0;}

Note:

  • const variables have type checking, making them safer than macro definitions (#define), which are simple text replacements without type checking);

  • Local const variables are stored on the stack, while global const variables are stored in the read-only data segment by default.

3. Modifying Pointers (const Pointers)

When const modifies a pointer, it is essential to distinguish between pointers to constants and constant pointers, primarily based on the position of const:

1. Pointer to a Constant (const modifies the pointed content)

The content pointed to by the pointer cannot be modified, but the pointer itself can point to other addresses.

Syntax:

const type* pointer_name; // or type const* pointer_name;  // equivalent

Example:

int main() {    int a = 10;    const int* p = &a;  // p points to content that cannot be modified    // *p = 20;  // Error: cannot modify the pointed constant content    a = 20;  // Allowed (a itself is not const, only p restricts modification through it)    cout << *p << endl;  // Output: 20    int b = 30;    p = &b;  // Allowed: pointer itself can point to another address    cout << *p << endl;  // Output: 30    return 0;}

2. Constant Pointer (const modifies the pointer itself)

The address of the pointer itself cannot be modified (the pointer points to a fixed address), but the content pointed to can be modified.

Syntax:

type* const pointer_name = initial_address;

Example:

int main() {    int a = 10;    int* const p = &a;  // Pointer p itself is constant, pointing to a fixed address    *p = 20;  // Allowed: modify the pointed content    cout << *p << endl;  // Output: 20    // int b = 30;    // p = &b;  // Error: constant pointer cannot point to another address    return 0;}

3. Pointer to a Constant Constant (Double const)

Neither the address of the pointer itself nor the content pointed to can be modified.

Syntax:

const type* const pointer_name = initial_address;

Example::

int main() {    int a = 10;    const int* const p = &a;  // Both the pointer and the pointed content are immutable    // *p = 20;  // Error: content cannot be modified    // p = &b;   // Error: pointer address cannot be modified    cout << *p << endl;  // Output: 10    return 0;}

4. Modifying Function Parameters

When const modifies function parameters, it indicates that the function will not modify the value of that parameter, commonly used to protect the passed arguments (especially reference/pointer parameters).

Scenario 1: Regular Parameters (Pass by Value)

When passing by value, the parameter is copied, and the effect of const is limited (only restricts modifications to the copy within the function), generally not recommended.

Example:

void print(const int num) {    // num = 100;  // Error: const parameter cannot be modified    cout << num << endl;}int main() {    print(50);  // Output: 50    return 0;}

Scenario 2: Reference/Pointer Parameters (Avoid Copy + Protect Actual Arguments)

Adding const to reference or pointer parameters can prevent the function from accidentally modifying the actual arguments while avoiding copy overhead (suitable for large objects).

Example:

#include <string>using namespace std;// const reference parameter: protects the actual argument from being modified, and avoids copyingvoid printString(const string& str) {    // str = "hello";  // Error: const reference cannot be modified    cout << str << endl;}// const pointer parameter: protects the pointed content from being modifiedvoid printArray(const int* arr, int size) {    for (int i = 0; i < size; ++i) {        // arr[i] = 0;  // Error: const pointer points to content that cannot be modified        cout << arr[i] << " ";    }}int main() {    string s = "C++ const";    printString(s);  // Output: C++ const    int arr[] = {1, 2, 3};    printArray(arr, 3);  // Output: 1 2 3    return 0;}

5. Modifying Function Return Values

<span><span>const</span></span> modifies function return values, restricting the return value from being modified, commonly used in scenarios returning pointers/references.

Example:

// Returning const reference: prohibits modification of the returned objectconst int& getMax(const int& a, const int& b) {    return (a > b) ? a : b;}int main() {    int x = 10, y = 20;    const int& max_val = getMax(x, y);    // max_val = 30;  // Error: return value is const reference, cannot be modified    cout << max_val << endl;  // Output: 20    return 0;}

Note:

  • When the return value is a value type (like const int), const has no practical significance (as the return value is a temporary copy, modifying it does not affect the original data);

  • When returning pointers/references, adding const can prevent the caller from modifying the original data through the return value.

6. Modifying Class Members

const can modify class member variables and member functions to restrict the modification behavior of class objects.

1. const Member Variables

When a class’s member variable is modified by const, it must be initialized in the constructor’s initialization list and cannot be modified afterward.

Example:

class Circle {private:    const double PI;  // const member variable    double radius;public:    // Must initialize const member in the initialization list    Circle(double r) : PI(3.1415926), radius(r) {}    double getArea() {        return PI * radius * radius;    }};int main() {    Circle c(5);    cout << c.getArea() << endl;  // Output: 78.5398    return 0;}

2. const Member Functions

Adding const after a class’s member function indicates that the function will not modify any member variables of the class (except for mutable members) and can only call other const member functions.

Syntax:

return_type function_name(parameters) const;

Example::

class Person {private:    string name;    int age;public:    Person(string n, int a) : name(n), age(a) {}    // const member function: cannot modify member variables    string getName() const {        // name = "Tom";  // Error: const function cannot modify members        return name;    }    // Non-const member function: can modify member variables    void setAge(int a) {        age = a;    }    int getAge() const {  // const member function        return age;    }};int main() {    const Person p("Alice", 20);  // const object    cout << p.getName() << endl;  // Allowed: call const member function    cout << p.getAge() << endl;   // Allowed    // p.setAge(21);  // Error: const object cannot call non-const member function    Person p2("Bob", 25);    p2.setAge(26);  // Allowed: non-const object can call non-const function    return 0;}

Note:

  • const objects can only call const member functions, while non-const objects can call any member function;

  • Mutable member variables can be modified in const member functions (for special scenarios, such as caching, counting).

7. Differences Between const and constexpr

C++11 introduced constexpr for defining constants, but it differs from const:

  • const indicates read-only, and its value can be determined at runtime (like local const variables);

  • constexpr indicates compile-time constants, and its value must be determined at compile time, usable in template parameters, array sizes, and other compile-time contexts.

Example:

void test9() {    // ========== const variable ==========    // const variable is read-only, but its value can be determined at runtime    const int a = 10;  // Although initialized with a literal, a is a local const variable    // Before C++11, local const variables could not be used for array sizes (needed compile-time constant expressions)    // In C++11 and later, if a const variable is initialized with a literal, some compilers (like GCC) allow it to be used as an array size    // But this depends on compiler extensions, not standard C++ behavior, and may cause errors in other compilers (like MSVC)    // int arr[a];  // May compile, but relies on compiler extensions, not recommended    // ========== constexpr variable ==========    // constexpr explicitly indicates compile-time constant, value must be determined at compile time    constexpr int b = 20;  // Compile-time constant, standard C++ allows    int arr2[b];  // Standard C++ allows: constexpr is a compile-time constant expression    // ========== Runtime const vs Compile-time constexpr ==========    int input = 5;    const int c = input;  // Value determined at runtime, cannot use constexpr    // constexpr int d = input;  // Error: input is not a compile-time constant    constexpr int e = 30;  // Compile-time constant    constexpr int f = e * 2;  // Compile-time calculation, also a compile-time constant    int arr3[f];  // Standard allows: f is a compile-time constant expression    // ========== Summary ==========    // const: read-only, value can be determined at runtime    // constexpr: compile-time constant, value must be determined at compile time, usable in scenarios requiring constant expressions    // For array sizes, template parameters, and other scenarios requiring compile-time constants, use constexpr instead of const}

8. Conclusion

The core value of const is **”read-only constraint”**, and its usage in different scenarios can be summarized as:

  • Modifying variables: read-only constants;

  • Modifying pointers: distinguishing between “pointer to constant” and “constant pointer”;

  • Modifying function parameters/return values: protecting data from being modified;

  • Modifying class members: restricting the modification behavior of member variables/functions.

Proper use of const can significantly enhance code safety and readability, representing an important aspect of good C++ programming style.

Leave a Comment