Como señala Joel en Podcast de desbordamiento de pila n.º 34en el lenguaje de programación C (también conocido como: K & R), se menciona esta propiedad de las matrices en C: a[5] == 5[a]
Joel dice que es por la aritmética de punteros pero sigo sin entender. Por que a[5] == 5[a]
?
Creo que las otras respuestas se están perdiendo algo.
Sí, p[i]
es por definición equivalente a *(p+i)
que (porque la suma es conmutativa) es equivalente a *(i+p)
que (de nuevo, por la definición de la []
operador) es equivalente a i[p]
.
(Y en array[i]
el nombre de la matriz se convierte implícitamente en un puntero al primer elemento de la matriz).
Pero la conmutatividad de la suma no es tan obvia en este caso.
Cuando ambos operandos son del mismo tipo, o incluso de diferentes tipos numéricos que se promocionan a un tipo común, la conmutatividad tiene mucho sentido: x + y == y + x
.
Pero en este caso estamos hablando específicamente de la aritmética de punteros, donde un operando es un puntero y el otro es un número entero. (Entero + entero es una operación diferente, y puntero + puntero no tiene sentido).
La descripción del estándar C de la +
operador (N1570 6.5.6) dice:
Para la suma, ambos operandos tendrán un tipo aritmético, o un operando será un puntero a un tipo de objeto completo y el otro tendrá un tipo entero.
Podría haber dicho con la misma facilidad:
Para la suma, ambos operandos deben tener tipo aritmético, o la izquierda
operando será un puntero a un tipo de objeto completo y el operando derecho
tendrá tipo entero.
en cuyo caso ambos i + p
y i[p]
sería ilegal.
En términos de C++, realmente tenemos dos conjuntos de sobrecargados +
operadores, que pueden describirse libremente como:
pointer operator+(pointer p, integer i);
y
pointer operator+(integer i, pointer p);
de los cuales sólo el primero es realmente necesario.
Entonces, ¿por qué es de esta manera?
C ++ heredó esta definición de C, que la obtuvo de B (la conmutatividad de la indexación de matrices se menciona explícitamente en el 1972 Referencia de los usuarios a B), que lo obtuvo de BCPL (manual fechado en 1967), que bien puede haberlo obtenido incluso de idiomas anteriores (¿CPL? ¿Algol?).
Entonces, la idea de que la indexación de matrices se define en términos de suma, y que la suma, incluso de un puntero y un número entero, es conmutativa, se remonta a muchas décadas atrás, a los lenguajes ancestrales de C.
Esos lenguajes estaban mucho menos tipificados que el C moderno. En particular, a menudo se ignoraba la distinción entre punteros y números enteros. (Los primeros programadores de C a veces usaban punteros como enteros sin signo, antes de que unsigned
se agregó una palabra clave al lenguaje). Por lo tanto, la idea de hacer que la suma no sea conmutativa porque los operandos son de diferentes tipos probablemente no se les habría ocurrido a los diseñadores de esos lenguajes. Si un usuario quisiera agregar dos “cosas”, ya sea que esas “cosas” sean números enteros, punteros u otra cosa, no dependía del idioma para evitarlo.
Y a lo largo de los años, cualquier cambio en esa regla habría violado el código existente (aunque el estándar ANSI C de 1989 podría haber sido una buena oportunidad).
Cambiar C y/o C++ para requerir colocar el puntero a la izquierda y el número entero a la derecha podría romper algún código existente, pero no habría pérdida de poder expresivo real.
Así que ahora tenemos arr[3]
y 3[arr]
significando exactamente lo mismo, aunque la última forma nunca debe aparecer fuera de la COICC.
Porque es útil para evitar anidamientos confusos.
Prefieres leer esto:
array[array[head].next].prev
o esto:
head[array].next[array].prev
Por cierto, C++ tiene una propiedad conmutativa similar para las llamadas a funciones. en lugar de escribir g(f(x))
como debe hacer en C, puede usar funciones miembro para escribir x.f().g()
. Reemplace f y g con tablas de búsqueda y puede escribir g[f[x]]
(estilo funcional) o (x[f])[g]
(estilo oop). Este último se vuelve realmente agradable con estructuras que contienen índices: x[xs].y[ys].z[zs]
. Usando la notación más común que es zs[ys[xs[x].y].z]
.
Y por supuesto
("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')
La razón principal de esto fue que en los años 70, cuando se diseñó C, las computadoras no tenían mucha memoria (64 KB era mucha), por lo que el compilador de C no verificaba mucho la sintaxis. Por lo tanto “X[Y]
“fue traducido ciegamente a”*(X+Y)
”
Esto también explica el “+=
” y “++
” sintaxis. Todo en la forma “A = B + C
” tenía la misma forma compilada. Pero, si B era el mismo objeto que A, entonces estaba disponible una optimización de nivel de ensamblaje. Pero el compilador no era lo suficientemente brillante como para reconocerlo, por lo que el desarrollador tuvo que (A += C
). Del mismo modo, si C
era 1
, estaba disponible una optimización de nivel de ensamblado diferente y nuevamente el desarrollador tuvo que hacerlo explícito, porque el compilador no lo reconoció. (Los compiladores más recientes lo hacen, por lo que esas sintaxis son en gran medida innecesarias en estos días)
Porque el compilador C siempre convierte la notación de matriz en notación de puntero.
a[5] = *(a + 5)
además 5[a] = *(5 + a) = *(a + 5)
Entonces, ambos son iguales.
sería algo como un[+] también funcionan como *( a++) O *(++a) ?
– Egon
13 mayo 2010 a las 16:14
@Egon: Eso es muy creativo, pero desafortunadamente no es así como funcionan los compiladores. El compilador interpreta
a[1]
como una serie de tokens, no cadenas: *({ubicación entera de}un {operador}+ {entero}1) es lo mismo que *({entero}1 {operador}+ {ubicación entera de}a) pero no es lo mismo que *({ubicación entera de}un {operador}+ {operador}+)– Dina
13 mayo 2010 a las 17:24
Una variación compuesta interesante de esto se ilustra en el acceso a arreglos ilógicos, donde tiene
char bar[]; int foo[];
yfoo[i][bar]
se usa como expresión.–Jonathan Leffler
17 de octubre de 2012 a las 6:38
@EldritchConundrum, ¿por qué crees que ‘el compilador no puede comprobar que la parte izquierda es un puntero’? Sí puede. Eso es verdad
a[b]
=*(a + b)
para cualquier dadoa
yb
pero fue la elección libre de los diseñadores del lenguaje para+
ser definido conmutativo para todos los tipos. Nada podía impedirles prohibiri + p
mientras permitep + i
.– ach
14/03/2014 a las 19:46
@Andrey Uno suele esperar
+
ser conmutativo, por lo que tal vez el verdadero problema es elegir hacer que las operaciones con punteros se parezcan a la aritmética, en lugar de diseñar un operador de compensación separado.– Enigma sobrenatural
18 de marzo de 2014 a las 10:36