Почему с массивами, почему [5] == 5 [a] - PullRequest
1522 голосов
/ 19 декабря 2008

Как указывает Джоэл в подкасте Переполнение стека # 34 , на языке программирования C (он же K & R), в C: * упоминается это свойство массивов: a[5] == 5[a]

Джоэл говорит, что это из-за арифметики указателей, но я все еще не понимаю. Почему a[5] == 5[a]?

Ответы [ 18 ]

1816 голосов
/ 19 декабря 2008

Стандарт C определяет оператор [] следующим образом:

a[b] == *(a + b)

Поэтому a[5] будет оценивать:

*(a + 5)

и 5[a] оценят в:

*(5 + a)

a - указатель на первый элемент массива. a[5] это значение, которое на 5 элементов дальше от a, что соответствует *(a + 5), и из математики начальной школы мы знаем, что они равны (сложение коммутативное ).

278 голосов
/ 19 декабря 2008

Поскольку доступ к массиву определяется с помощью указателей. a[i] определяется как означающее *(a + i), которое является коммутативным.

212 голосов
/ 23 августа 2013

Я думаю, что что-то упускается другими ответами.

Да, p[i] по определению эквивалентно *(p+i), что (поскольку сложение является коммутативным) эквивалентно *(i+p), что (опять же, по определению оператора []) эквивалентно i[p].

(А в array[i] имя массива неявно преобразуется в указатель на первый элемент массива.)

Но в этом случае коммутативность сложения не так уж очевидна.

Когда оба операнда имеют одинаковый тип или даже разные числовые типы, которые переводятся в общий тип, коммутативность имеет смысл: x + y == y + x.

Но в данном случае мы говорим конкретно об арифметике указателей, где один операнд является указателем, а другой - целым числом. (Целое + целое - это другая операция, а указатель + указатель - это нонсенс.)

Описание стандарта * оператора + ( 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 ), который получил от BCPL (руководство от 1967 г.), которое вполне могло быть получено из более ранних языков (CPL? Algol?).

Таким образом, идея о том, что индексация массива определяется в терминах сложения и что добавление, даже указателя и целого числа, является коммутативной, восходит на многие десятилетия назад к языкам-предкам Си.

Эти языки были гораздо менее строго типизированы, чем современные языки. В частности, различие между указателями и целыми числами часто игнорировалось. (Ранние программисты на C иногда использовали указатели как целые числа без знака до того, как ключевое слово unsigned было добавлено в язык.) Поэтому идея сделать добавление некоммутативной, поскольку операнды имеют разные типы, вероятно, не возникла бы у разработчиков эти языки. Если пользователь хотел добавить две «вещи», будь то эти «вещи», являются целыми числами, указателями или чем-то еще, язык не мог предотвратить это.

И с годами любое изменение в этом правиле нарушило бы существующий код (хотя стандарт ANSI C 1989 года мог бы стать хорошей возможностью).

Изменение C и / или C ++, требующее размещения указателя слева и целого числа справа, может нарушить некоторый существующий код, но при этом не будет потеря реальной выразительной силы.

Так что теперь у нас есть arr[3] и 3[arr], означающие абсолютно одно и то же, хотя последняя форма никогда не должна появляться за пределами IOCCC .

191 голосов
/ 19 декабря 2008

И, конечно же,

 ("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, была доступна другая оптимизация на уровне сборки, и разработчику снова пришлось сделать это явным, потому что компилятор не распознал ее. (В последнее время это делают компиляторы, поэтому эти синтаксисы в настоящее время в основном не нужны)

52 голосов
/ 11 февраля 2009

Похоже, что никто не упомянул о проблеме Дины с sizeof:

Вы можете добавить целое число только к указателю, вы не можете добавить два указателя вместе. Таким образом, добавляя указатель на целое число или целое число на указатель, компилятор всегда знает, какой бит имеет размер, который необходимо учитывать.

48 голосов
/ 11 августа 2011

Ответить на вопрос буквально. Не всегда верно, что 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
23 голосов
/ 20 декабря 2008

Хороший вопрос / ответы.

Просто хочу указать, что указатели и массивы C не являются одинаковыми , хотя в этом случае разница не является существенной.

Рассмотрим следующие объявления:

int a[10];
int* p = a;

В a.out символ a находится по адресу, который является началом массива, а символ p - по адресу, где указатель сохраняется, и значение указателя в этой ячейке памяти является началом массива.

22 голосов
/ 10 июня 2012

Я просто обнаружил, что этот уродливый синтаксис может быть «полезен» или, по крайней мере, очень забавно играть, когда вы хотите иметь дело с массивом индексов, которые ссылаются на позиции в одном и том же массиве. Он может заменить вложенные квадратные скобки и сделать код более читабельным!

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)

}

Конечно, я вполне уверен, что в реальном коде для этого нет смысла, но мне все равно было интересно:)

18 голосов
/ 23 марта 2012

Для указателей в C у нас есть

a[5] == *(a + 5)

, а также

5[a] == *(5 + a)

Следовательно, это правда, что a[5] == 5[a].

14 голосов
/ 19 июня 2011

Не ответ, а просто пища для размышлений. Если в классе перегружен оператор индекса / индекса, выражение 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&);
};
...