Что означает ключевое слово const в C++?

Ответ

Ключевое слово const в C++ указывает на неизменяемость (константность) объекта или выражения после его инициализации. Это обещание компилятору и другим программистам, что значение не будет изменено, что повышает безопасность, читаемость и позволяет проводить некоторые оптимизации. const является частью системы типов и проверяется на этапе компиляции.

Основные контексты использования:

  1. Константные переменные и объекты:

    const int MAX_BUFFER_SIZE = 1024;
    const std::string GREETING = "Hello";
    // MAX_BUFFER_SIZE = 2048; // Ошибка компиляции: присваивание константе
  2. Указатели и const: Здесь важно различать указатель на константу и константный указатель.

    int value = 10;
    int other = 20;
    
    // 1. Указатель на константное целое (данные нельзя менять через этот указатель)
    const int* ptr_to_const = &value;
    // *ptr_to_const = 30; // Ошибка: данные константны
    ptr_to_const = &other; // OK: сам указатель можно перенаправить
    
    // 2. Константный указатель (указатель нельзя перенаправить)
    int* const const_ptr = &value;
    *const_ptr = 30; // OK: данные можно менять
    // const_ptr = &other; // Ошибка: указатель константен
    
    // 3. Константный указатель на константные данные
    const int* const const_ptr_to_const = &value;
    // *const_ptr_to_const = 30; // Ошибка
    // const_ptr_to_const = &other; // Ошибка
  3. Константные методы класса: Метод, объявленный с const в конце сигнатуры, гарантирует, что он не будет изменять нестатические поля объекта (за исключением полей, объявленных как mutable). Константные объекты могут вызывать только константные методы.

    class DataContainer {
        std::vector<int> data;
        mutable int accessCounter = 0; // Можно менять даже в const-методах
    public:
        // Не-константный метод: может изменять объект
        void add(int value) { data.push_back(value); }
    
        // Константный метод: не может изменять объект (кроме mutable полей)
        int getSize() const {
            ++accessCounter; // Разрешено, так как accessCounter - mutable
            return data.size();
        }
        // int getLast() { return data.back(); } // Нельзя вызвать для const DataContainer
        int getLast() const { return data.back(); } // Можно вызвать для const DataContainer
    };
    
    int main() {
        DataContainer dc;
        dc.add(5);
        std::cout << dc.getSize(); // OK
    
        const DataContainer& const_ref = dc;
        // const_ref.add(10); // Ошибка: add() не является const-методом
        std::cout << const_ref.getSize(); // OK: getSize() - const
    }
  4. Константные параметры функций и возвращаемые значения: Гарантируют, что функция не изменит переданный аргумент или что возвращаемое значение нельзя изменить.

    void printVector(const std::vector<int>& vec) { // Безопасная передача по ссылке
        for (int val : vec) std::cout << val << ' ';
        // vec.push_back(10); // Ошибка компиляции
    }
    
    const int* find(const int* arr, int size, int target) {
        // Возвращаем указатель на константу, чтобы caller не мог изменить найденный элемент
        for (int i = 0; i < size; ++i) if (arr[i] == target) return &arr[i];
        return nullptr;
    }

    Использование const — это хорошая практика, которая делает контракты функций явными и предотвращает множество ошибок.

Ответ 18+ 🔞

А, бля, const! Ну это ж классика, ебать мои старые костыли. Сидишь такой, пишешь код, а потом бац — тебе говорят: «А сделай тут так, чтобы эту хуйню нельзя было поменять». И ты такой: «А, ну понятно, const». Но не всё так просто, ёпта.

Представь себе, ты объявляешь переменную и говоришь компилятору: «Смотри, брат, это вот значение — святое. Тронешь — я тебе руки поотбиваю». Компилятор тебе верит и начинает следить, как сторож у склада. Попробуешь изменить — сразу ошибку в морду: «Не-не-не, дружок, ты же обещал!».

const int MAX_BUFFER_SIZE = 1024;
const std::string GREETING = "Hello";
// MAX_BUFFER_SIZE = 2048; // Компилятор: «Иди ты нахуй, я тебе верюл что-ли?»

А вот с указателями начинается ёперный театр. Тут надо мозги включать, а не просто так, с бухты-барахты. Есть разница — ты не можешь менять данные, на которые смотришь, или ты не можешь перенаправить свой взгляд на другие данные. Или и то, и другое сразу, хуй с горы.

int value = 10;
int other = 20;

// 1. Смотришь на константу. Данные трогать низя, но смотреть в другую сторону — пожалуйста.
const int* ptr_to_const = &value;
// *ptr_to_const = 30; // Компилятор: «Руки прочь, пидарас шерстяной!»
ptr_to_const = &other; // А вот тут окей, посмотрел на другого.

// 2. Сам указатель прикован цепью. Куда посмотрел — там и сидишь. Но данные под тобой менять можно.
int* const const_ptr = &value;
*const_ptr = 30; // Окей, поменял то, на что смотрю.
// const_ptr = &other; // Компилятор: «Куда собрался, распиздяй? Сиди на месте!»

// 3. Полный пиздец. И смотреть никуда не двигаешься, и данные трогать нельзя. Сидишь в углу и молчишь.
const int* const const_ptr_to_const = &value;
// *const_ptr_to_const = 30; // Не-а.
// const_ptr_to_const = &other; // Тоже не-а.

Дальше — классы. Тут const вообще становится хитрой жопой. Ты объявляешь метод с const в конце — это как клятва Гиппократа: «Не навреди объекту». Ты говоришь компилятору: «Бля, я этот объект не трону, честно-честно». И компилятор тебе верит. А потом ты пытаешься вызвать этот метод у константного объекта, и всё работает. А если метод без const — получаешь по шапке. Доверия ебать ноль, сразу проверяет.

Но есть же эти ваши mutable поля. Это такая лазейка, задняя дверь. Типа: «Да-да, объект я не трону... кроме вот этой маленькой счётчиковой переменненькой, её можно?». И компилятор такой: «Ну ладно уж, раз mutable».

class DataContainer {
    std::vector<int> data;
    mutable int accessCounter = 0; // Её можно ебашить даже в const-методах, она же mutable!
public:
    // Обычный метод — делает что хочет.
    void add(int value) { data.push_back(value); }

    // Const-метод — клянётся, что объект не испоганит.
    int getSize() const {
        ++accessCounter; // А это можно, потому что accessCounter — распиздяй и mutable.
        return data.size();
    }
    // int getLast() { return data.back(); } // Этот вызовет у константного объекта истерику: «Я тебе не верю!»
    int getLast() const { return data.back(); } // А этот — молодец, пушистый и безопасный.
};

int main() {
    DataContainer dc;
    dc.add(5);
    std::cout << dc.getSize(); // Всё гуд.

    const DataContainer& const_ref = dc; // Смотрим на объект через стёклышко «не тронь».
    // const_ref.add(10); // Ошибка! «Ты чё, охренел? Я же константная ссылка!»
    std::cout << const_ref.getSize(); // А это ок, метод-то const.
}

Ну и функции, само собой. Передаёшь параметр как const ссылку — всем сразу понятно: «Мужики, я сюда только посмотреть пришёл, ничего ломать не буду». Возвращаешь const указатель — кричишь на весь мир: «Нашёл я вам данные, но вы их, блядь, не трогайте!».

void printVector(const std::vector<int>& vec) { // Идеально. Никаких копий, и безопасно.
    for (int val : vec) std::cout << val << ' ';
    // vec.push_back(10); // Компилятор: «А я тебе верюл? В пизду такие шутки!»
}

const int* find(const int* arr, int size, int target) {
    // Нашёл адрес и возвращаю, но обёрнутый в целлофан. Не распаковывать, не изменять.
    for (int i = 0; i < size; ++i) if (arr[i] == target) return &arr[i];
    return nullptr;
}

Короче, const — это не просто буквы. Это договор. С компилятором, с самим собой через месяц, и с тем долбоёбом, который будет читать твой код после тебя. Используй его везде, где только можно. От этого меньше геморроя, меньше ошибок, и в душе спокойнее. А то без него — терпения ноль ебать потом отлаживать, где что перезаписалось.