Общепринятым соглашением C является запись T *p
, тогда как общепринятым соглашением C ++ является запись T* p
. Оба разбираются как T (*p)
; *
является частью объявления, а не спецификатором типа. Это просто случайность синтаксиса объявления указателя, который вы можете написать любым способом.
Синтаксис объявления C (и, соответственно, C ++): выражение-центрированное ; Таким образом, форма объявления должна соответствовать форме выражения того же типа в коде.
Например, предположим, что у нас был указатель на int
, и мы хотели получить доступ к этому целочисленному значению. Для этого мы разыменовываем указатель с помощью оператора косвенного обращения *
, например:
x = *p;
Тип выражения *p
равен int
; таким образом, из этого следует, что объявление p
должно быть
int *p
Внутренность p
предоставляется спецификатором типа int
, а указатель p
предоставляется декларатором *p
.
В качестве немного более сложного примера, предположим, что у нас был указатель на массив float
, и мы хотели получить доступ к значению с плавающей запятой в i
-ом элементе массива через указатель. Мы разыменовываем указатель массива и добавляем результат:
f = (*ap)[i];
Тип выражения (*ap)[i]
равен float
, поэтому из объявления указателя массива следует
float (*ap)[N];
Число с плавающей запятой ap
предоставляется спецификатором типа float
, а указатель и массив - с помощью декларатора (*ap)[N]
. Обратите внимание, что в этом случае *
должен явно быть привязанным к идентификатору; []
имеет более высокий приоритет, чем унарный *
в синтаксисе выражений и объявлений, поэтому float* ap[N]
будет анализироваться как float *(ap[N])
, или "массив указателей на float
", а не "указатель на массив float
». Я полагаю, вы могли бы написать это как
float(* ap)[N];
но я не уверен, какой смысл; это не делает тип ap
более понятным.
Еще лучше, как насчет указателя на функцию, которая возвращает указатель на массив указателей на int
:
int *(*(*f)())[N];
Опять же, по крайней мере два из *
оператора должны быть явно связаны в объявителе; привязка последнего *
к спецификатору типа, как в
int* (*(*f)())[N];
просто указывает на запутанное мышление ИМО.
Несмотря на то, что я использую его в своем собственном коде C ++, и хотя я понимаю, почему он стал популярным, у меня есть проблема с обоснованием соглашения T* p
, заключающееся в том, что оно просто не применяется вне простейших объявлений указателей, и это усиливает упрощенное представление о том, что точка неправильного синтаксиса объявлений C и C ++. Да, тип p
- это «указатель на T», но это не меняет того факта, что с точки зрения грамматики языка *
привязывается к декларатору, а не к спецификатору типа.
В другом случае, если тип a
равен «N-элементному массиву T», мы не пишем
T[N] a;
Очевидно, грамматика не позволяет этого. Опять же, аргумент просто не применяется в этом случае.
EDIT
Как отметил Стив в комментариях, вы можете использовать typedefs, чтобы скрыть некоторые сложности. Например, вы можете переписать
int *(*(*f)())[N];
как что-то вроде
typedef int *iptrarr[N]; // iptrarr is an array of pointer to int
typedef iptrarr *arrptrfunc(); // arrptrfunc is a function returning
// a pointer to iptrarr
arrptrfunc *f; // f is a pointer to arrptrfunc
Теперь вы можете безоговорочно применить соглашение T* p
, объявив f
как arrptrfunc* f
. Лично мне не нравится делать вещи таким образом, так как из определения типа не обязательно ясно, как f
предполагается использовать в выражении или как использовать объект типа arrptrfunc
. Версия без определения типа может быть уродливой и трудной для чтения, но, по крайней мере, она говорит вам все, что вам нужно знать заранее; Вам не нужно копаться во всех typedef.