С массивами, почему это так, что[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]