c# - punteros - referencias c++
¿Cuál es la diferencia entre una referencia C#y un puntero? (10)
Creo que es importante que los desarrolladores entiendan el concepto de un puntero, es decir, para comprender la indirección. Eso no significa que necesariamente tengan que usar punteros. También es importante entender 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 simplemente un bloque de memoria del tamaño de un puntero que sostiene un puntero al objeto. Sin embargo, esta variable no se puede usar de la misma manera que se puede usar una variable de puntero. En C # (y C, y C ++, ...), un puntero puede indexarse como una matriz, pero una referencia no. En C #, el recolector de basura rastrea una referencia, un puntero no puede ser. En C ++, un puntero puede reasignarse, una referencia no puede. Sintáctica y semánticamente, los punteros y las referencias son bastante diferentes, pero mecánicamente son lo mismo.
No entiendo muy bien la diferencia entre una referencia C # y un puntero. Ambos señalan un lugar en la memoria, ¿no? La única diferencia que puedo deducir es que los punteros no son tan ingeniosos, 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 de 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. Mucha gente que aprende idiomas de alto nivel se pierde esto y, por lo tanto, tiene esta debilidad.
Simplemente no entiendo qué es tan complejo sobre un puntero? Básicamente es solo una referencia a un lugar en la memoria ¿no es así? ¿Puede devolver su ubicación e interactuar directamente con el objeto en esa ubicación?
¿Me he perdido un punto masivo?
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" en el sentido de que no se puede garantizar la exactitud de la memoria que señalan.
Tomemos por ejemplo el siguiente código
int* p1 = GetAPointer();
Esto es 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 realmente apunte a un int. Podría ser un char, doble o simplemente un puntero en la 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 un código no seguro). Las referencias son mucho más seguras a este respecto que los indicadores.
string str = GetAString();
En este caso, str tiene uno de dos estados 1) apunta a ningún objeto y, por lo 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 quiere para un puntero.
Lo que hace que los indicadores sean algo complejos no es lo que son, sino lo que puedes hacer con ellos. Y cuando tiene un puntero a un puntero a un puntero. Ahí es cuando realmente comienza a divertirse.
Los punteros apuntan a una ubicación en el espacio de direcciones de memoria. Las referencias apuntan a una estructura de datos. Las estructuras de datos se movieron todo el tiempo (bueno, no con frecuencia, pero de vez en cuando) por el recolector de basura (para compactar el espacio de la 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 usar en un contexto inseguro.
Primero, creo que debes definir un "Puntero" en tus sematics. ¿Te refieres al puntero que puedes crear en un código inseguro con fixed ? ¿Te refieres a un IntPtr que obtienes de 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 el registro, 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 la capacidad de administrar la memoria o la vida del objeto. Eso significa que usted pierde todos los beneficios de un sistema de recolección de basura. Debe administrar manualmente la memoria de objeto y tiene la posibilidad de fugas.
Una referencia, por otro lado, es más o menos un "puntero administrado" que el GC conoce. Todavía es una dirección de un objeto, pero ahora el GC conoce los detalles del objetivo, por lo que puede moverlo, hacer compactaciones, finalizar, eliminar y todas las demás cosas buenas que hace un entorno administrado.
La diferencia principal, realmente, es en cómo y por qué los usarías. Para la gran mayoría de los casos en un lenguaje administrado, vas a utilizar 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, este es un buen ejemplo de cuándo podría usar un "puntero" en el código administrado, en este caso es un GCHandle, pero exactamente lo mismo se podría haber hecho con AllocHGlobal o utilizando fixed en una matriz de bytes o struct. Tiendo a preferir el GCHandle porque se siente más ".NET" para mí.
Un puntero puede señalar cualquier byte en el espacio de direcciones de la aplicación. Una referencia está estrechamente restringida y controlada y administrada por el entorno .NET.
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 un puntero, mientras que una referencia encapsula no solo un conjunto de bits, sino también algunos metadatos que mantienen el 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 continuada 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, a nada le importará su existencia. Por el contrario, los marcos basados en referencia como .NET o JVM requieren que siempre sea posible que el sistema identifique cada referencia de objeto en existencia, y cada referencia de objeto en existencia siempre debe ser null
o identificar un objeto de su tipo apropiado .
Tenga en cuenta que cada referencia de objeto en realidad encapsula dos tipos de información: (1) el contenido del campo del objeto que identifica, y (2) el conjunto de otras referencias al mismo objeto. Aunque no existe ningún mecanismo por el cual el sistema pueda identificar rápidamente todas las referencias que existen para un objeto, el conjunto de otras referencias que existen para 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 tokens de bloqueo). Aunque el sistema mantiene unos pocos bits de datos para cada objeto para usar en GetHashCode
, los objetos no tienen una identidad real más allá del conjunto de referencias que les existen. Si X
contiene la única referencia existente a un objeto, reemplazar X
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 por GetHashCode()
, e incluso ese efecto no está garantizado.
Una referencia es un puntero "abstracto": no se puede hacer aritmética con una referencia y no se puede jugar ningún trucos de bajo nivel con su valor.
Uno de los mayores beneficios de las referencias sobre punteros es una mayor simplicidad y legibilidad. Como siempre, cuando simplificas algo, lo haces más fácil de usar, pero a costa de la flexibilidad y el control, obtienes 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 desreferenciarlo
myClass->Method() or (*myClass).Method()
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 el valor) y por la ganancia de rendimiento de no tener que copiar objetos grandes.
Para mí, esta es la razón por la cual las referencias ''nacieron'' en primer lugar para proporcionar el mismo beneficio que los punteros, pero sin toda esa sintaxis del puntero. Ahora puede pasar el objeto real (no solo su valor) Y tiene una forma más legible y normal de interactuar con el objeto.
MyMethod(&type parameter)
{
parameter.DoThis()
parameter.DoThat()
}
Las referencias de C ++ difieren de las referencias de C # / Java en que una vez que se le asigna un valor, no se puede volver a asignar (y se debe asignar cuando se declaró). Esto era lo mismo que usar un puntero const (un puntero que no se podía volver a apuntar a otro objeto).
Java y C # son lenguajes modernos de 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 tenían que ser ''limpiadas''.
En cuanto a su comentario sobre conocer los indicadores, lo convierte en un programador más fuerte, esto es cierto en la mayoría de los casos. Si sabes "cómo" funciona algo en lugar de solo usarlo sin saber, diría que esto a menudo puede darte una ventaja. ¿Cuánto de un borde 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 con los indicadores sobre 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ás pasando por valor Bueno, para empezar, cuando usas un puntero, sabes que el puntero solo contiene una dirección, eso es todo. La variable en sí está casi vacía y es por eso que es tan bueno pasar 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 tiene una referencia que es un tipo de interfaz y apunta a un objeto, solo puede llamar a los 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 entiendes bien estos conceptos, no creo que te falte demasiado por no haber usado punteros. C ++ se usa a menudo como un lenguaje para aprender programación porque a veces es bueno ensuciarse las manos. Además, trabajar con aspectos de menor nivel te hace apreciar las comodidades de un lenguaje moderno. Empecé con C ++ y ahora soy un programador de C # y tengo ganas de trabajar con punteros crudos que me han ayudado a tener una mejor comprensión de lo que sucede bajo el capó.
No creo que sea necesario que todos comiencen con punteros, pero lo importante es que entiendan por qué se usan referencias en lugar de tipos de valores y la mejor manera de entender eso es mirar a su antecesor, el puntero.
Las referencias de C # pueden ser reubicadas por el recolector de basura pero los punteros normales son estáticos. Esta es la razón por la que usamos palabras clave fixed
cuando adquirimos un puntero a un elemento de matriz, para evitar que se mueva.
EDITAR: Conceptualmente, sí. Son más o menos lo mismo.