Ответ 1
Правило объявления в c, вы объявляете его так, как вы его используете.
char *p
означает, что вам нужно *p
, чтобы получить char,
char **p
означает, что вам нужно **p
, чтобы получить char.
Я борюсь со знаком указателя *, я нахожу его очень запутанным в том, как он используется как в объявлениях, так и в выражениях.
Например:
int *i; // i is a pointer to an int
Но какова логика синтаксиса? Что означает * непосредственно перед тем, что я имею в виду? Возьмем следующий пример. Пожалуйста, поправьте меня, где я ошибаюсь:
char **s;
char *(*s); // added parentheses to highlight precedence
И здесь я теряю трек. * S между скобками означает: s - указатель? Но указатель на что? И что означает * вне круглых скобок: указатель на то, что указывает?
Таким образом, значение этого параметра: Указатель, указывающий на то, что указывает s, является указателем на char?
Я в недоумении. Является ли знак * интерпретирован по-разному в декларациях и выражениях? Если да, то как оно интерпретируется по-разному? Где я ошибаюсь?
Правило объявления в c, вы объявляете его так, как вы его используете.
char *p
означает, что вам нужно *p
, чтобы получить char,
char **p
означает, что вам нужно **p
, чтобы получить char.
Возьмите это так:
int *i
означает, что значение, на которое указывает i, является целым числом.
char **p
означает, что p является указателем, который сам является указателем на char.
int i; //i is an int.
int *i; //i is a pointer to an int
int **i;//i is a pointer to a pointer to an int.
Является ли знак * интерпретирован по-разному в декларациях и выражениях?
Да. Они совершенно разные. в объявлении * используется для объявления указателей. В выражении унарный * используется для разыменования указателя (или как оператора двоичного умножения)
Некоторые примеры:
int i = 10; //i is an int, it has allocated storage to store an int.
int *k; // k is an uninitialized pointer to an int.
//It does not store an int, but a pointer to one.
k = &i; // make k point to i. We take the address of i and store it in k
int j = *k; //here we dereference the k pointer to get at the int value it points
//to. As it points to i, *k will get the value 10 and store it in j
Объявления в C являются ориентированными на выражение, что означает, что форма объявления должна соответствовать форме выражения в исполняемом коде.
Например, предположим, что у нас есть указатель на целое число с именем p
. Мы хотим получить доступ к целочисленному значению, на которое указывает p
, поэтому мы разыскиваем указатель так:
x = *p;
Тип выражения *p
равен int
; поэтому объявление p
принимает вид
int *p;
В этом объявлении int
- спецификатор типа, а *p
- декларатор. Объявление объявляет имя объявляемого объекта (p
), а также дополнительную информацию о типе, не указанную спецификатором типа. В этом случае дополнительная информация о типе заключается в том, что p
является типом указателя. Объявление может быть прочитано как "p
имеет указатель типа на int
" или "p
- это указатель на тип int
". Я предпочитаю использовать вторую форму, другие предпочитают первую.
Это случайность синтаксиса C и С++, что вы можете написать это объявление как либо int *p;
, либо int* p;
. В обоих случаях он анализируется как int (*p);
- другими словами, *
всегда ассоциируется с именем переменной, а не с спецификатором типа.
Теперь предположим, что у нас есть массив указателей на int
, и мы хотим получить доступ к значению, на которое указывает i-й элемент массива. Мы индексируем в массив и разыгрываем результат так:
x = *ap[i]; // parsed as *(ap[i]), since subscript has higher precedence
// than dereference.
Опять же, тип выражения *ap[i]
равен int
, поэтому объявление ap
равно
int *ap[N];
где декларатор *ap[N]
означает, что ap
представляет собой массив указателей на int
.
И просто для того, чтобы вести точку дома, теперь предположим, что у нас есть указатель на указатель на int
и хотите получить доступ к этому значению. Опять же, мы полагаемся на указатель, затем мы разыгрываем этот результат, чтобы получить целочисленное значение:
x = **pp; // *pp deferences pp, then **pp dereferences the result of *pp
Так как тип выражения **pp
равен int
, декларация
int **pp;
Объявление **pp
указывает, что pp
является указателем на другой указатель на int
.
Двойное косвенное отображение часто появляется, как правило, когда вы хотите изменить значение указателя, которое вы передаете функции, например:
void openAndInit(FILE **p)
{
*p = fopen("AFile.txt", "r");
// do other stuff
}
int main(void)
{
FILE *f = NULL;
...
openAndInit(&f);
...
}
В этом случае мы хотим, чтобы функция обновляла значение f
; для этого мы должны передать указатель на f
. Поскольку f
уже является типом указателя (FILE *
), это означает, что мы передаем указатель на FILE *
, следовательно объявление p
как FILE **p
. Помните, что выражение *p
в openAndInit
относится к тому же объекту, что и выражение f
в main
.
В обеих декларациях и выражениях как []
, так и ()
имеют более высокий приоритет, чем унарный *
. Например, *ap[i]
интерпретируется как *(ap[i])
; выражение ap[i]
является типом указателя и *
различиями в указателе. Таким образом, ap
представляет собой массив указателей. Если вы хотите объявить указатель на массив, вы должны явно сгруппировать *
с именем массива, например:
int (*pa)[N]; // pa is a pointer to an N-element array of int
и когда вы хотите получить доступ к значению в массиве, перед тем, как применить индекс, вы должны соблюдать pa
:
x = (*pa)[i];
Аналогично с функциями:
int *f(); // f is a function that returns a pointer to int
...
x = *f(); // we must dereference the result of f() to get the int value
int (*f)(); // f is a pointer to a function that returns an int
...
x = (*f)(); // we must dereference f and execute the result to get the int value
Моим любимым методом анализа сложных деклараторов является правило по часовой стрелке.
В основном вы начинаете с идентификатора и следуете по часовой стрелке. См. Ссылку, чтобы узнать, как именно она используется.
Две вещи в статье не упоминаются:
1- Вы должны отделить спецификатор типа (int, char и т.д.) от декларатора, проанализировать декларатор и затем добавить спецификатор типа.
2- Если вы столкнулись с квадратными скобками, которые обозначают массив, убедитесь, что вы читаете следующие квадратные скобки (если они есть).
int * i
означает, что я является указателем на int (чтение назад, чтение * в качестве указателя).
char **p
и char *(*p)
означают указатель на указатель на char.
Здесь приведены некоторые другие примеры
int* a[3]
//a - массив из 3 указателей на int
int (*a)[3]
//a является указателем на массив из 3 ints
У вас есть ответ на ваши вопросы.
Действительно, двойная звезда используется для указания указателя на указатель.
В объявлении * означает, что переменная является указателем на другую переменную/константу. что означает, что он может содержать адрес переменной типа. например: char *c;
означает, что c может удерживать адрес до некоторого char, тогда как int *b
означает, что b может содержать адрес некоторого int, тип ссылки важен, поскольку в арифметике указателей pointer + 1
фактически pointer + (1 * sizeof(*pointer))
.
Символ * в выражении означает "значение, сохраненное в адресе", поэтому, если c
является указателем на некоторый char, то *c
является конкретным char.
char *(*s);
означает, что s является указателем на указатель на char, поэтому s не удерживает адрес char, а адрес переменной, которая содержит адрес char.
вот немного информации
variable pointer
declaring &a p
reading/ a *p
processing
Объявление &a
означает, что оно указывает на *i
. В конце концов это указатель на *int
. Целое число должно указывать на *i
. Но если считать j = *k
указателем на указатель, значит, &k
будет значением k
, а k
будет иметь указатель на *int
.