const int*, int * const, const int * const: Кровавый цепешный разбор

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему эта тема сводит с ума даже опытных C++ разработчиков?

Если вы когда-либо открывали чужой код на C++ и видели нечто вроде const int * const data, то наверняка испытывали желание закрыть редактор и пойти пить чай. Этот странный синтаксис с const, разбросанным по обе стороны от звездочки, — настоящий камень преткновения. Вопрос «What is the difference between const int*, const int * const, and int * const?» на Stack Overflow собрал сотни тысяч просмотров. И это не случайно.

Понимание этих конструкций — не просто прихоть педанта. Это ключ к написанию безопасного, самодокументируемого кода и, что немаловажно, к прохождению собеседований. Сегодня мы раз и навсегда разберемся с этой путаницей, используя простое правило, которое работает всегда.

Три кита: что мы будем сравнивать?

В C++ указатели и константность могут сочетаться четырьмя способами (три из них — самые популярные). Давайте обозначим их:

  • Вариант 1: const int* ptr (или int const* ptr — это одно и то же).
  • Вариант 2: int * const ptr.
  • Вариант 3: const int * const ptr (комбинация первых двух).

Ключевая идея: const всегда относится к тому, что находится слева от него. Если слева ничего нет — к тому, что справа. Звучит просто, но на практике это правило спасает.

Вариант 1: const int* ptr (Указатель на константное целое)

Читаем справа налево: «ptr — это указатель на целое число, которое является константным».

Что это значит на практике?

  • Вы не можете изменить значение переменной, на которую указывает ptr, через этот указатель.
  • Вы можете изменить сам указатель (заставить его указывать на другую переменную).
#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    const int* ptr = &a;  // ptr — указатель на const int

    // *ptr = 15; // ОШИБКА компиляции! Нельзя менять значение через const int*
    std::cout << "*ptr = " << *ptr << std::endl; // Выведет 10

    ptr = &b; // ОК! Меняем сам указатель
    std::cout << "*ptr = " << *ptr << std::endl; // Выведет 20

    return 0;
}

Этот тип часто используется для передачи данных в функцию, когда вы хотите гарантировать, что функция не испортит оригинальные данные. Например, строка void print(const int* data) — это обещание «только чтение».

Вариант 2: int * const ptr (Константный указатель на целое число)

Читаем справа налево: «ptr — это константный указатель на целое число».

Здесь все наоборот:

  • Вы можете изменять значение переменной, на которую указывает ptr.
  • Вы не можете изменить сам указатель (он всегда должен указывать на одну и ту же область памяти).
#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    int * const ptr = &a; // ptr — константный указатель на int

    *ptr = 15; // ОК! Меняем значение через указатель
    std::cout << "*ptr = " << *ptr << std::endl; // Выведет 15

    // ptr = &b; // ОШИБКА компиляции! Нельзя менять сам указатель

    return 0;
}

Представьте, что int * const — это «привязанный» указатель. Он часто используется для работы с регистрами аппаратуры или в тех случаях, когда указатель на буфер не должен «убегать» в другое место.

Вариант 3: const int * const ptr (Константный указатель на константное целое)

Читаем справа налево: «ptr — это константный указатель на целое число, которое является константным».

Это «двойная блокировка»:

  • Вы не можете изменить значение переменной, на которую указывает ptr.
  • Вы не можете изменить сам указатель.
#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    const int * const ptr = &a; // ptr — константный указатель на const int

    // *ptr = 15; // ОШИБКА! Нельзя менять значение
    // ptr = &b;  // ОШИБКА! Нельзя менять указатель

    std::cout << "*ptr = " << *ptr << std::endl; // Выведет 10

    return 0;
}

Этот вариант — вершина безопасности. В реальном коде вы встретите его реже, чем первые два, но он незаменим, когда нужно передать доступ к данным, которые нельзя ни перемещать, ни изменять (например, дескрипторы или глобальные константы).

Бонус: int const * vs const int *

Многие путаются, но это абсолютно одно и то же. В C++ нет разницы между const int* и int const*. Оба означают «указатель на const int». Выбор — дело вкуса и стиля кодирования. Лично я предпочитаю int const*, потому что правило «const относится к тому, что слева» работает без исключений: int const* — const относится к int. Но const int* — исключение из правила (const стоит первым и относится к int). Выбирайте то, что читается легче для вашей команды.

Как легко запомнить? Правило «Справа-налево»

Вместо того чтобы заучивать три варианта, запомните один алгоритм. Чтобы прочитать любое объявление указателя с const:

  1. Найдите имя переменной (например, ptr).
  2. Читайте справа налево, проговаривая каждое слово.
  3. Если видите * — говорите «указатель на».
  4. Если видите const — говорите «константный» и применяйте к тому, что слева.

Пример: int * const ptr.

  • Справа от ptr: const. Говорим: «ptr — константный».
  • Левее: *. Говори

Блоги

Рекомендаций по книгам