С массивами, почему это так, что[5] == 5[а]?
Как указывает Джоэл в переполнение стека подкаст #34, в Язык Программирования C (aka: K & R), есть упоминание об этом свойстве массивов в C: a[5] == 5[a]
Джоэл говорит, что это из-за арифметики указателя, но я все еще не понимаю. почему a[5] == 5[a]?
17 ответов
стандарт C определяет [] оператор следующим образом:
a[b] == *(a + b)
a[5] будет оцениваться:
*(a + 5)
и 5[a] будет оцениваться:
*(5 + a)
a - это указатель на первый элемент массива. a[5] - это значение, 5 элементов дальше a, что то же самое, что *(a + 5), и из математики начальной школы мы знаем, что они равны (сложение коммутативной).
потому что доступ к массиву определяется с точки зрения указателей. a[i] означает *(a + i), который является коммутативной.
я думаю, что другие ответы что-то упускают.
да p[i] по определению эквивалентно *(p+i), что (поскольку сложение коммутативно) эквивалентно *(i+p), который (опять же, по определению [] оператор) эквивалентно i[p].
(и array[i] имя массива неявно преобразуется в указатель на первый элемент массива.)
но коммутативность сложения не так очевидна в этом случай.
когда оба операнда одного типа или даже разных числовых типов, которые повышаются до общего типа, коммутативность имеет смысл:x + y == y + x.
но в этом случае мы говорим конкретно об арифметике указателя, где один операнд является указателем, а другой-целым числом. (Integer + integer-это другая операция, а pointer + pointer-ерунда.)
описание стандарта C + оператор (N1570 6.5.6) говорит:
для сложения либо оба операнда должны иметь арифметический тип, либо один операнд должен быть указателем на полный тип объекта, а другой должен иметь целочисленный тип.
он мог бы так же легко сказать:
для сложения либо оба операнда должны иметь арифметический тип, либо левой операндом должен быть указатель на полный тип объекта и правый операнд должен иметь целочисленный тип.
в этом случае i + p и i[p] было бы незаконно.
в терминах C++ у нас действительно есть два набора перегруженных + операторы, которые можно условно охарактеризовать как:
pointer operator+(pointer p, integer i);
и
pointer operator+(integer i, pointer p);
из которых только первое действительно необходимо.
так почему же это так?
в C++ унаследовал это определение от C, который получил его от B (коммутативность индексирования массива явно упоминается в 1972 ссылка пользователей на B), который получил его от!--59-->нуждающийся в представлении (руководство датировано 1967), которое вполне могло получить его от еще более ранних языков (CPL? Алгол?).
таким образом, идея, что индексация массива определяется в терминах сложения, и что добавление, даже указателя и целого числа, является коммутативным, восходит на многие десятилетия к языкам предков C.
эти языки были гораздо менее строго, чем современный C это. В частности, часто игнорировалось различие между указателями и целыми числами. (Ранние программисты C иногда использовали указатели как целые числа без знака, перед unsigned ключевое слово было добавлено в язык.) Таким образом, идея сделать добавление некоммутативным, потому что операнды разных типов, вероятно, не пришла бы в голову разработчикам этих языков. Если пользователь хочет добавить две "вещи" , являются ли эти "вещи" целыми числами, указателями или что-то еще, это не было до языка, чтобы предотвратить это.
и с годами любое изменение этого правила нарушило бы существующий код (хотя стандарт ANSI C 1989 года мог бы быть хорошей возможностью).
изменение C и / или c++, требующее размещения указателя слева и целого числа справа, может нарушить некоторый существующий код, но не будет потери реальной выразительной силы.
Итак, теперь у нас есть arr[3] и 3[arr] смысл именно то же самое, хотя последняя форма никогда не должна появляться за пределами IOCCC.
и конечно
("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')
основной причиной этого было то, что еще в 70-х годах, когда был разработан C, компьютеры не имели много памяти (64KB было много), поэтому компилятор C не делал много синтаксической проверки. Отсюда "X[Y] " было довольно слепо переведено на "*(X+Y)"
это также объясняет "+=" и "++" синтаксис. Все в форме "A = B + C " имел такую же скомпилированную форму. Но если B был тем же объектом, что и A, то уровень сборки оптимизация была доступна. Но компилятор был недостаточно умен, чтобы распознать его, поэтому разработчику пришлось (A += C). Аналогично, если C был 1, была доступна другая оптимизация уровня сборки, и снова разработчик должен был сделать ее явной, потому что компилятор не распознал ее. (В последнее время компиляторы делают, поэтому эти синтаксисы в основном не нужны в эти дни)
одно никто не упомянул о проблеме Дины с sizeof:
вы можете добавить только целое число к указателю, вы не можете добавить два указателя вместе. Таким образом, при добавлении указателя на целое число или целого числа к указателю компилятор всегда знает, какой бит имеет размер, который необходимо учитывать.
ответить на вопрос буквально. Не всегда верно, что x == x
double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;
печать
false
хороший вопрос/ответы.
просто хочу отметить, что указатели и массивы C не являются то же самое, хотя в этом случае разница не является существенной.
рассмотрим следующие объявления:
int a[10];
int* p = a;
на а.из символ a находится по адресу, который является началом массива, и символом p находится по адресу, где хранится указатель, и значение указателя в этой памяти расположение-это начало массива.
Я просто узнаю, что этот уродливый синтаксис может быть "полезным" или, по крайней мере, очень весело играть, когда вы хотите иметь дело с массивом индексов, которые ссылаются на позиции в том же массиве. Он может заменить вложенные квадратные скобки и сделать код более читабельным !
int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a; // s == 5
for(int i = 0 ; i < s ; ++i) {
cout << a[a[a[i]]] << endl;
// ... is equivalent to ...
cout << i[a][a][a] << endl; // but I prefer this one, it's easier to increase the level of indirection (without loop)
}
конечно, я совершенно уверен, что в реальном коде для этого нет прецедента, но мне все равно было интересно :)
для указателей в C, у нас есть
a[5] == *(a + 5)
и
5[a] == *(5 + a)
значит это правда, что a[5] == 5[a].
не ответ, а просто пища для размышлений.
Если класс имеет перегруженный оператор index/subscript, выражение 0[x] не работает:
class Sub
{
public:
int operator [](size_t nIndex)
{
return 0;
}
};
int main()
{
Sub s;
s[0];
0[s]; // ERROR
}
Так как у нас нет доступа к int класс, это невозможно сделать:
class int
{
int operator[](const Sub&);
};
он имеет очень хорошее объяснение УЧЕБНИК ПО УКАЗАТЕЛЯМ И МАССИВАМ В C Тед Дженсен.
Тед Дженсен объяснил это так:
на самом деле, это правда, я.e везде, где пишут
a[i]он может быть заменить*(a + i)без каких-либо проблем. Фактически, компилятор в любом случае будет создан один и тот же код. Таким образом, мы видим, что указатель арифметика-это то же самое, что индексация массива. Любой синтаксис производит тот же результат.это не говорит о том, что указатели и массивы это одно и то же, но это не так. Мы говорим это только для того, чтобы идентифицировать данный элемент массива мы имеем выбор из двух синтаксисов, один используя индексирование массива и другое используя арифметику указателя, которая дают одинаковые результаты.
теперь, глядя на это последнее выражение, часть этого..
(a + i), простое добавление используя + оператор и правила C утверждают, что такое выражение коммутативный. То есть (a + i) тождественно(i + a). Таким образом, мы могли бы пиши*(i + a)так же легко, как*(a + i). Но!--7--> мог прийти изi[a]! Из всего этого возникает любопытство. правда, что если:char a[20];писать
a[3] = 'x';это то же самое, что писать
3[a] = 'x';
Я знаю, что на вопрос ответили, но я не мог удержаться, чтобы не поделиться этим объяснением.
Я помню принципы разработки компилятора,
Предположим a - это int массив и размер int 2 байта,
& Базовый адрес для a - это 1000.
как a[5] совместимость ->
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010
и
аналогично, когда код c разбивается на 3-адресный код,
5[a] будет ->
Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010
так что в принципе оба операторы указывают на одно и то же место в памяти и, следовательно,a[5] = 5[a].
это объяснение также является причиной того, почему отрицательные индексы в массивах работают в C.
т. е. если я подключусь к a[-5] это даст мне
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990
он вернет мне объект в месте 990.
на C массивы, arr[3] и 3[arr] одинаковы, а их эквивалентные обозначения указателей -*(arr + 3) to *(3 + arr). Но наоборот!--4--> или [3]arr неверно и приведет к синтаксической ошибке, как (arr + 3)* и (3 + arr)* недопустимые выражения. Причина в том, что оператор разыменования должен располагаться перед адресом, заданным выражением, а не после адреса.
в компиляторе c
a[i]
i[a]
*(a+i)
различные способы ссылки на элемент в массиве ! (СОВСЕМ НЕ СТРАННО)
In C
int a[]={10,20,30,40,50};
int *p=a;
printf("%d\n",*p++);//output will be 10
printf("%d\n",*a++);//will give an error
указатель-это "переменная"
имя массива является "мнемоническим"или " синонимом"
p++; допустима, но a++ недопустимо
a[2] равно 2[a], потому что внутренняя операция на обоих из них
"указатель арифметика" внутренне рассчитывается как
*(a+3) равна *(3+a)
Ну, это функция, которая возможна только из-за языковой поддержки.
компилятор интерпретирует a[i] as *(a+i) и выражение 5[a] значение *(5+a). Поскольку сложение коммутативно, получается, что оба они равны. Отсюда и выражение true.
типы указателей
1) указатель на данные
int *ptr;
2) const указатель на данные
int const *ptr;
3) const указатель на const data
int const *const ptr;
и массивы имеют тип (2) из нашего списка
Когда ты определить массив за один раз - адрес инициализации в этом указателе
Как мы знаем, мы не можем изменить или изменить значение const в нашей программе, потому что он бросает при компиляции время
на большая разница я нашел...
мы можем повторно инициализировать указатель по адресу, но не в том же случае с массивом.
======
и вернемся к вашему вопросу...
а[5] есть не что иное, как *(а + 5)
вы можете легко понять
a-содержащий адрес (люди называют его базовым адресом) так же, как (2) тип указателя в нашем списке
[]- этот оператор может быть заменен с указателем * .
так, наконец...
a[5] == *(a +5) == *(5 + a) == 5[a]