El .
operador dot ( ) se usa para acceder a un miembro de una estructura, mientras que el operador de flecha ( ->
) en C se usa para acceder a un miembro de una estructura al que hace referencia el puntero en cuestión.
El puntero en sí no tiene ningún miembro al que se pueda acceder con el operador de punto (en realidad es solo un número que describe una ubicación en la memoria virtual, por lo que no tiene ningún miembro). Por lo tanto, no habría ambigüedad si solo definiéramos el operador de punto para desreferenciar automáticamente el puntero si se usa en un puntero (una información que el compilador conoce en tiempo de compilación afaik).
Entonces, ¿por qué los creadores del lenguaje han decidido complicar las cosas agregando este operador aparentemente innecesario? ¿Cuál es la gran decisión de diseño?
fuente
.
operador tiene mayor prioridad que el*
operador? Si no fuera así, podríamos tener * ptr.member y var.member.Respuestas:
Interpretaré su pregunta como dos preguntas: 1) por qué
->
existe, y 2) por qué.
no desreferencia automáticamente el puntero. Las respuestas a ambas preguntas tienen raíces históricas.¿Por qué
->
existe?En una de las primeras versiones de lenguaje C (que me referiré como CRM para " C Manual de referencia ", que vino con sexta edición Unix de mayo de 1975), el operador
->
tenía un significado muy exclusivo, no es sinónimo de*
y.
combinaciónEl lenguaje C descrito por CRM era muy diferente del C moderno en muchos aspectos. En CRM, los miembros de la estructura implementaron el concepto global de desplazamiento de bytes , que podría agregarse a cualquier valor de dirección sin restricciones de tipo. Es decir, todos los nombres de todos los miembros de la estructura tenían un significado global independiente (y, por lo tanto, tenían que ser únicos). Por ejemplo, podrías declarar
y nombre
a
representaría el desplazamiento 0, mientras que nombreb
representaría el desplazamiento 2 (suponiendo unint
tipo de tamaño 2 y sin relleno). El lenguaje requería que todos los miembros de todas las estructuras en la unidad de traducción tengan nombres únicos o representen el mismo valor de desplazamiento. Por ejemplo, en la misma unidad de traducción, también puede declarary eso estaría bien, ya que el nombre
a
significaría constantemente el desplazamiento 0. Pero esta declaración adicionalsería formalmente inválido, ya que intentó "redefinir"
a
como desplazamiento 2 yb
como desplazamiento 0.Y aquí es donde
->
entra el operador. Dado que cada nombre de miembro de estructura tiene su propio significado global autosuficiente, el lenguaje admite expresiones como estasEl compilador interpretó la primera asignación como "tomar dirección
5
, agregarle desplazamiento2
y asignarle42
elint
valor en la dirección resultante". Es decir, lo anterior asignaría42
alint
valor en la dirección7
. Tenga en cuenta que este uso de->
no le importaba el tipo de expresión en el lado izquierdo. El lado izquierdo se interpretó como una dirección numérica de valor (ya sea un puntero o un número entero).Este tipo de engaño no era posible con
*
y.
combinación. No podias hacerya que
*i
es una expresión inválida. El*
operador, dado que está separado de él.
, impone requisitos de tipo más estrictos en su operando. Para proporcionar una capacidad para evitar esta limitación, CRM introdujo el->
operador, que es independiente del tipo de operando de la izquierda.Como señaló Keith en los comentarios, esta diferencia entre
->
y*
+.
combinación es a lo que CRM se refiere como "relajación del requisito" en 7.1.8: Excepto por la relajación del requisito queE1
es de tipo puntero, la expresiónE1−>MOS
es exactamente equivalente a(*E1).MOS
Más tarde, en K&R C, muchas de las características descritas originalmente en CRM se modificaron significativamente. La idea de "miembro de estructura como identificador de desplazamiento global" se eliminó por completo. Y la funcionalidad del
->
operador se volvió completamente idéntica a la funcionalidad*
y la.
combinación.¿Por qué no puede
.
desreferenciar el puntero automáticamente?Una vez más, en la versión de CRM de la lengua el operando de la izquierda
.
se requiere operador para ser un valor-I . Ese fue el único requisito impuesto a ese operando (y eso es lo que lo hizo diferente->
, como se explicó anteriormente). Tenga en cuenta que CRM no requería que el operando izquierdo de.
tuviera un tipo de estructura. Solo requería que fuera un valor, cualquier valor . Esto significa que en la versión CRM de C podría escribir código como esteEn este caso, el compilador escribiría
55
en unint
valor posicionado en byte-offset 2 en el bloque de memoria continua conocido comoc
, a pesar de que el tipostruct T
no tenía un campo nombradob
. Al compilador no le importaría el tipo real dec
nada. Lo único que le importabac
era que fuera un valor: algún tipo de bloque de memoria grabable.Ahora tenga en cuenta que si hiciste esto
el código se consideraría válido (ya
s
que también es un lvalue) y el compilador simplemente intentaría escribir datos en el puntero ens
sí , en byte-offset 2. No hace falta decir que cosas como esta podrían resultar en desbordamiento de memoria, pero el lenguaje no se preocupó por tales asuntos.Es decir, en esa versión del lenguaje, su idea propuesta sobre la sobrecarga del operador
.
para los tipos de puntero no funcionaría: el operador.
ya tenía un significado muy específico cuando se usaba con punteros (con punteros de valor o con cualquier valor). Era una funcionalidad muy extraña, sin duda. Pero estaba allí en ese momento.Por supuesto, esta funcionalidad extraña no es una razón muy fuerte contra la introducción de un
.
operador sobrecargado para punteros (como usted sugirió) en la versión reelaborada de C - K&R C. Pero no se ha hecho. Quizás en ese momento había algún código heredado escrito en la versión CRM de C que tenía que ser compatible.(La URL para el Manual de referencia de 1975 C puede no ser estable. Otra copia, posiblemente con algunas diferencias sutiles, está aquí ).
fuente
*i
ser un valor de algún tipo predeterminado (int?) En la dirección 5? Entonces (* i) .b habría funcionado de la misma manera.struct stat
) prefijan sus campos (por ejemplo,st_mode
).Más allá de las razones históricas (buenas y ya informadas), también hay un pequeño problema con la precedencia de los operadores: el operador punto tiene mayor prioridad que el operador estrella, por lo que si tiene una estructura que contiene un puntero a una estructura que contiene un puntero a una estructura ... Estos dos son equivalentes:
Pero el segundo es claramente más legible. El operador de flecha tiene la máxima prioridad (igual que el punto) y se asocia de izquierda a derecha. Creo que esto es más claro que usar el operador de punto tanto para los punteros como para estructurar y estructurar, porque conocemos el tipo de la expresión sin tener que mirar la declaración, que incluso podría estar en otro archivo.
fuente
a.b.c.d
como(*(*(*a).b).c).d
, haciendo que el->
operador inútil. Entonces, la versión del OP (a.b.c.d
) es igualmente legible (en comparación cona->b->c->d
). Es por eso que su respuesta no responde la pregunta del OP.a.b.c.d
ya->b->c->d
como dos cosas muy diferentes: la primera es un acceso de memoria único a un subobjeto anidado (en este caso, solo hay un único objeto de memoria) ), el segundo es tres accesos a la memoria, persiguiendo punteros a través de cuatro objetos distintos. Esa es una gran diferencia en el diseño de la memoria, y creo que C tiene razón al distinguir estos dos casos de manera muy visible.C también hace un buen trabajo al no hacer nada ambiguo.
Seguro que el punto podría estar sobrecargado para significar ambas cosas, pero la flecha se asegura de que el programador sepa que está operando con un puntero, al igual que cuando el compilador no le permite mezclar dos tipos incompatibles.
fuente