Con las matrices, ¿por qué se da el caso de que a[5] == 5[a]?
Como señala Joel en el podcast #34 de Stack Overflow , en 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 todavía no lo entiendo. Por quea[5] == 5[a]
?
El estándar C define al []
operador de la siguiente manera:
a[b] == *(a + b)
Por lo tanto a[5]
evaluará a:
*(a + 5)
y 5[a]
evaluará a:
*(5 + a)
a
es un puntero al primer elemento de la matriz. a[5]
es el valor que está 5 elementos más lejos de a
, que es el mismo que *(a + 5)
, y por las matemáticas de la escuela primaria sabemos que son iguales (la suma es conmutativa ).
Porque el acceso a la matriz se define en términos de punteros. a[i]
se define como significado *(a + i)
, que es conmutativo.
Creo que las otras respuestas están pasando por alto algo.
Sí, p[i]
es por definición equivalente a *(p+i)
, que (porque la suma es conmutativa) es equivalente a *(i+p)
, que (nuevamente, según la definición del []
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 promueven a un tipo común, la conmutatividad tiene perfecto sentido: x + y == y + x
.
Pero en este caso estamos hablando específicamente de 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 +
operador del estándar C ( N1570 6.5.6) dice:
Para la suma, ambos operandos serán de tipo aritmético o un operando será un puntero a un tipo de objeto completo y el otro será de tipo entero.
Podría haber dicho con la misma facilidad:
Para la suma, ambos operandos serán de tipo aritmético, o el operando izquierdo será un puntero a un tipo de objeto completo y el operando derecho será de tipo entero.
en cuyo caso ambos i + p
y i[p]
serían ilegales.
En términos de C++, realmente tenemos dos conjuntos de +
operadores sobrecargados, que pueden describirse en términos generales 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 así?
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 la Referencia de los usuarios de B de 1972 ), que la obtuvo de BCPL (manual de 1967), que bien pudo haberla obtenido incluso de lenguas 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 tipificados con mucha menos fuerza 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 agregara la palabra clave al lenguaje). Entonces, 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 sean números enteros, punteros u otra cosa, no dependía del lenguaje evitarlo.
Y a lo largo de los años, cualquier cambio a 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 poner 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]
significamos exactamente lo mismo, aunque esta última forma nunca debería aparecer fuera del IOCCC .
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]
" se tradujo bastante ciegamente a " *(X+Y)
"
Esto también explica las sintaxis " +=
" y " ++
". Todo en el formulario " A = B + C
" tenía el mismo formulario compilado. Pero, si B era el mismo objeto que A, entonces estaba disponible una optimización a nivel de ensamblaje. Pero el compilador no era lo suficientemente brillante como para reconocerlo, por lo que el desarrollador tuvo que ( A += C
). De manera similar, si C
era 1
, había disponible una optimización de nivel de ensamblaje diferente y nuevamente el desarrollador tuvo que hacerlo explícito porque el compilador no lo reconoció. (Más recientemente, los compiladores lo hacen, por lo que esas sintaxis son en gran medida innecesarias en estos días)
Una cosa que nadie parece haber mencionado sobre el problema de Dinah sizeof
:
Solo puede agregar un número entero a un puntero, no puede sumar dos punteros juntos. De esa manera, al agregar un puntero a un número entero, o un número entero a un puntero, el compilador siempre sabe qué bit tiene un tamaño que debe tenerse en cuenta.