No entiendo muy bien la diferencia entre una referencia de C # y un puntero. Ambos señalan un lugar en la memoria, ¿no es así? La única diferencia que puedo descubrir es que los punteros no son tan inteligentes, no pueden apuntar a nada en el montón, están exentos de la recolección de basura y solo pueden hacer referencia a estructuras o tipos base.
Una de las razones por las que pregunto es que existe la percepción de que la gente necesita entender bien los indicadores (de C, supongo) para ser un buen programador. Muchas personas que aprenden idiomas de nivel superior se pierden esto y, por lo tanto, tienen esta debilidad.
Simplemente no entiendo qué tiene de complejo un puntero. Básicamente es solo una referencia a un lugar en la memoria, ¿no es así? ¿Puede devolver su ubicación e interactuar con el objeto en esa ubicación directamente?
¿Me he perdido un punto importante?
Respuestas:
Las referencias de C # pueden, y serán reubicadas por el recolector de basura, pero los punteros normales son estáticos. Es por eso que usamos
fixed
palabras clave cuando adquirimos un puntero a un elemento de matriz, para evitar que se mueva.EDITAR: Conceptualmente, sí. Son más o menos iguales.
fuente
Existe una distinción leve, pero extremadamente importante, entre un puntero y una referencia. Un puntero apunta a un lugar en la memoria mientras que una referencia apunta a un objeto en la memoria. Los punteros no son "seguros para escribir" en el sentido de que no se puede garantizar la exactitud de la memoria a la que apuntan.
Tomemos, por ejemplo, el siguiente código
int* p1 = GetAPointer();
Este es un tipo seguro en el sentido de que GetAPointer debe devolver un tipo compatible con int *. Sin embargo, todavía no hay garantía de que * p1 apunte realmente a un int. Podría ser un char, doble o simplemente un puntero a una memoria aleatoria.
Sin embargo, una referencia apunta a un objeto específico. Los objetos se pueden mover en la memoria, pero la referencia no se puede invalidar (a menos que use código inseguro). Las referencias son mucho más seguras a este respecto que los punteros.
string str = GetAString();
En este caso, str tiene uno de dos estados: 1) no apunta a ningún objeto y, por tanto, es nulo o 2) apunta a una cadena válida. Eso es. El CLR garantiza que este sea el caso. No puede y no lo hará para un puntero.
fuente
Una referencia es un puntero "abstracto": no puedes hacer aritmética con una referencia y no puedes hacer trucos de bajo nivel con su valor.
fuente
Una diferencia importante entre una referencia y un puntero es que un puntero es una colección de bits cuyo contenido solo importa cuando se usa activamente como puntero, mientras que una referencia encapsula no solo un conjunto de bits, sino también algunos metadatos que mantienen la marco subyacente informado de su existencia. Si existe un puntero a algún objeto en la memoria, y ese objeto se elimina pero el puntero no se borra, la existencia continua del puntero no causará ningún daño a menos que o hasta que se intente acceder a la memoria a la que apunta. Si no se intenta utilizar el puntero, nada se preocupará por su existencia. Por el contrario, los marcos basados en referencias como .NET o la JVM requieren que siempre sea posible que el sistema identifique cada referencia de objeto existente, y cada referencia de objeto existente debe ser siempre
null
o bien identificar un objeto de su tipo adecuado.Tenga en cuenta que cada referencia de objeto en realidad encapsula dos tipos de información: (1) el contenido de campo del objeto que identifica y (2) el conjunto de otras referencias al mismo objeto. Aunque no existe ningún mecanismo mediante el cual el sistema pueda identificar rápidamente todas las referencias que existen a un objeto, el conjunto de otras referencias que existen a un objeto a menudo puede ser lo más importante encapsulado por una referencia (esto es especialmente cierto cuando las cosas de tipo
Object
se usan como cosas como fichas de bloqueo). Aunque el sistema guarda algunos bits de datos para cada objeto para su usoGetHashCode
, los objetos no tienen una identidad real más allá del conjunto de referencias que existen a ellos. SiX
tiene la única referencia existente a un objeto, reemplazandoX
con una referencia a un nuevo objeto con el mismo contenido de campo no tendrá ningún efecto identificable excepto para cambiar los bits devueltos porGetHashCode()
, e incluso ese efecto no está garantizado.fuente
Los punteros apuntan a una ubicación en el espacio de direcciones de la memoria. Las referencias apuntan a una estructura de datos. Todas las estructuras de datos se mueven todo el tiempo (bueno, no tan a menudo, pero de vez en cuando) por el recolector de basura (para compactar el espacio de memoria). Además, como dijiste, las estructuras de datos sin referencias obtendrán basura recolectada después de un tiempo.
Además, los punteros solo se pueden utilizar en contextos no seguros.
fuente
Creo que es importante para los desarrolladores comprender el concepto de puntero, es decir, comprender la indirección. Eso no significa que necesariamente tengan que usar punteros. También es importante comprender que el concepto de referencia difiere del concepto de puntero , aunque solo sutilmente, pero que la implementación de una referencia casi siempre es un puntero.
Es decir, una variable que contiene una referencia es solo un bloque de memoria del tamaño de un puntero que sostiene un puntero al objeto. Sin embargo, esta variable no se puede utilizar de la misma forma que se puede utilizar una variable de puntero. En C # (y C, y C ++, ...), un puntero se puede indexar como una matriz, pero una referencia no. En C #, el recolector de elementos no utilizados realiza un seguimiento de una referencia, no un puntero. En C ++, se puede reasignar un puntero, no una referencia. Sintácticamente y semánticamente, los punteros y las referencias son bastante diferentes, pero mecánicamente son lo mismo.
fuente
Primero creo que necesitas definir un "Puntero" en tu semántica. ¿Te refieres al puntero que puedes crear en código inseguro con fijo ? ¿Te refieres a un IntPtr que recibes de quizás una llamada nativa o Marshal.AllocHGlobal ? ¿Te refieres a un GCHandle ? Todos son esencialmente lo mismo, una representación de una dirección de memoria donde se almacena algo, ya sea una clase, un número, una estructura, lo que sea. Y para que conste, ciertamente pueden estar en el montón.
Un puntero (todas las versiones anteriores) es un elemento fijo. El GC no tiene idea de qué hay en esa dirección y, por lo tanto, no tiene capacidad para administrar la memoria o la vida del objeto. Eso significa que pierde todos los beneficios de un sistema de recolección de basura. Debe administrar manualmente la memoria del objeto y existe la posibilidad de que se produzcan fugas.
Una referencia, por otro lado, es prácticamente un "puntero administrado" que el GC conoce. Sigue siendo una dirección de un objeto, pero ahora el GC conoce los detalles del objetivo, por lo que puede moverlo, hacer compactaciones, finalizar, desechar y todas las demás cosas buenas que hace un entorno administrado.
La principal diferencia, en realidad, está en cómo y por qué los usaría. Para la gran mayoría de los casos en un lenguaje administrado, usará una referencia de objeto. Los punteros se vuelven útiles para hacer interoperabilidad y la rara necesidad de un trabajo realmente rápido.
Editar: De hecho, aquí hay un buen ejemplo de cuándo puede usar un "puntero" en el código administrado; en este caso, es un GCHandle, pero exactamente lo mismo podría haberse hecho con AllocHGlobal o usando fijo en una matriz de bytes o estructura. Tiendo a preferir GCHandle porque me parece más ".NET".
fuente
Un puntero puede apuntar a cualquier byte en el espacio de direcciones de la aplicación. Una referencia está estrictamente restringida y controlada y administrada por el entorno .NET.
fuente
Lo que tienen los punteros que los hace algo complejos no es lo que son, sino lo que puedes hacer con ellos. Y cuando tienes un puntero a un puntero a un puntero. Ahí es cuando realmente comienza a divertirse.
fuente
Uno de los mayores beneficios de las referencias sobre los punteros es una mayor simplicidad y legibilidad. Como siempre, cuando simplifica algo, lo hace más fácil de usar, pero a costa de la flexibilidad y el control que obtiene con las cosas de bajo nivel (como han mencionado otras personas).
Los punteros a menudo son criticados por ser "feos".
class* myClass = new class();
Ahora, cada vez que lo use, primero debe eliminar la referencia
A pesar de perder algo de legibilidad y agregar complejidad, las personas aún necesitaban usar punteros a menudo como parámetros para poder modificar el objeto real (en lugar de pasar por valor) y para obtener una ganancia de rendimiento al no tener que copiar objetos grandes.
Para mí, esta es la razón por la que las referencias 'nacieron' en primer lugar para proporcionar el mismo beneficio que los punteros pero sin toda esa sintaxis de punteros. Ahora puede pasar el objeto real (no solo su valor) Y tiene una forma más legible y normal de interactuar con el objeto.
Las referencias de C ++ difieren de las referencias de C # / Java en que una vez que le asigna un valor que era, no puede reasignarlo (y debe asignarse cuando se declaró). Esto era lo mismo que usar un puntero constante (un puntero que no se podía volver a apuntar a otro objeto).
Java y C # son lenguajes modernos de muy alto nivel que limpiaron muchos de los líos que se habían acumulado en C / C ++ a lo largo de los años y los punteros eran definitivamente una de esas cosas que necesitaban ser 'limpiadas'.
En la medida en que su comentario acerca de conocer los punteros lo convierte en un programador más fuerte, esto es cierto en la mayoría de los casos. Si sabe 'cómo' funciona algo en lugar de simplemente usarlo sin saberlo, diría que esto a menudo puede darle una ventaja. La cantidad de ventaja siempre variará. Después de todo, usar algo sin saber cómo se implementa es una de las muchas bellezas de OOP e Interfaces.
En este ejemplo específico, ¿qué le ayudaría saber sobre punteros con las referencias? Comprender que una referencia de C # NO es el objeto en sí, sino que apunta al objeto, es un concepto muy importante.
# 1: NO está pasando por valor Bueno, para empezar, cuando usa un puntero, sabe que el puntero contiene solo una dirección, eso es todo. La variable en sí está casi vacía y por eso es tan agradable pasarla como argumentos. Además de la ganancia de rendimiento, está trabajando con el objeto real, por lo que los cambios que realice no son temporales
# 2: Polimorfismo / Interfaces Cuando tienes una referencia que es un tipo de interfaz y apunta a un objeto, solo puedes llamar a métodos de esa interfaz aunque el objeto tenga muchas más habilidades. Los objetos también pueden implementar los mismos métodos de manera diferente.
Si comprende bien estos conceptos, no creo que se esté perdiendo demasiado por no haber utilizado punteros. C ++ se usa a menudo como lenguaje para aprender a programar porque a veces es bueno ensuciarse las manos. Además, trabajar con aspectos de nivel inferior te hace apreciar las comodidades de un lenguaje moderno. Comencé con C ++ y ahora soy un programador de C # y siento que trabajar con punteros en bruto me ha ayudado a comprender mejor lo que sucede debajo del capó.
No creo que sea necesario que todos comiencen con punteros, pero lo importante es que comprendan por qué se usan referencias en lugar de tipos de valor y la mejor manera de entenderlo es mirar a su ancestro, el puntero.
fuente
.
usaran->
, perofoo.bar(123)
fuera sinónimo de una llamada al método estáticofooClass.bar(ref foo, 123)
. Eso habría permitido cosas comomyString.Append("George")
; [que modificaría la variablemyString
], y hizo más obvia la diferencia de significado entremyStruct.field = 3;
ymyClassObject->field = 3;
.