Что означает const*?

462
20 февраля 2017, 17:49

Именно со звездой - демонические конструкции могут принимать вид

const char* const* blah_blah,

char const* blah

int const* const integer,

int* const* const и так далее в самых разных комбинациях. Как расшифровать сии начертания и зачем вообще может кому-то понадобиться оборачиваться в const, будто шелкопряду?

Answer 1

Такие записи проще всего рассматривать справа налево. Например,

Данное объявление

const char * const  * blah_blah;

объявляет указатель с именем blah_blah, который указывает на константный указатель (так как квалификатор const предшествует *, если смотреть справа налево), который указывает на константный объект типа char.

Вот пример простой программы, который облегчает понимание этого объявления.

#include <stdio.h>
int main(void) 
{
    const char * const  * blah_blah;
    const char *p = "Hello";
    blah_blah = &p;
    puts( *blah_blah );
}

На консоль будет выведено

Hello

Можно ввести typedef имена для наглядности

typedef const char CONST_CHAR_TYPE;
typedef CONST_CHAR_TYPE *POINTER_TO_CONST_CHAR_TYPE;
const POINTER_TO_CONST_CHAR_TYPE *blah_blah;

в этом случае переменная blah_blah является указателем на константный указатель на константный объект типа char.

Я думаю будет легче понять эти объявления, если следовать грамматике определения указателя.

Указатель в стандарте C определяется следующим образом

pointer: 
    * type-qualifier-listopt 
    * type-qualifier-listopt pointer

то есть за знаком * может следовать список квалификации, который к нему относится.

Поэтому предыдущее объявление можно записать как

const char ( * const ( *blah_blah ) );

Можно сделать и сам указатель blah_blah константным. Но тогда он должен быть явно инициализирован при объявлении, если имеет автоматическую продолжительность памяти. Например

const char ( * const ( * const blah_blah ) ) = /* некоторое значение */;

или

const char * const * const blah_blah  = /* некоторое значение */;

Вот демонстрационная программа, в которой сам указатель blah_blah также объявлен как константный.

#include <stdio.h>
int main(void) 
{
    const char *p = "Hello";
    const char ( * const ( * const  blah_blah ) ) = &p;
    puts( *blah_blah );
}

Объявление этого указателя читается как константный указатель на константный указатель на константный объект типа char.

В объявления функций, имеющих прототип, можно опускать идентификатор параметра.

Ниже представлена такая программа

#include <stdio.h>
void display( const char ( * const ( * const ) ) );
void display( const char * const * const );
void display( const char ( * const  ( * ) ) );
void display( const char * const  * );
int main(void) 
{
    const char *p = "Hello";
    display( &p );
}
void display( const char * const * const blah_blah )
{
    puts( *blah_blah );
}

Так как объявлений одной и той же функции (без ее определения) может быть несколько, то все выше указанные функции объявляют одну и ту же функцию. Так называемый квалификатор верхнего уровня, который относится непосредственно к параметру, можно убрать из объявления функции. То есть (еще один пример) эти пары функций объявляют одни и те же функции

void f( const int x );
void f( int x );

и

void g( int * const p );
void g( int * p );
Answer 2

В языках С и С++ нередко используется [неформальная/полуформальная] концепция пути доступа (access path) к объекту. А именно: указатель (или ссылка) на объект является путем доступа к этому объекту. Пути доступа бывают константные и неконстантные. Через константные пути доступа объект модифицировать нельзя, через неконстантные - можно (при условии, что сам объект является модифицируемым). К одному и тому же объекту могу вести как константные так и неконстантные пути доступа.

int a = 42;
int *pa = &a;
const int *ca = &a;
// `*pa` - неконстантный путь доступа к `a`
// `*ca` - константный путь доступа к `a`

Приведенные вами примеры использования модификатора const на более глубоких уровнях вложенности в объявлениях указателей (т.е. слева от *) как раз управляют константностью пути доступа, то есть разрешают или запрещают модификацию указуемого объекта через данный путь доступа.

(Из ряда вот выходит только ваш последний пример, где крайний правый const задает константность самого указателя integer, а не пути доступа к указуемому объекту.)

Если один и то же объект виден вам как через константный, так и через неконстантный путь доступа, то модификация этого объекта, сделанная через неконстантный путь доступа, будет тут же видна через константный путь доступа

int a = 42;
int *pa = &a;
const int *ca = &a;
*pa = 5;
// Теперь значение `*ca` равно 5

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

Правила константной корректности языка С и С++ позволяют вам свободно превращать неконстантные пути доступа в константные

int a = 42;
int *pa = &a;
// `*pa` - неконстантный путь доступа к `a`
const int *ca = pa;
// `*ca` - константный путь доступа к `a`

но обратное превращение невозможно. (Путем использования const_cast или C-style cast можно "грубой силой" превращать константные пути доступа в неконстантные, но это уже другая история.)

Можно дополнительно заметить, что правила константной корректности языков С и С++ несколько отличаются, что приводит к ряду сохраняющихся по сей день "нелогичностей" в языке С, давно устраненных в языке С++

int **p = 0;
const int *const *ccp = p; // Ошибка в С, разрешено в С++
int a[10] = { 0 };
const int (*сpa)[10] = &a; // Ошибка в С, разрешено в С++

Это временами мешает полноценно пользоваться константными путями доступа в С.

Answer 3

Ну смотрите, например:

int j;    //  Переменная типа int, в нее можно писать, что угодно.
int * pi; //  Указатель на переменную int, в которую можно писать: *pi = 5;
const int * cpi; // Указатель на константную переменную int,
                 // которую можно только читать: о = *cpi;
                 // Сам указатель можно менять: cpi = &j;
const int * const ccpi; // Константный указатель на константную переменную.
                        // И переменную, на которую он указывает, можно только читать, 
                        // и сам указатель нельзя изменять...

Так более-менее понятно?

Более сложные варианты могут включать, например, константный указатель на вот такой константный указатель, как ccpi выше :)

READ ALSO
Как вызвать функцию как аргумент?

Как вызвать функцию как аргумент?

Смысл такой, что нужно расписать программу для сортировки массива по функциям без использования глобальных переменныхЯ решил пойти путём...

248
как правильно передать массив в функцию?

как правильно передать массив в функцию?

Почему в функцию передается TCHAR(&name)[100], а не TCHAR *name?

281
Как запустить сервис на Android при помощи Qt?

Как запустить сервис на Android при помощи Qt?

Пытаюсь написать сервис который будет кликать по экрануДелаю это так:

387
Наполнения списка возможных Command Arguments

Наполнения списка возможных Command Arguments

Есть ли возможность для *vcxproj указать перечень возможных аргументов для запуска приложения?

211