Указатели могут быть довольно запутанной темой для тех, кто недавно начала знакомство с C++. Еще более сложными для понимания они становятся при использовании в сочетании с модифкатором const. И, если ваш компилятор поддерживает стандарт C++11 (или более поздний), вам редко придется работаться обычными указателями вместо «умных» (std::shared_ptr и т.д.), синтаксис «классических» указателей необходимо понимать.

Для начала, давайте удостоверимся, что мы понимаем синтаксис объявления константных указателей и данных.

// Указатель и данные, на которые он указывает, не являются константными
char* ptr = "string of data";

// Данные, на которые указывает указатель, константны, значение самого указателя можно изменять
const char* ptr = "string of data";

// Значение самого указателя изменять нельзя, данные, на которые он указывает - можно
char* const ptr = "string of data";

// Ни значение указателя, ни данные, на которые он указывает, менять нельзя
const char* const ptr = "string of data";

Существует удобный метод чтения таких объявлений, позволящий быстро понять, что они означают. Посмотрите на звездочки (*) и разделить строку объявления указателя на 2 части: слева от звездочки и справа от звездочки. Теперь вам будет гораздо проще понять, что является константным, а что нет. Давайте рассмотрим следующую строку в качестве примера:

char* const ptr = "string of data";

Мы смотрим слева от звездочки и видим тип char без ключевого слова const, таким образом, данные в данном случае не являются константными, т.е. содержимое строки может быть изменено. Теперь мы смотрим справа от звездочки и видим const ptr. «Ага!», говорим мы, «указатель ptr константен». Таким образом, мы приходим к выводу, что приведенное выше объявление указателя ptr означает следующее: константный указатель на неконстантные данные.
Теперь, когда мы знаем как читать объявления указателей, мы могли бы задаваться вопросом, что именно означают термины «константные данные» и «константный указатель». На самом деле, это довольно просто: просто надо помнить, что и данные и указатель являются переменными (указатель является переменной, которая содержит в качестве значения адрес другой переменной ). Таким образом, такие понятия как «константные данные» и «константный указатель» на самом деле означают «константная переменная».
Итак, давайте подведем итог: выражение «константный указатель на некоторые данные» означает, что указатель, что после его инициализации, не может указывать на какие-либо другие данные, т.е. нельзя изменить адрес памяти, который содержит указатель. Выражение «константные данные» означает, что через данный указатель, мы не можем изменить данные, на которые он указывает (такие указатели очень полезны в качестве аргументов функции). Вот пример кода, демонстрирующий эти понятия (заметим, что для образовательных целей приведенный ниже код содержит строки, которые могут вызывать ошибки компиляции, однако они закомментированны, так что код должен собираться):

#include <iostream>

using namespace std;

int main()
{
    int foo = 4;
    int bar = 16;

    // ptr - неконстантный указатель на неконстантные данные
    // data
    int* ptr = &foo;

    // OK: Данные неконстантны, мы можеи их менять через указатель ptr
    *ptr = 6;

    // Указатель неконстантен, поэтому мы можем "перенацелить"  его на другие данные и поменять их
    ptr = &bar;
    *ptr = 22;

    // ptr_to_const - неконстантный указатель на константные данные
    const int* ptr_to_const = &foo;

    // Если раскомментировать следующую строку, будет ошибка компиляци: 
    // ptr_to_const указывает на константные данные т поменять их через этот указатель нельзя
    // *ptr_to_const = 10;

    // OK: Указатель неконстантен, мы можем менять его значение (т.е. менять адрес памяти, который он хранит)
    ptr_to_const = &bar;

    // Если раскомментировать следующую строку, будет ошибка компиляци: 
    // для ptr_to_const любые данные, на котрые он указывает - константны, т.е из нельзя поменять
    // *ptr_to_const = 100;

    // const_ptr - константный указатель на неконстантные данные
    int* const const_ptr = &foo;

    // OK: const_ptr указывает на неконстантные данные - значит их можно менять через const_ptr
    *const_ptr = 15;

    // Если раскомментировать следующую строку, будет ошибка компиляци: 
    // const_ptr - константный указатель, т.е. нельзя поменять адрес памяти, который он хранит
    // const_ptr = &bar;

    // const_ptr_to_const - константный указатель на константные данные
    const int* const const_ptr_to_const = &foo;

    // Если раскомментировать следующую строку, будет ошибка компиляци: 
    // нельзя менять данные, т.к. они константны
    // *const_ptr_to_const = 28;

    // Если раскомментировать следующую строку, будет ошибка компиляци: 
    // нельзя поменять адрес памяти, который он хранит, т.к. const_ptr_to_const - константный указатель
    // const_ptr_to_const = &bar;

    return 0;
}

Примечание: иногда в коде вы можете увидеть ключевое слово const после типа, а не перед ним, чтобы акцентировать внимание на том, что указатель указывает на константные данные. Не удивляйтесь — это лишь еще один способ написать то же самое. Таким образом, следующие декларации эквивалентны:

// Объявляем указатель на константыне данные типа integer
const int* ptr1;

// Объявляем указатель на константыне данные типа integer
// (полные эквивалент объявлению ptr1)
int const* ptr2;

Источник: eli.thegreenplace.net