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