Como Joel señala en el podcast # 34 de Stack Overflow , en C Programming Language (también conocido como: K & R), se menciona esta propiedad de las matrices en C:a[5] == 5[a]
Joel dice que se debe a la aritmética de punteros, pero todavía no lo entiendo. ¿Por qué lo hacea[5] == 5[a]
?
El estándar C define al []
operador de la siguiente manera:
a[b] == *(a + b)
Por a[5]
lo tanto evaluará:
*(a + 5)
y 5[a]
evaluará para:
*(5 + a)
a
es un puntero al primer elemento de la matriz. a[5]
es el valor que está 5 elementos más lejos a
, que es el mismo que *(a + 5)
, y de 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 para significar *(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 adición es conmutativa) es equivalente a *(i+p)
, que (de nuevo, 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, conmutatividad tiene mucho 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:
Además, ambos operandos deben tener un tipo aritmético, o un operando debe ser un puntero a un tipo de objeto completo y el otro debe tener un tipo entero.
Con la misma facilidad podría haber dicho:
Para agregar, ambos operandos deben tener un tipo aritmético, o el operando izquierdo debe ser un puntero a un tipo de objeto completo y el operando derecho debe tener un 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 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 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 usuarios de 1972 a B ), que la obtuvo de BCPL (manual con fecha de 1967), que bien puede haberla obtenido incluso idiomas anteriores (CPL? Algol?).
Entonces, la idea de que la indexación de matrices se define en términos de suma, y esa suma, incluso de un puntero y un entero, es conmutativa, se remonta a muchas décadas atrás, a los lenguajes ancestros de C.
Esos lenguajes se escribieron 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 unsigned
agregar la palabra clave al lenguaje). Por lo tanto, la idea de hacer que la suma no sea conmutativa porque los operandos son de tipos diferentes probablemente no se les habría ocurrido a los diseñadores de esos lenguajes. Si un usuario quisiera agregar dos "cosas", ya sean esas "cosas" números enteros, punteros u otra cosa, no estaba en manos del idioma evitarlo.
Y a lo largo de los años, cualquier cambio a esa regla habría roto 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 que requiera colocar el puntero a la izquierda y el 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 la ú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 (64KB era mucha), por lo que el compilador de C no revisó mucho la sintaxis. Por lo tanto, " X[Y]
" se tradujo más bien 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 de nivel de ensamblaje. Pero el compilador no era lo suficientemente brillante como para reconocerlo, por lo que el desarrollador tuvo que hacerlo ( A += C
). De manera similar, si lo C
estaba 1
, estaba disponible una optimización de nivel de ensamblaje 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)
Una cosa que nadie parece haber mencionado sobre el problema de Dinah con sizeof
:
Solo puede agregar un número entero a un puntero, no puede agregar 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.
Para responder a la pregunta literalmente. No siempre es cierto quex == x
double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;
huellas dactilares
false
Acabo de descubrir que esta fea sintaxis podría ser "útil", o al menos muy divertida de jugar cuando quieres lidiar con una matriz de índices que se refieren a posiciones en la misma matriz. ¡Puede reemplazar los corchetes anidados y hacer que el código sea más legible!
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)
}
Por supuesto, estoy bastante seguro de que no hay un caso de uso para eso en el código real, pero lo encontré interesante de todos modos :)
Buenas preguntas / respuestas.
Solo quiero señalar que los punteros de C y las matrices no son lo mismo , aunque en este caso la diferencia no es esencial.
Considere las siguientes declaraciones:
int a[10];
int* p = a;
En a.out
, el símbolo a
está en una dirección que es el comienzo de la matriz, y el símbolo p
está en una dirección donde se almacena un puntero, y el valor del puntero en esa ubicación de memoria es el comienzo de la matriz.
Para punteros en C, tenemos
a[5] == *(a + 5)
y también
5[a] == *(5 + a)
De ahí que sea cierto que a[5] == 5[a].
No es una respuesta, sino algo para pensar. Si la clase tiene un operador de índice / subíndice sobrecargado, la expresión 0[x]
no funcionará:
class Sub
{
public:
int operator [](size_t nIndex)
{
return 0;
}
};
int main()
{
Sub s;
s[0];
0[s]; // ERROR
}
Como no tenemos acceso a la clase int , esto no se puede hacer:
class int
{
int operator[](const Sub&);
};
Tiene muy buena explicación en UN TUTORIAL SOBRE PUNTEROS Y ARRAYES EN C de Ted Jensen.
Ted Jensen lo explicó como:
De hecho, esto es cierto, es decir, dondequiera que se escriba,
a[i]
se puede reemplazar*(a + i)
sin ningún problema. De hecho, el compilador creará el mismo código en cualquier caso. Por lo tanto, vemos que la aritmética de punteros es lo mismo que la indexación de matrices. Cualquiera de las dos sintaxis produce el mismo resultado.Esto NO significa que los punteros y las matrices sean lo mismo, no lo son. Solo estamos diciendo que para identificar un elemento dado de una matriz tenemos la opción de dos sintaxis, una usando indexación de matriz y la otra usando aritmética de puntero, que arrojan resultados idénticos.
Ahora, mirando esta última expresión, parte de ella ...
(a + i)
, es una simple adición usando el operador + y las reglas de C establecen que dicha expresión es conmutativa. Es decir (a + i) es idéntico a(i + a)
. Por lo tanto, podríamos escribir con la*(i + a)
misma facilidad que*(a + i)
. ¡Pero*(i + a)
podría haber venido dei[a]
! De todo esto surge la curiosa verdad de que si:char a[20];
escribiendo
a[3] = 'x';
es lo mismo que escribir
3[a] = 'x';
Sé que la pregunta está respondida, pero no pude resistirme a compartir esta explicación.
Recuerdo los principios del diseño del compilador, supongamos que a
es una int
matriz y el tamaño int
es de 2 bytes, y la dirección base para a
es 1000.
¿Cómo a[5]
funcionará? ->
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010
Asi que,
De manera similar, cuando el código c se divide en un código de 3 direcciones, 5[a]
se convertirá en ->
Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010
Así que, básicamente, tanto las declaraciones apuntan a la misma ubicación en la memoria y, por tanto, a[5] = 5[a]
.
Esta explicación es también la razón por la que los índices negativos en matrices funcionan en C.
es decir si a[-5]
accedo me dará
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990
Me devolverá el objeto en la ubicación 990.
En C matrices , arr[3]
y 3[arr]
son lo mismo, y sus notaciones de puntero equivalentes son *(arr + 3)
a *(3 + arr)
. Pero por el contrario [arr]3
o [3]arr
no es correcto y dará como resultado un error de sintaxis, ya que (arr + 3)*
y (3 + arr)*
no son expresiones válidas. La razón es que el operador de desreferencia debe colocarse antes de la dirección que proporciona la expresión, no después de la dirección.
en el compilador c
a[i]
i[a]
*(a+i)
¡Hay diferentes formas de referirse a un elemento en una matriz! (NO ES EXTRAÑO)
Un poco de historia ahora. Entre otros lenguajes, BCPL tuvo una influencia bastante importante en el desarrollo temprano de C. Si declaró una matriz en BCPL con algo como:
let V = vec 10
que en realidad asignó 11 palabras de memoria, no 10. Normalmente, V era la primera y contenía la dirección de la palabra inmediatamente siguiente. Entonces, a diferencia de C, nombrar V fue a esa ubicación y tomó la dirección del elemento cero de la matriz. Por lo tanto, la indirección de arreglos en BCPL, expresada como
let J = V!5
realmente tenía que hacerlo J = !(V + 5)
(usando la sintaxis BCPL) ya que era necesario buscar V para obtener la dirección base de la matriz. Así V!5
y 5!V
fueron sinónimos. Como observación anecdótica, WAFL (Warwick Functional Language) fue escrito en BCPL, y en lo mejor de mi memoria tendía a usar la última sintaxis en lugar de la primera para acceder a los nodos utilizados como almacenamiento de datos. De acuerdo, esto es de hace entre 35 y 40 años, así que mi memoria está un poco oxidada. :)
La innovación de prescindir de la palabra adicional de almacenamiento y hacer que el compilador insertara la dirección base de la matriz cuando se nombró llegó más tarde. Según el artículo de historia de C, esto sucedió aproximadamente en el momento en que se agregaron las estructuras a C.
Tenga !
en cuenta que en BCPL era un operador de prefijo unario y un operador de infijo binario, en ambos casos haciendo indirección. solo que la forma binaria incluía una suma de los dos operandos antes de realizar la indirección. Dada la naturaleza orientada a palabras de BCPL (y B), esto en realidad tenía mucho sentido. La restricción de "puntero y entero" se hizo necesaria en C cuando ganó tipos de datos y se sizeof
convirtió en una cosa.
Bueno, esta es una característica que solo es posible debido al soporte de idiomas.
El compilador interpreta a[i]
como *(a+i)
y la expresión se 5[a]
evalúa como *(5+a)
. Dado que la suma es conmutativa, resulta que ambos son iguales. Por lo tanto, la expresión se evalúa como true
.
Cía
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
El puntero p
es una "variable", el nombre de la matriz a
es un "mnemónico" o "sinónimo", por lo que p++
es válido pero a++
no es válido.
a[2]
es igual a 2[a]
porque la operación interna en ambos es "Aritmética de puntero" calculada internamente como *(a+2)
igual*(2+a)
La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.
Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.
Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.
Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.
Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?
¿Quiere probar un cepillo de dientes Sonicare sin gastar mucho dinero en uno de sus modelos favoritos de gama alta? Puede comprar un kit de la Serie 2 o Serie 3 por tan solo $ 30 hoy en Amazon. Haga clic aquí para ver la lista completa de modelos elegibles y tenga en cuenta que se descontarán $ 10 adicionales en su carrito.
Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.
Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.
Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.
El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.
Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon
El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.
El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".
Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.
Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.