operadores - ¿Por qué C tiene una distinción entre-> y.?
operadores relacionales en c++ (7)
OK, esto no tiene consecuencias serias, pero me ha estado molestando por un tiempo: ¿Hay alguna razón para la distinción entre ->
y .
operadores?
Por supuesto, la regla actual es esa .
actúa sobre una estructura, y ->
actúa sobre un puntero-a-estructura (o unión). Pero así es como funciona en la práctica. Seamos una estructura que incluye un elemento x
, y ps
sea un puntero a una estructura de la misma forma.
Si tú escribes
s->x
el compilador escupirá una advertencia en el camino de
Querías decir sx. Por favor, vuelve a escribir eso y vuelve a compilar.
Si tú escribes
ps.x
el compilador escupirá una advertencia en el camino de
Querías decir ps-> x. Por favor, vuelva a escribir eso y vuelva a compilar.
Como el compilador conoce el tipo de s
y ps
en tiempo de compilación, tiene toda la información que necesita para interpretar cuál sería el operador correcto. Sospecho que esto no es como otras advertencias (como un punto y coma faltante), ya que no hay ambigüedad sobre la corrección correcta.
Así que aquí hay una propuesta hipotética para el comité de estándares C1x (que nunca se consideraría, porque el ISO está en una racha conservadora):
Dada la expresión lhs.rhs, si lhs es una estructura o tipo de unión, la expresión se referirá al elemento de lhs llamado rhs. Si lhs es de tipo pointer-to-struct o -union, entonces esto se interpretará como (* lhs) .rhs.
Esto nos ahorraría todo el tiempo, y facilitaría que las personas aprendieran C [y he enseñado lo suficiente como para decir con autoridad que los estudiantes encuentran que ->
es confuso o molesto].
Incluso hay precedentes, donde C hace un puñado de cosas similares. Por ejemplo, por razones de implementación, las declaraciones de funciones siempre se convierten en puntero a función, entonces f(x,y)
y (*f)(x,y)
funcionarán independientemente de si f
se declaró como una función o un puntero funcionar.
Entonces, mi pregunta: ¿qué pasa con esta propuesta? ¿Puedes pensar en ejemplos donde habría una ambigüedad fatal entre ps.x
y sx
, o por qué mantener la distinción obligatoria es útil de otro modo?
Sí, está bien, pero no es lo que C realmente necesita en absoluto
No solo está bien, sino que es el estilo moderno. Java y Go solo usan .
. Como todo lo que no cabe en un registro es en cierto nivel una referencia, la distinción entre cosa y puntero a cosa es definitivamente un poco arbitraria, al menos hasta que se llega a las llamadas de función.
El primer paso evolutivo fue hacer que el operador de desreferencia fuera postfijo, algo que una vez dmr implicó que en algún momento prefirió. Pascal hace esto, entonces tiene p^.field
. La única razón por la que incluso hay un operador ->
es porque es ridículo tener que escribir (*p).field
o p[0].field
.
Entonces sí, funcionaría. Sería incluso mejor ya que funciona a un nivel más alto de abstracción. Uno realmente debería ser capaz de hacer tantos cambios como sea posible sin necesidad de cambiar el código descendente, que en cierto sentido es el punto de los lenguajes de nivel superior.
He argumentado que el uso de ()
para llamadas a funciones y []
para la suscripción a matrices es incorrecto. ¿Por qué no permitir diferentes implementaciones para exportar diferentes abstracciones?
Pero no hay muchas razones para hacer el cambio. Es improbable que los programadores C se rebelen por la falta de una extensión sintáctica de azúcar que ahorra un carácter en una expresión y que sería difícil de usar de todos modos porque no sería adoptada de forma inmediata o universal. Recuerde que cuando los comités de estándares se vuelven pícaros, terminan predicando en habitaciones vacías. Requieren la cooperación voluntaria y el acuerdo de los desarrolladores de compiladores del mundo.
Lo que realmente necesita C no son formas cada vez más rápidas de escribir códigos inseguros. No me importa trabajar en C, pero a los gerentes de proyecto no les gusta que su peor tipo determine su confiabilidad, y es posible que lo que realmente necesita C sea un dialecto seguro, algo así como Cyclone , o tal vez algo como Go .
Bueno, claramente no hay ambigüedad o la propuesta no pudo hacerse. El único problema es que si ves:
p->x = 3;
sabes que p
es un puntero pero si permites:
p.x = 3;
en esa circunstancia, entonces, en realidad no lo sabe, lo que podría crear problemas, especialmente si más tarde lanza ese puntero y utiliza la cantidad incorrecta de niveles de indirección.
Bueno, definitivamente podría haber casos en los que tenga algo complejo como:
(*item)->elem
(que he tenido en algunos programas), y si escribiste algo así como
item.elem
es decir, lo anterior, podría ser confuso si elem es un elemento de struct item, o un elemento de una estructura a la que apunta el elemento, o un elemento de una estructura que apunta a ser un elemento en una lista apuntada por un elemento elemento iterador, y así sucesivamente.
Así que sí, hace las cosas un poco más claras cuando se usan punteros para hacer punteros a las estructuras, etc.
Bueno, si realmente deseaba introducir ese tipo de funcionalidad en la especificación del lenguaje C, entonces, para hacerlo "combinar" con el resto del lenguaje, lo lógico sería ampliar el concepto de "decaimiento a puntero". "para estructurar tipos. Usted mismo hizo un ejemplo con una función y un puntero de función. La razón por la que funciona de esa manera es porque el tipo de función en C decae al tipo de puntero en todos los contextos, excepto para sizeof
y unary &
operators. (Lo mismo sucede con las matrices, por cierto).
Entonces, para implementar algo similar a lo que sugiere, podríamos introducir el concepto de "decaimiento de estructura a puntero", que funcionaría exactamente de la misma manera que todos los otros "decaimientos" en C (es decir, matriz-a decaimiento de puntero y decaimiento de función a puntero): cuando se utiliza un objeto struct de tipo T
en una expresión, su tipo decae inmediatamente para escribir T*
- puntero al principio del objeto struct - excepto cuando es un operando de sizeof
o unario &
. Una vez que se introduce una regla de desintegración para las estructuras, puede usar ->
operador para acceder a los elementos de la estructura, independientemente de si tiene un puntero a struct o la propia estructura en el lado izquierdo. Operador sería completamente innecesario en este caso (a menos que me falta algo), siempre usaría ->
y solo ->
.
Lo anterior, una vez más, cómo se vería esta característica, en mi opinión, si se implementó en el espíritu del lenguaje C.
Pero yo diría (estoy de acuerdo con lo que dijo Charles) que la pérdida de distinción visual entre el código que funciona con punteros a las estructuras y el código que funciona con las estructuras mismas no es exactamente deseable.
PD: una consecuencia negativa obvia de tal regla de decaimiento para las estructuras sería que además del actual ejército de novatos desinteresadamente creyendo que "las matrices son solo punteros constantes", tendríamos un ejército de novatos desinteresadamente creyendo que "los objetos de estructura son solo punteros constantes". ". Y las preguntas frecuentes de la matriz de Chris Torek tendrían que ser aproximadamente 1.5-2x más grandes para cubrir las estructuras también :)
En todo caso, la sintaxis actual le permite a los lectores del código saber si el código funciona o no con un puntero o el objeto real. Alguien que no conoce el código de antemano lo entiende mejor.
No creo que haya nada loco por lo que has dicho. Usando .
para los punteros a las estructuras funcionaría.
Sin embargo, me gusta el hecho de que los punteros a las estructuras y las estructuras se tratan de manera diferente.
Da un contexto sobre las operaciones y pistas sobre lo que podría ser costoso.
Considere este fragmento, imagine que está en el medio de una función razonablemente grande.
s.c = 99;
f(s);
assert(s.c == 99);
Actualmente puedo decir que s
es una estructura. Sé que se va a copiar en su totalidad para la llamada a f
. También sé que esa afirmación no puede disparar.
Si usas con punteros a struct permitidos, no sabría nada de eso y la afirmación podría disparar, f
podría establecer sc
(err s->c
) en otra cosa.
El otro inconveniente es que reduciría la compatibilidad con C ++. C ++ permite ->
estar sobrecargado por clases para que las clases puedan ser punteros ''como''. Es importante que y ->
comportarse de manera diferente. "Nuevo" código C que se usó .
con punteros a estructuras probablemente ya no sería aceptable como código C ++.
Una característica distintiva del lenguaje de programación C (a diferencia de su C ++ relativo) es que el modelo de costo es muy explícito . El punto se distingue de la flecha porque la flecha requiere una referencia de memoria adicional, y C tiene mucho cuidado de hacer evidente el número de referencias de memoria del código fuente.