Следующее объявление в C:
int* a, b;
объявит a
как тип int*
и b
как тип int
. Я хорошо знаю об этом прерывании, но что я хочу знать, то, почему оно прокладывает себе путь. Почему это также не объявляет b
как int*
, поскольку большинство людей интуитивно ожидало бы? Другими словами, почему делает *
обратиться к имени переменной, а не типу?
Уверенный Вы могли записать этому этот способ более согласовываться с тем, как это на самом деле работает:
int *a, b;
Однако я и все, которые я говорил для размышления с точки зрения типа "указателя на интервал", а не указателя на некоторые данные и тип тех данных, являемся "интервалом".
Это было просто плохим решением разработчиков C или является там некоторым серьезным основанием, почему это проанализировало этот путь? Я уверен, что на вопрос ответили прежде, но я, может казаться, не нахожу его с помощью поиска.
На веб-странице The Development of the C Language говорится: "Синтаксис этих объявлений отражает наблюдение, что i, *pi и **ppi все дают тип int при использовании в выражении". Найдите это предложение на странице, чтобы найти соответствующий раздел, в котором обсуждается этот вопрос.
Я могу только догадываться, почему нужно было повторять *
для каждого имени переменной, упомянутого в строке, чтобы все они были указателями. Может быть, это связано с решением о согласованности языка? Позвольте мне проиллюстрировать это на примере:
Предположим, у вас есть функция foo
, объявленная следующим образом:
int foo() { ... }
Давайте также предположим, что вы хотите объявить два указателя на эту функцию:
int (*fooptr1, fooptr2)();
// this doesn't work; and even if it did, what would the syntax possibly
// look like to initializing them to function foo?
// int (*fooptr1 = foo, fooptr2 = foo)() ?
int (*fooptr1)() = foo, (*fooptr2)() = foo;
// this works.
В этом случае вам просто нужно полностью повторить объявление типа для обеих переменных, и вы не ошибетесь, потому что другого нет способ сделать это (учитывая синтаксис объявления C как есть).
Теперь, может быть, считалось, что если есть случаи, когда объявление типа должно повторяться для всех переменных, возможно, это должен быть просто общий случай.
(Не забывайте, что я здесь только предполагаю.)
Я предполагаю, что это связано с синтаксисом полного объявления для модификаторов типа:
int x[20], y;
int (*fp)(), z;
В этих примерах кажется гораздо более очевидным, что модификаторы влияют только на одно из объявлений. Можно предположить, что когда K&R решил разрабатывать модификаторы таким образом, было «правильным» иметь модификаторы, влияющие только на одно объявление.
Кстати, я бы порекомендовал ограничиться одной переменной для каждого объявления:
int *x;
int y;
*
изменяет имя переменной, а не спецификатор типа. В основном это связано с тем, как анализируется *
. Возьмите эти утверждения:
char* x;
char *x;
Эти утверждения эквивалентны. Оператор *
должен находиться между спецификатором типа и именем переменной (он обрабатывается как инфиксный оператор), но может располагаться по обе стороны от пробела. При этом объявление
int* a, b;
не сделает b
указателем, потому что к нему нет *
. *
работает только с объектами по обе стороны от него.
Также подумайте об этом так: когда вы пишете объявление int x;
, вы указываете, что x
является целым числом. Если y
является указателем на целое число, то * y
является целым числом. Когда вы пишете int * y;
, вы указываете, что * y
является целым числом (что вам и нужно). В операторе char a, * b, *** c;
вы указываете, что переменная a
, разыменованное значение b
и тройное -разыменованные значения c
все имеют тип char
.Объявление переменных таким образом делает использование оператора звезды (почти) совместимым с разыменованием.
Я согласен с тем, что было бы логичнее, если бы все было наоборот. Чтобы избежать этой ловушки, я взял за правило всегда объявлять указатели в строке сами по себе.
Потому что, если бы оператор
int* a, b;
объявлял b
также как указатель, тогда у вас не было бы возможности объявить
int* a;
int b;
в одной строке.
С другой стороны, вы можете сделать
int*a, *b;
, чтобы получить желаемое.
Подумайте об этом так: то, как оно есть сейчас, по-прежнему остается наиболее кратким и в то же время уникальным способом сделать это. Это то, о чем в основном говорит C :)
Декларации языка Си были написаны таким образом, чтобы "декларация зеркально отражала использование". Вот почему вы объявляете массивы так:
int a[10];
Если бы вместо этого было предложенное вами правило, где всегда
type identifier, identifier, identifier, ... ;
... то массивы логически должны были бы объявляться так:
int[10] a;
что хорошо, но не отражает того, как вы используете a
. Обратите внимание, что это справедливо и для функций - мы объявляем функции следующим образом:
void foo(int a, char *b);
а не
void(int a, char* b) foo;
В общем, правило "объявление зеркально отражает использование" означает, что вам нужно помнить только один набор правил ассоциативности, которые применяются как к операторам типа *
, []
и ()
, когда вы используете значение, так и к соответствующим лексемам в деклараторах типа *
, []
и ()
.
После некоторых дальнейших размышлений, я думаю, стоит также отметить, что написание "pointer to int" как "int*
" в любом случае является лишь следствием "использования зеркал декларации". Если бы вы собирались использовать другой стиль объявления, то, вероятно, было бы логичнее написать "pointer to int" как "&int
", или что-то совершенно другое, например "@int
".
Может быть дополнительная историческая причина, но я всегда понимал это так:
Одно объявление, один тип.
Если здесь a, b, c и d должны быть одного типа:
int a, b, c, d;
Тогда все в строке также должно быть целым числом.
int a, *b, **c, ***d;
4 целых числа:
Это также может быть связано с приоритетом оператора, или оно могло быть каким-то точка в прошлом.
Рассмотрим объявление:
int *a[10];
int (*b)[10];
Первый - это массив из десяти указателей на целые числа, второй - это указатель на массив из десяти целых чисел.
Теперь, если бы * был прикреплен к объявлению типа, было бы синтаксически неправильно помещать круглые скобки между ними. Так что вам придется найти другой способ различать эти две формы.