С массивами, почему это так, что a[5] = = 5[a]?
Как указывает Джоэл в переполнение стека подкаст #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-это другая операция, а указатель + указатель-это нонсенс.)
описание стандарта 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 был разработан, компьютеры не имели много памяти (64 КБ было много), поэтому компилятор 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;
In а.из символ 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 Тед Дженсен.
Тед Дженсен объяснил это так:
на самом деле это правда, т. е. везде, где пишут
a[i]
он может быть заменено на*(a + i)
без каких-либо проблем. На самом деле, компилятор в любом случае будет создан один и тот же код. Таким образом, мы видим, что указатель арифметика-это то же самое, что индексация массива. Либо синтаксис производит тот же результат.это не говорит, что указатели и массивы это одно и то же, их нет. Мы только говорим, что для идентификации данный элемент массива мы имеем выбор из двух синтаксисов, один используя индексирование массива и другое используя арифметику указателя, которая дают одинаковые результаты.
теперь, глядя на это последнее выражение, часть его..
(a + i)
, является простым дополнением с помощью + оператор и правила C утверждают, что такое выражение является коммутативный. То есть (a + i) тождественно(i + a)
. Таким образом, мы могли бы пиши*(i + a)
так же легко, как*(a + i)
. Но*(i + a)
могла возникнуть от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)
до*(3 + arr)
. Но наоборот[arr]3
или[3]arr
не является правильным и приведет к синтаксической ошибке, как(arr + 3)*
и(3 + arr)*
не являются допустимыми выражениями. Причина заключается в том, что оператор разыменования должен быть помещен перед адресом, полученным выражением, а не после адреса.
в компиляторе c
a[i] i[a] *(a+i)
есть разные способы ссылаться на элемент в массиве ! (СОВСЕМ НЕ СТРАННО)
В 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]
Как*(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 в нашей программе, потому что это бросает при компиляции времяThe большая разница я нашел...
мы можем повторно инициализировать указатель по адресу, но не в том же случае с массивом.
======
и вернемся к вашему вопросу...
а[5] - это не что иное, как *(а + 5)
вы можете легко понять
a-содержащий адрес (люди называют его базовым адресом) так же, как (2) тип указателя в нашем списке
[]- этот оператор может быть заменен с указателем * .так, наконец...
a[5] == *(a +5) == *(5 + a) == 5[a]