Ich verstehe den Unterschied zwischen einer C # -Referenz und einem Zeiger nicht ganz. Sie zeigen beide auf einen Ort in der Erinnerung, nicht wahr? Der einzige Unterschied, den ich herausfinden kann, ist, dass Zeiger nicht so clever sind, auf nichts auf dem Heap verweisen können, von der Garbage Collection ausgenommen sind und nur auf Strukturen oder Basistypen verweisen können.
Einer der Gründe, die ich frage, ist die Wahrnehmung, dass Menschen Zeiger gut verstehen müssen (von C, denke ich), um ein guter Programmierer zu sein. Viele Leute, die höhere Sprachen lernen, verpassen dies und haben daher diese Schwäche.
Ich verstehe einfach nicht, was an einem Zeiger so komplex ist? Es ist im Grunde nur ein Hinweis auf einen Ort in der Erinnerung, nicht wahr? Kann es seinen Standort zurückgeben und direkt mit dem Objekt an diesem Standort interagieren?
Habe ich einen massiven Punkt verpasst?
Antworten:
C # -Referenzen können und werden vom Garbage Collector verschoben, normale Zeiger sind jedoch statisch. Aus diesem Grund verwenden wir
fixed
beim Erfassen eines Zeigers auf ein Array-Element ein Schlüsselwort, um zu verhindern, dass es verschoben wird.EDIT: Konzeptionell ja. Sie sind mehr oder weniger gleich.
quelle
Es gibt eine leichte, aber äußerst wichtige Unterscheidung zwischen einem Zeiger und einer Referenz. Ein Zeiger zeigt auf eine Stelle im Speicher, während eine Referenz auf ein Objekt im Speicher zeigt. Zeiger sind nicht "typsicher" in dem Sinne, dass Sie die Richtigkeit des Speichers, auf den sie zeigen, nicht garantieren können.
Nehmen Sie zum Beispiel den folgenden Code
Dies ist typsicher in dem Sinne, dass GetAPointer einen mit int * kompatiblen Typ zurückgeben muss. Es gibt jedoch noch keine Garantie dafür, dass * p1 tatsächlich auf ein int verweist. Es kann ein Zeichen, ein Doppel oder nur ein Zeiger in den zufälligen Speicher sein.
Eine Referenz verweist jedoch auf ein bestimmtes Objekt. Objekte können im Speicher verschoben werden, aber die Referenz kann nicht ungültig gemacht werden (es sei denn, Sie verwenden unsicheren Code). Referenzen sind in dieser Hinsicht viel sicherer als Zeiger.
In diesem Fall hat str einen von zwei Zuständen 1) es zeigt auf kein Objekt und ist daher null oder 2) es zeigt auf einen gültigen String. Das ist es. Die CLR garantiert dies. Es kann und wird nicht für einen Zeiger.
quelle
Eine Referenz ist ein "abstrakter" Zeiger: Sie können mit einer Referenz nicht rechnen und mit ihrem Wert keine Streiche auf niedriger Ebene spielen.
quelle
Ein Hauptunterschied zwischen einer Referenz und einem Zeiger besteht darin, dass ein Zeiger eine Sammlung von Bits ist, deren Inhalt nur dann von Bedeutung ist, wenn er aktiv als Zeiger verwendet wird, während eine Referenz nicht nur einen Satz von Bits, sondern auch einige Metadaten enthält, die die zugrunde liegender Rahmen über seine Existenz informiert. Wenn ein Zeiger auf ein Objekt im Speicher vorhanden ist und dieses Objekt gelöscht wird, der Zeiger jedoch nicht gelöscht wird, verursacht das Fortbestehen des Zeigers keinen Schaden, es sei denn oder bis versucht wird, auf den Speicher zuzugreifen, auf den er verweist. Wenn nicht versucht wird, den Zeiger zu verwenden, kümmert sich nichts um seine Existenz. Im Gegensatz dazu erfordern referenzbasierte Frameworks wie .NET oder die JVM, dass das System immer jede vorhandene Objektreferenz identifizieren kann, und jede vorhandene Objektreferenz muss immer entweder vorhanden sein
null
oder identifizieren Sie ein Objekt seines richtigen Typs.Beachten Sie, dass jede Objektreferenz tatsächlich zwei Arten von Informationen enthält: (1) den Feldinhalt des Objekts, das sie identifiziert, und (2) die Menge anderer Referenzen auf dasselbe Objekt. Obwohl es keinen Mechanismus gibt, mit dem das System alle auf ein Objekt vorhandenen Referenzen schnell identifizieren kann, ist die Menge der anderen Referenzen, die auf ein Objekt existieren, häufig das Wichtigste, was in einer Referenz enthalten ist (dies gilt insbesondere dann, wenn Dinge vom Typ
Object
werden als Dinge wie Sperren verwendet. Obwohl das System einige Datenbits für jedes Objekt zur Verwendung in speichertGetHashCode
, haben Objekte keine echte Identität, die über den Satz von Referenzen hinausgeht, die auf sie existieren. WennX
der einzige noch vorhandene Verweis auf ein Objekt vorhanden ist, wird ersetztX
Bei einem Verweis auf ein neues Objekt mit demselben Feldinhalt hat dies keinen erkennbaren Effekt, außer dass die von zurückgegebenen Bits geändert werdenGetHashCode()
, und selbst dieser Effekt ist nicht garantiert.quelle
Zuerst denke ich, dass Sie einen "Zeiger" in Ihrer Sematik definieren müssen. Meinen Sie den Zeiger, den Sie in unsicherem Code mit festem erstellen können ? Meinen Sie einen IntPtr , den Sie möglicherweise von einem nativen Anruf oder von Marshal.AllocHGlobal erhalten ? Meinst du einen GCHandle ? Alle sind im Wesentlichen dasselbe - eine Darstellung einer Speicheradresse, in der etwas gespeichert ist - sei es eine Klasse, eine Zahl, eine Struktur, was auch immer. Und für die Aufzeichnung können sie sicherlich auf dem Haufen sein.
Ein Zeiger (alle oben genannten Versionen) ist ein fester Punkt. Der GC hat keine Ahnung, was sich an dieser Adresse befindet, und kann daher den Speicher oder die Lebensdauer des Objekts nicht verwalten. Das bedeutet, dass Sie alle Vorteile eines Müllsammelsystems verlieren. Sie müssen den Objektspeicher manuell verwalten und es besteht die Gefahr von Undichtigkeiten.
Eine Referenz hingegen ist so ziemlich ein "verwalteter Zeiger", den der GC kennt. Es ist immer noch eine Adresse eines Objekts, aber jetzt kennt der GC Details des Ziels, sodass er es verschieben, Komprimierungen vornehmen, finalisieren, entsorgen und all die anderen netten Dinge, die eine verwaltete Umgebung tut.
Der Hauptunterschied besteht darin, wie und warum Sie sie verwenden würden. In den meisten Fällen in einer verwalteten Sprache verwenden Sie eine Objektreferenz. Zeiger sind praktisch für Interop und die seltene Notwendigkeit, wirklich schnell zu arbeiten.
Bearbeiten: In der Tat ist hier ein gutes Beispiel dafür, wann Sie einen "Zeiger" in verwaltetem Code verwenden könnten - in diesem Fall ist es ein GCHandle, aber genau das gleiche könnte mit AllocHGlobal oder mit einem festen Byte-Array oder einer Struktur geschehen sein. Ich bevorzuge den GCHandle, weil er sich für mich eher ".NET" anfühlt.
quelle
Zeiger zeigen auf eine Stelle im Speicheradressraum. Referenzen verweisen auf eine Datenstruktur. Alle Datenstrukturen wurden ständig (nicht so oft, aber ab und zu) vom Garbage Collector verschoben (um den Speicherplatz zu komprimieren). Wie Sie bereits sagten, wird bei Datenstrukturen ohne Referenzen nach einer Weile Müll gesammelt.
Außerdem können Zeiger nur in unsicheren Kontexten verwendet werden.
quelle
Ich denke, es ist wichtig für Entwickler, das Konzept eines Zeigers zu verstehen, dh die Indirektion zu verstehen. Das bedeutet nicht, dass sie unbedingt Zeiger verwenden müssen. Es ist auch wichtig zu verstehen , dass das Konzept einer Referenz unterscheidet mich von dem Konzept des Zeigers , wenn auch nur subtil, aber , dass die Umsetzung einer Referenz fast immer ist ein Zeiger.
Das heißt, eine Variable, die eine Referenz enthält, ist nur ein zeigergroßer Speicherblock, der einen Zeiger auf das Objekt enthält. Diese Variable kann jedoch nicht auf die gleiche Weise verwendet werden wie eine Zeigervariable. In C # (und C und C ++, ...) kann ein Zeiger wie ein Array indiziert werden, eine Referenz jedoch nicht. In C # wird eine Referenz vom Garbage Collector verfolgt, ein Zeiger kann dies nicht sein. In C ++ kann ein Zeiger neu zugewiesen werden, eine Referenz nicht. Syntaktisch und semantisch sind Zeiger und Referenzen sehr unterschiedlich, aber mechanisch sind sie gleich.
quelle
Ein Zeiger kann auf ein beliebiges Byte im Adressraum der Anwendung zeigen. Eine Referenz ist stark eingeschränkt und wird von der .NET-Umgebung gesteuert und verwaltet.
quelle
Die Sache mit Zeigern, die sie etwas komplexer macht, ist nicht, was sie sind, sondern was Sie damit machen können. Und wenn Sie einen Zeiger auf einen Zeiger auf einen Zeiger haben. Dann wird es richtig lustig.
quelle
Einer der größten Vorteile von Referenzen gegenüber Zeigern ist die größere Einfachheit und Lesbarkeit. Wie immer, wenn Sie etwas vereinfachen, vereinfachen Sie die Verwendung, aber auf Kosten der Flexibilität und Kontrolle erhalten Sie die Low-Level-Inhalte (wie andere bereits erwähnt haben).
Zeiger werden oft als "hässlich" kritisiert.
Jedes Mal, wenn Sie es verwenden, müssen Sie es zuerst zuerst dereferenzieren
Trotz des Verlusts der Lesbarkeit und der Erhöhung der Komplexität mussten die Benutzer häufig Zeiger als Parameter verwenden, damit Sie das tatsächliche Objekt ändern konnten (anstatt den Wert zu übergeben) und um den Leistungsgewinn zu erzielen, dass keine großen Objekte kopiert werden mussten.
Für mich ist dies der Grund, warum Referenzen in erster Linie "geboren" wurden, um den gleichen Vorteil wie Zeiger zu bieten, jedoch ohne all diese Zeigersyntax. Jetzt können Sie das eigentliche Objekt übergeben (nicht nur seinen Wert) UND Sie haben eine besser lesbare, normale Art der Interaktion mit dem Objekt.
C ++ - Referenzen unterschieden sich von C # / Java-Referenzen darin, dass Sie einen Wert, der es war, nicht erneut zuweisen konnten (und er muss zugewiesen werden, als er deklariert wurde). Dies war dasselbe wie die Verwendung eines const-Zeigers (ein Zeiger, der nicht erneut auf ein anderes Objekt gerichtet werden konnte).
Java und C # sind moderne Sprachen auf sehr hohem Niveau, die viele der in C / C ++ im Laufe der Jahre angesammelten Probleme beseitigt haben, und Zeiger waren definitiv eines der Dinge, die "bereinigt" werden mussten.
Soweit Ihr Kommentar zum Erkennen von Zeigern Sie zu einem stärkeren Programmierer macht, trifft dies in den meisten Fällen zu. Wenn Sie wissen, wie etwas funktioniert, anstatt es nur zu verwenden, ohne es zu wissen, würde ich sagen, dass dies Ihnen oft einen Vorteil verschaffen kann. Wie stark eine Kante ist, variiert immer. Schließlich ist es eine der vielen Schönheiten von OOP und Interfaces, etwas zu verwenden, ohne zu wissen, wie es implementiert ist.
Was würde Ihnen in diesem speziellen Beispiel das Wissen über Zeiger bei Referenzen helfen? Es ist ein sehr wichtiges Konzept zu verstehen, dass eine C # -Referenz NICHT das Objekt selbst ist, sondern auf das Objekt verweist.
# 1: Sie übergeben NICHT den Wert Gut für den Anfang, wenn Sie einen Zeiger verwenden, wissen Sie, dass der Zeiger nur eine Adresse enthält, das war's. Die Variable selbst ist fast leer und deshalb ist es so schön, sie als Argumente zu übergeben. Zusätzlich zum Leistungsgewinn arbeiten Sie mit dem eigentlichen Objekt, sodass alle Änderungen, die Sie vornehmen, nicht vorübergehend sind
# 2: Polymorphismus / Schnittstellen Wenn Sie eine Referenz haben, die ein Schnittstellentyp ist und auf ein Objekt verweist, können Sie nur Methoden dieser Schnittstelle aufrufen, obwohl das Objekt möglicherweise über viel mehr Fähigkeiten verfügt. Die Objekte können dieselben Methoden auch unterschiedlich implementieren.
Wenn Sie diese Konzepte gut verstehen, dann glaube ich nicht, dass Ihnen zu viel fehlt, wenn Sie keine Zeiger verwendet haben. C ++ wird oft als Sprache zum Erlernen der Programmierung verwendet, da es manchmal gut ist, sich die Hände schmutzig zu machen. Wenn Sie mit Aspekten auf niedrigerer Ebene arbeiten, schätzen Sie auch den Komfort einer modernen Sprache. Ich habe mit C ++ angefangen und bin jetzt ein C # -Programmierer. Ich habe das Gefühl, dass die Arbeit mit rohen Zeigern mir geholfen hat, besser zu verstehen, was unter der Haube vor sich geht.
Ich denke nicht, dass jeder mit Zeigern beginnen muss, aber wichtig ist, dass er versteht, warum Referenzen anstelle von Werttypen verwendet werden, und dass der beste Weg, dies zu verstehen, darin besteht, seinen Vorfahren, den Zeiger, zu betrachten.
quelle
.
verwendet hätten->
, aberfoo.bar(123)
synonym mit einem Aufruf der statischen MethodefooClass.bar(ref foo, 123)
. Das hätte Dinge erlaubt wiemyString.Append("George")
; [was die Variable modifizieren würdemyString
] und machte den Unterschied in der Bedeutung zwischenmyStruct.field = 3;
und deutlichermyClassObject->field = 3;
.