С массивами, почему это так, что a[5] = = 5[a]?


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

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

17 1449

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]