C ++ указатель на константный указатель - PullRequest
0 голосов
/ 04 января 2019

Я все еще не понимаю, куда поместить const в указателях с более чем одним косвенным указанием. Может кто-нибудь уточнить?

например. прямо сейчас мне нужен указатель на константный указатель, означающий такую ​​переменную int **ppTargets, что я могу присвоить ей int *pTargets переменную, например:

int foo(int **ppTargets) {
    int *pTargets = /* calculate here */;
    *ppTargets = pTargets;
    return 37; // just e.g.
}

В приведенном выше коде отсутствует const. Таким образом, в foo я хочу, чтобы pTargets указывал на постоянную память и был не назначаемым после инициализации (так что нельзя написать, например, pTargets++), это было бы int const *const pTargets = /* assigned once */. Далее я хочу объявить ppTargets, что сам ppTargets может быть назначен, но тогда *ppTargets может быть прочитан только.

Другими словами, в коде вызывающего абонента , который я хочу:

int const* pTargets;
foo(&pTargets);

Я пытался объявить foo следующим образом, но получил ошибку you cannot assign to a variable that is const:

int foo(int *const *const ppTargets)

Ответы [ 7 ]

0 голосов
/ 05 января 2019

После отзывов других, особенно правила по часовой стрелке / спирали от @Mahesh, а также некоторых дебатов, я понял, как легко читать и писать такие вещи.

Мы должны посмотреть, что можно, а что нельзя изменить. Поэтому рассмотрим объявление без const s: int **ppTargets. Мы хотим, чтобы ppTargets не мог быть изменен сам, в то время как *pTargets может быть изменен, в то время как **pTargets не может быть изменен.

Затем примените эти наблюдения справа налево: int const * * const ppTargets.

Самый правый const говорит, что ppTargets++ невозможен.

Тогда отсутствие const в середине говорит о том, что (*ppTargets)=pTargets возможно.

Тогда другой, самый левый const говорит, что (**ppTargets)++ невозможен.

0 голосов
/ 05 января 2019

На мой взгляд, основная формализация косвенного обращения

(read-only|read-write) <memory zone> * (read-only|read-write) <pointer>

где <pointer> сама зона памяти. Для двойной косвенности выражение становится

(read-only|read-write) <memory zone> * (read-only|read-write) <memory zone/pointer-level2> * (read-only|read-write) <pointer-level1>

Что делает вещи более сложными для понимания, так это возможность размещения квалификаторов (например, read-only) перед ИЛИ после <memory zone> в левом размере символа *. Справа от символа * квалификатор (и) можно размещать только перед <pointer>.

В C ++ read-only означает const , а read-write - неявный квалификатор.

Таким образом, мы можем иметь:

  • char* p указатель чтения-записи для чтения-записи char зона памяти
  • const char* p указатель чтения-записи только для чтения char зона памяти
  • char* const p только для чтения указатель на чтение-запись char зона памяти
  • const char* const p только для чтения указатель на только для чтения char зона памяти

Затем мы можем переместиться на const после объявления базового типа в результате эквивалентных объявлений:

  • char const* p указатель чтения-записи только для чтения char зона памяти
  • char const* const p только для чтения указатель на только для чтения char зона памяти

Допускается преобразование указателя с равным или менее ограничительным квалификатором в указателе источника для каждого уровня косвенности по сравнению с указателем назначения.

В результате допустимы следующие случаи:

int foo(const int* const* const p);

{// equal leftmost qualifier
const int* p = nullptr;

const int** p1 = &p; // 2nd and 3rd qualifiers are less restrictive
foo(p1);

const int* const* p2 = &p; // 2nd qualifier is equal, 3rd one (implicit read-write) is less restrictive
foo(p2);

const int* const* const p3 = &p; // 2nd and 3rd qualifiers are equal
foo(p3);
}

{// less restrictive leftmost qualifier of p
int* p = nullptr;

int** p1 = &p; // 2nd and 3rd qualifiers are less restrictive
foo(p1); 

int* const* p2 = &p; // 2nd qualifier is equal, 3rd one (implicit read-write) is less restrictive
foo(p2);

int* const* const p3 = &p; // 2nd and 3rd qualifiers are equal
foo(p3);
}

В вашем случае крайний левый указатель указателя, переданный в качестве аргумента (&pTargets), не равен или менее ограничен, чем крайний левый указатель указателя из функции foo.

0 голосов
/ 05 января 2019

Итак, я хочу, чтобы pTargets указывал на постоянную память и сам был const, это было бы int const *const pTargets = /* assigned once */. Далее я хочу объявить ppTargets, что ppTargets само может быть назначено, но тогда *ppTargets можно прочитать только.

К сожалению, это не имеет смысла. Ваш пример кода присваивает *ppTargets, что действительно является основной целью функции foo(). Если *ppTargets может быть назначен один раз, то он может быть назначен снова.

Неясно, почему вы хотите, чтобы foo() local pTargets был const, а не просто не изменял его, но вы можете присвоить значение const объекту соответствующего не- const -квалифицированный тип. Таким образом, то, что вы на самом деле ищете, может быть

int foo(int const **ppTargets) {
    int const * const pTargets = /* calculate here */;
    *ppTargets = pTargets;
    return 37; // just e.g.
}

И это, кажется, согласуется с вашим предполагаемым использованием:

Другими словами, в коде вызова я хочу:

int const* pTargets;
foo(&pTargets);

Для любого типа T тип указателя на T может быть записан T *. В частности, типом этого &pTargets является int const ** (выглядит знакомо?), И это соответствующий тип для параметра функции, с помощью которого функция должна иметь возможность установить значение pTargets.

вызывающей стороны.

И снова, вызывая foo(), чтобы установить значение вызывающего абонента pTargets, похоже, это как раз и есть точка. Если бы foo() было запрещено делать это, то идеальным подходом было бы передать сам pTargets (по значению), вместо того, чтобы передавать его адрес и спорить const квалификаторы.

0 голосов
/ 05 января 2019

Я всегда читаю определения C / C ++ из крайнего правого имени переменной влево.

Итак:

  • const char *p;

    p - указатель на char, то есть const

    Так что p можно изменить, но *p нельзя.

  • const char * * const p = &a;

    p - это указатель const на указатель на char, то есть const.

    Так что p нельзя изменить (поэтому я его инициализировал); *p банка; но **p не могу.

[EDIT - добавлены массивы для полноты]

  • const char * * const p[4] = { &a, &b, &c, &d };

    p - это 4-элементный массив из const указателей на ...

0 голосов
/ 05 января 2019

То, что вы ищете, это int const * const * ppTarget. Нет, подожди, ты ищешь int const ** const ppTarget. Нет-нет, это int * const * const * ppTarget.

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

Что вы должны сделать, это использовать typedef s, чтобы люди, читающие код, поняли, что вы хотите.

typedef const int *CINT_PTR;
CINT_PTR pTarget = ....;
CINT_PTR *ppTarget = &pTarget;
0 голосов
/ 05 января 2019

Поскольку pTargets - это const int *, его адрес - const int **, то есть тип, который вы хотите использовать для параметра функции:

int foo(const int **ppTargets)
{
    int *pTargets = malloc(sizeof(int)*4);
    pTargets[0] = 1;
    pTargets[1] = 2;
    pTargets[2] = 3;
    pTargets[3] = 4;
    *ppTargets = pTargets;
    return 37;
}

int main()
{
    int const *pTargets;
    foo(&pTargets);
    return 0;
}

EDIT:

Если переменная для установки определена как int const * const pTargets;, единственный способ установить ее - это когда она инициализируется. Вы можете сделать это вместо этого:

const int *foo2()
{
    int *pTargets = malloc(sizeof(int)*4);
    pTargets[0] = 1;
    pTargets[1] = 2;
    pTargets[2] = 3;
    pTargets[3] = 4;
    return pTargets;
}

int main()
{
    int const * const pTargets = foo2();
    return 0;
}
0 голосов
/ 05 января 2019

Итак, я хочу, чтобы pTargets указывала на постоянную память и сама была константой

Далее я хочу объявить ppTargets, что сама ppTargets может быть назначена, но тогда * ppTargets можно только читать.

Для ясности, пусть int const * будет Ptr, а int const * const (т.е. Ptr const) будет CPtr.

Точно так же, как вы правильно написали int const *const pTargets (то есть CPtr), когда вы хотели, чтобы константный указатель константировал int, так же, как если вы хотите, чтобы неконстантный указатель константный указатель константировал как const int (то есть тип &pTargets т.е. CPtr*), вам нужно int const *const * ppTargets. Обратите внимание, что Ptr* неявно преобразуется в CPtr*.

Ваша попытка int *const *const ppTargets будет указателем const на указатель const на не-const int. Поскольку тип является константным указателем, его нельзя присвоить, что противоречит вашему требованию.


В общем, простое эмпирическое правило состоит в том, чтобы читать объявления типа указателя C справа налево, и постоянство применяется слева от ключевого слова (кроме случаев, когда это самый левый токен, в этом случае он применяется справа).


Теперь, когда мы нашли тип, который соответствует вашим заявленным требованиям, позвольте мне обратить ваше внимание на реализацию foo, которая соответствует *ppTargets = pTargets. Это противоречит требованию "*ppTargets можно прочитать только ".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...