Aus der Anzahl der hier gestellten Fragen geht hervor, dass die Leute einige ziemlich grundlegende Probleme haben, wenn sie sich mit Zeigern und Zeigerarithmetik beschäftigen.
Ich bin gespannt warum. Sie haben mir nie wirklich große Probleme bereitet (obwohl ich sie bereits im Neolithikum kennengelernt habe). Um bessere Antworten auf diese Fragen zu schreiben, würde ich gerne wissen, was die Leute schwierig finden.
Also, wenn Sie mit Zeigern zu kämpfen haben oder Sie es kürzlich aber plötzlich "verstanden" haben, was waren die Aspekte von Zeigern, die Ihnen Probleme bereiteten?
Antworten:
Ich vermute, die Leute gehen etwas zu tief in ihre Antworten. Ein Verständnis der Zeitplanung, des tatsächlichen CPU-Betriebs oder der Speicherverwaltung auf Baugruppenebene ist nicht unbedingt erforderlich.
Als ich unterrichtete, stellte ich fest, dass die folgenden Lücken im Verständnis der Schüler die häufigste Ursache für Probleme sind:
Die meisten meiner Schüler waren in der Lage, eine vereinfachte Zeichnung eines Speicherblocks zu verstehen, im Allgemeinen den Abschnitt mit den lokalen Variablen des Stapels im aktuellen Bereich. Im Allgemeinen half es, den verschiedenen Orten explizite fiktive Adressen zu geben.
Zusammenfassend denke ich, wenn Sie Zeiger verstehen wollen, müssen Sie Variablen verstehen und wissen, was sie in modernen Architekturen tatsächlich sind.
quelle
Als ich anfing, mit ihnen zu arbeiten, war das größte Problem, das ich hatte, die Syntax.
sind alle gleich.
aber:
Warum? weil der "Zeiger" -Teil der Deklaration zur Variablen und nicht zum Typ gehört.
Und dann die Dereferenzierung des Dings verwendet eine sehr ähnliche Notation:
Außer wenn Sie tatsächlich einen Zeiger benötigen ... dann verwenden Sie ein kaufmännisches Und!
Hurra für die Konsistenz!
Dann, anscheinend nur, um Idioten zu sein und zu beweisen, wie klug sie sind, verwenden viele Bibliotheksentwickler Zeiger-zu-Zeiger-zu-Zeiger, und wenn sie eine Reihe dieser Dinge erwarten, warum nicht auch einfach einen Zeiger darauf übergeben? .
Um dies aufzurufen, benötige ich die Adresse des Array von Zeigern auf Zeiger auf Zeiger von Ints:
In sechs Monaten, wenn ich diesen Code pflegen muss, werde ich mehr Zeit damit verbringen, herauszufinden, was dies alles bedeutet, als von Grund auf neu zu schreiben. (Ja, wahrscheinlich habe ich diese Syntax falsch verstanden - es ist eine Weile her, seit ich irgendetwas in C gemacht habe. Ich vermisse es irgendwie, aber dann bin ich ein bisschen ein Massochist)
quelle
Das richtige Verständnis von Zeigern erfordert Kenntnisse über die Architektur der zugrunde liegenden Maschine.
Viele Programmierer wissen heute nicht, wie ihre Maschine funktioniert, genauso wie die meisten Leute, die wissen, wie man ein Auto fährt, nichts über den Motor wissen.
quelle
Wenn es um Hinweise geht, befinden sich verwirrte Personen in einem von zwei Lagern. Ich war (bin?) In beiden.
Die
array[]
MengeDies ist die Menge, die direkt nicht weiß, wie sie von der Zeigernotation in die Arraynotation übersetzt werden soll (oder nicht einmal weiß, dass sie überhaupt verwandt sind). Es gibt vier Möglichkeiten, auf Elemente eines Arrays zuzugreifen:
Die Idee dabei ist , dass Arrays über Zeiger zugreifen scheint ziemlich einfach und unkompliziert, aber eine Tonne sehr kompliziert und kluge Dinge kann auf diese Weise durchgeführt werden. Einige davon können erfahrene C / C ++ - Programmierer verwirren, geschweige denn unerfahrene Neulinge.
Die
reference to a pointer
undpointer to a pointer
MengeDies ist ein großartiger Artikel, der den Unterschied erklärt und dem ich Code zitieren und stehlen werde :)
Als kleines Beispiel kann es sehr schwierig sein, genau zu sehen, was der Autor tun wollte, wenn Sie auf so etwas stoßen:
Oder in geringerem Maße so etwas:
Was lösen wir am Ende des Tages wirklich mit all dem Kauderwelsch? Nichts.
Diese Komplexität und die scheinbare (kühn erscheinende) Austauschbarkeit mit Referenzen, die häufig eine weitere Einschränkung von Zeigern und ein Fehler von Neuankömmlingen darstellt, erschweren das Verständnis von Zeigern. Es ist auch wichtig zu verstehen, um ihrer selbst willen Abschluss, dass Hinweise auf Referenzen illegal sind in C und C ++ aus Gründen verwirrend , die Sie in nehmen
lvalue
-rvalue
Semantik.Wie eine frühere Antwort bemerkte, haben Sie oft nur diese Hotshot-Programmierer, die denken, dass sie klug sind,
******awesome_var->lol_im_so_clever()
und die meisten von uns sind wahrscheinlich schuldig, manchmal solche Gräueltaten zu schreiben, aber es ist einfach kein guter Code, und es ist sicherlich nicht wartbar .Nun, diese Antwort war länger als ich gehofft hatte ...
quelle
Ich beschuldige die Qualität der Referenzmaterialien und die Personen, die den Unterricht persönlich durchführen. Die meisten Konzepte in C (aber insbesondere Zeiger) werden einfach schlecht gelehrt . Ich drohe immer wieder, mein eigenes C-Buch zu schreiben (mit dem Titel Das Letzte, was die Welt braucht, ist ein weiteres Buch über die C-Programmiersprache ), aber ich habe weder die Zeit noch die Geduld, dies zu tun. Also hänge ich hier ab und werfe zufällige Zitate aus dem Standard auf Leute.
Es gibt auch die Tatsache, dass bei der ersten Entwicklung von C davon ausgegangen wurde, dass Sie die Maschinenarchitektur ziemlich detailliert verstanden haben, nur weil es bei Ihrer täglichen Arbeit keine Möglichkeit gab, dies zu vermeiden (der Speicher war so knapp und die Prozessoren so langsam Sie mussten verstehen, wie sich das, was Sie geschrieben haben, auf die Leistung auswirkte.
quelle
Es gibt einen großartigen Artikel, der die Vorstellung unterstützt, dass Zeiger auf Joel Spolskys Website - The Perils of JavaSchools - schwer zu finden sind .
[Haftungsausschluss - Ich bin per se kein Java-Hasser .]
quelle
Die meisten Dinge sind schwerer zu verstehen, wenn Sie nicht auf dem Wissen basieren, das "darunter" liegt. Als ich CS unterrichtete, wurde es viel einfacher, als ich meine Schüler mit dem Programmieren einer sehr einfachen "Maschine" begann, eines simulierten Dezimalcomputers mit Dezimal-Opcodes, dessen Speicher aus Dezimalregistern und Dezimaladressen bestand. Sie würden sehr kurze Programme einfügen, um beispielsweise eine Reihe von Zahlen hinzuzufügen, um eine Summe zu erhalten. Dann machten sie einen Schritt, um zu sehen, was geschah. Sie könnten die Eingabetaste gedrückt halten und zusehen, wie sie "schnell" läuft.
Ich bin sicher, fast jeder auf SO wundert sich, warum es nützlich ist, so einfach zu werden. Wir vergessen, wie es war, nicht zu programmieren. Wenn Sie mit einem solchen Spielzeugcomputer spielen, werden Konzepte eingeführt, ohne die Sie nicht programmieren können, z. B. die Idee, dass die Berechnung ein schrittweiser Prozess ist, bei dem eine kleine Anzahl grundlegender Grundelemente zum Erstellen von Programmen verwendet wird, und das Konzept des Speichers Variablen als Orte, an denen Zahlen gespeichert werden, an denen sich die Adresse oder der Name der Variablen von der darin enthaltenen Zahl unterscheidet. Es wird unterschieden zwischen dem Zeitpunkt, zu dem Sie das Programm aufrufen, und dem Zeitpunkt, zu dem es "ausgeführt" wird. Ich vergleiche das Programmieren mit dem Überqueren einer Reihe von "Geschwindigkeitsbegrenzungen", wie sehr einfachen Programmen, dann Schleifen und Unterprogrammen, dann Arrays, dann sequentiellen E / A, dann Zeigern und Datenstruktur.
Wenn Sie zu C kommen, sind die Zeiger verwirrend, obwohl K & R sie sehr gut erklärt hat. Ich habe sie in C gelernt, indem ich wusste, wie man sie liest - von rechts nach links. Wie wenn ich
int *p
in meinem Kopf sehe, sage ich "p
zeigt auf einint
". C wurde als ein Schritt weiter als die Assemblersprache erfunden und das gefällt mir daran - es liegt nahe an diesem "Boden". Zeiger sind wie alles andere schwerer zu verstehen, wenn Sie diese Grundlage nicht haben.quelle
Ich habe keine Hinweise bekommen, bis ich die Beschreibung in K & R gelesen habe. Bis zu diesem Zeitpunkt machten Zeiger keinen Sinn. Ich habe eine ganze Reihe von Dingen gelesen, in denen die Leute sagten: "Lerne keine Hinweise, sie sind verwirrend und werden deinen Kopf verletzen und dir Aneurysmen geben." Also scheute ich mich lange davor und schuf diese unnötige Atmosphäre eines schwierigen Konzepts .
Ansonsten dachte ich meistens, warum um alles in der Welt sollten Sie eine Variable wollen, die Sie durchlaufen müssen, um den Wert zu erhalten, und wenn Sie ihr etwas zuweisen wollten, mussten Sie seltsame Dinge tun, um Werte zum Laufen zu bringen in sie. Der springende Punkt einer Variablen ist etwas, um einen Wert zu speichern, dachte ich. Warum jemand es kompliziert machen wollte, war mir ein Rätsel. "Also muss man mit einem Zeiger den
*
Operator benutzen , um seinen Wert zu erreichen ??? Was für eine doofe Variable ist das?" , Ich dachte. Sinnlos, kein Wortspiel beabsichtigt.Der Grund, warum es kompliziert war, war, dass ich nicht verstand, dass ein Zeiger eine Adresse war für etwas war. Wenn Sie erklären, dass es sich um eine Adresse handelt, dass es sich um eine Adresse handelt, die eine Adresse für etwas anderes enthält, und dass Sie diese Adresse manipulieren können, um nützliche Dinge zu tun, könnte dies die Verwirrung beseitigen.
Eine Klasse, die die Verwendung von Zeigern für den Zugriff auf / das Ändern von Ports auf einem PC, die Verwendung von Zeigerarithmetik zur Adressierung verschiedener Speicherorte und die Betrachtung eines komplizierteren C-Codes, der ihre Argumente modifizierte, erforderte, machte mich von der Idee ab, dass Zeiger sinnlos waren.
quelle
Hier ist ein Zeiger- / Array-Beispiel, das mir eine Pause gab. Angenommen, Sie haben zwei Arrays:
Ihr Ziel ist es, den Inhalt von uint8_t mit memcpy () vom Quellziel zu kopieren. Ratet mal, welche der folgenden Ziele erreicht werden:
Die Antwort (Spoiler Alert!) Ist ALLE. "Ziel", "& Ziel" und "& Ziel [0]" haben alle den gleichen Wert. "& Ziel" ist ein anderer Typ als die beiden anderen, aber es ist immer noch der gleiche Wert. Gleiches gilt für die Permutationen von "Quelle".
Nebenbei bevorzuge ich persönlich die erste Version.
quelle
sizeof(source)
, denn wennsource
es sich um einen Zeiger handelt,sizeof
wird er nicht das sein, was Sie wollen. Ich schreibe manchmal (nicht immer),sizeof(source[0]) * number_of_elements_of_source
nur um mich von diesem Fehler fernzuhalten.Zunächst sollte ich sagen, dass C und C ++ die ersten Programmiersprachen waren, die ich gelernt habe. Ich habe mit C angefangen, dann viel C ++ in der Schule gemacht und bin dann zurück zu C gegangen, um fließend zu sprechen.
Das erste, was mich beim Lernen von C über Zeiger verwirrte, war das Einfache:
Diese Verwirrung beruhte hauptsächlich darauf, dass die Verwendung eines Verweises auf eine Variable für OUT-Argumente eingeführt wurde, bevor mir Zeiger ordnungsgemäß eingeführt wurden. Ich erinnere mich, dass ich das Schreiben der ersten Beispiele in C for Dummies übersprungen habe, weil sie zu einfach waren, um nie das erste Programm, das ich geschrieben habe, zum Laufen zu bringen (höchstwahrscheinlich aus diesem Grund).
Was daran verwirrend war, war, was
&ch
eigentlich bedeutete und warumstr
es nicht gebraucht wurde.Nachdem ich mich damit vertraut gemacht hatte, erinnere ich mich, dass ich über die dynamische Zuordnung verwirrt war. Irgendwann wurde mir klar, dass Zeiger auf Daten ohne dynamische Zuweisung eines Typs nicht besonders nützlich waren, also schrieb ich etwas wie:
um zu versuchen, dynamisch etwas Speicherplatz zuzuweisen. Es hat nicht funktioniert. Ich war mir nicht sicher, ob es funktionieren würde, aber ich wusste nicht, wie es sonst funktionieren könnte.
Ich habe später von
malloc
und erfahrennew
, aber sie schienen mir wirklich magische Gedächtnisgeneratoren zu sein. Ich wusste nichts darüber, wie sie funktionieren könnten.Einige Zeit später wurde mir wieder Rekursion beigebracht (ich hatte es vorher selbst gelernt, war aber jetzt im Unterricht) und ich fragte, wie es unter der Haube funktioniert - wo wurden die einzelnen Variablen gespeichert? Mein Professor sagte "auf dem Stapel" und viele Dinge wurden mir klar. Ich hatte den Begriff schon einmal gehört und zuvor Software-Stacks implementiert. Ich hatte schon lange zuvor gehört, dass andere sich auf "den Stapel" bezogen, aber ich hatte es vergessen.
Um diese Zeit wurde mir auch klar, dass die Verwendung mehrdimensionaler Arrays in C sehr verwirrend sein kann. Ich wusste, wie sie funktionierten, aber sie waren so leicht zu verwickeln, dass ich mich entschied, sie zu umgehen, wann immer ich konnte. Ich denke, dass das Problem hier hauptsächlich syntaktisch war (insbesondere das Übergeben oder Zurückgeben von Funktionen).
Seit ich in den nächsten ein oder zwei Jahren C ++ für die Schule geschrieben habe, habe ich viel Erfahrung mit Zeigern für Datenstrukturen. Hier hatte ich eine Reihe neuer Probleme - das Verwechseln von Zeigern. Ich hätte mehrere Ebenen von Zeigern (Dinge wie
node ***ptr;
), die mich stolpern lassen. Ich würde einen Zeiger falsch oft dereferenzieren und schließlich herausfinden, wie viele*
ich durch Ausprobieren brauchte.Irgendwann habe ich gelernt, wie der Haufen eines Programms funktioniert (irgendwie, aber gut genug, dass es mich nachts nicht mehr wach hält). Ich erinnere mich, dass ich gelesen habe, wenn Sie ein paar Bytes vor dem Zeiger suchen, der
malloc
auf einem bestimmten System zurückgegeben wird, können Sie sehen, wie viele Daten tatsächlich zugewiesen wurden. Ich erkannte, dass der Code inmalloc
mehr Speicher vom Betriebssystem verlangen konnte und dieser Speicher nicht Teil meiner ausführbaren Dateien war. Eine anständige Vorstellung davon zu haben, wie esmalloc
funktioniert, ist wirklich nützlich.Bald danach nahm ich an einer Montageklasse teil, die mir nicht so viel über Zeiger beibrachte, wie die meisten Programmierer wahrscheinlich denken. Es brachte mich dazu, mehr darüber nachzudenken, in welche Assembly mein Code übersetzt werden könnte. Ich hatte immer versucht, effizienten Code zu schreiben, aber jetzt hatte ich eine bessere Idee, wie es geht.
Ich nahm auch an einigen Kursen teil, in denen ich etwas Lispeln schreiben musste . Beim Schreiben von lisp ging es mir nicht so sehr um Effizienz wie in C. Ich hatte sehr wenig Ahnung, in was dieser Code übersetzt werden könnte, wenn er kompiliert wird, aber ich wusste, dass es so aussah, als würde man viele lokal benannte Symbole (Variablen) verwenden Dinge viel einfacher. Irgendwann schrieb ich einen AVL-Baumrotationscode in ein wenig Lisp, was mir aufgrund von Zeigerproblemen sehr schwer fiel, in C ++ zu schreiben. Ich erkannte, dass meine Abneigung gegen das, was ich für überschüssige lokale Variablen hielt, meine Fähigkeit, dieses und mehrere andere Programme in C ++ zu schreiben, behindert hatte.
Ich habe auch einen Compiler-Kurs besucht. Während in dieser Klasse blätterte ich in das vorgeschobenen Material voraus und darüber gelernt , statische Einzelbelegung (SSA) und toten Variablen, die außer nicht so wichtig ist , dass es hat mich gelehrt , dass jeder anständiger Compiler einen anständigen Job für den Umgang mit Variablen tun , die sind nicht mehr verwendet. Ich wusste bereits, dass mehr Variablen (einschließlich Zeiger) mit korrekten Typen und guten Namen mir helfen würden, die Dinge im Kopf zu behalten, aber jetzt wusste ich auch, dass es noch dümmer war, sie aus Effizienzgründen zu vermeiden, als meine weniger auf Mikrooptimierung ausgerichteten Professoren sagten mich.
Für mich hat es sehr geholfen, ein gutes Stück über das Speicherlayout eines Programms zu wissen. Das Überlegen, was mein Code symbolisch und auf der Hardware bedeutet, hilft mir dabei. Die Verwendung lokaler Zeiger mit dem richtigen Typ hilft sehr. Ich schreibe oft Code, der aussieht wie:
Wenn ich also einen Zeigertyp vermassle, wird durch den Compilerfehler sehr deutlich, wo das Problem liegt. Wenn ich es tat:
Wenn ein Zeigertyp falsch ist, ist der Compilerfehler viel schwieriger herauszufinden. Ich wäre versucht, in meiner Frustration auf Versuch und Irrtum-Änderungen zurückzugreifen und die Dinge wahrscheinlich noch schlimmer zu machen.
quelle
Rückblickend gab es vier Dinge, die mir wirklich geholfen haben, endlich Hinweise zu verstehen. Vorher konnte ich sie verwenden, aber ich habe sie nicht vollständig verstanden. Das heißt, ich wusste, wenn ich den Formularen folgen würde, würde ich die gewünschten Ergebnisse erzielen, aber ich verstand das „Warum“ der Formulare nicht vollständig. Mir ist klar, dass dies nicht genau das ist, was Sie gefragt haben, aber ich denke, es ist eine nützliche Folge.
Schreiben einer Routine, die einen Zeiger auf eine Ganzzahl nahm und die Ganzzahl änderte. Dies gab mir die notwendigen Formen, auf denen ich mentale Modelle der Funktionsweise von Zeigern aufbauen konnte.
Eindimensionale dynamische Speicherzuordnung. Durch das Herausfinden der 1-D-Speicherzuordnung habe ich das Konzept des Zeigers verstanden.
Zweidimensionale dynamische Speicherzuordnung. Das Herausfinden der 2-D-Speicherzuordnung verstärkte dieses Konzept, lehrte mich aber auch, dass der Zeiger selbst Speicher benötigt und berücksichtigt werden muss.
Unterschiede zwischen Stapelvariablen, globalen Variablen und Heapspeicher. Durch das Herausfinden dieser Unterschiede lernte ich die Arten von Speicher, auf die die Zeiger verweisen.
Für jeden dieser Punkte musste man sich vorstellen, was auf einer niedrigeren Ebene vor sich ging - ein mentales Modell aufbauen, das jeden Fall befriedigte, den ich mir vorstellen konnte, darauf zu werfen. Es hat Zeit und Mühe gekostet, aber es hat sich gelohnt. Ich bin überzeugt, dass man, um Zeiger zu verstehen, dieses mentale Modell darauf aufbauen muss, wie sie funktionieren und wie sie implementiert werden.
Nun zurück zu Ihrer ursprünglichen Frage. Basierend auf der vorherigen Liste gab es einige Punkte, die ich ursprünglich nur schwer erfassen konnte.
quelle
Ich hatte meinen "Zeiger-Moment" bei einigen Telefonieprogrammen in C. Ich musste einen AXE10-Austauschemulator mit einem Protokollanalysator schreiben, der nur das klassische C verstand. Alles hing davon ab, Zeiger zu kennen. Ich habe versucht, meinen Code ohne sie zu schreiben (hey, ich war "Pre-Pointer", habe mich etwas entspannt) und bin völlig gescheitert.
Der Schlüssel zum Verständnis war für mich der Operator & (Adresse). Sobald ich verstanden hatte, dass
&i
dies die "Adresse von i" bedeutete, dann verstand ich das*i
dass "der Inhalt der Adresse, auf die i zeigte", etwas später. Wann immer ich meinen Code schrieb oder las, wiederholte ich immer, was "&" bedeutete und was "*" bedeutete, und schließlich kam ich dazu, sie intuitiv zu verwenden.Zu meiner Schande wurde ich gezwungen, VB und dann Java zu verwenden, damit mein Zeigerwissen nicht mehr so scharf ist wie früher, aber ich bin froh, dass ich "Post-Pointer" bin. Bitten Sie mich jedoch nicht, eine Bibliothek zu verwenden, in der ich * * p verstehen muss.
quelle
&i
ist die Adresse und*i
der Inhalt, was isti
?Die Hauptschwierigkeit bei Zeigern ist, zumindest für mich, dass ich nicht mit C angefangen habe. Ich habe mit Java angefangen. Die ganze Vorstellung von Zeigern war bis zu ein paar Klassen am College, in denen ich C kennen sollte, wirklich fremd. Dann brachte ich mir selbst die Grundlagen von C bei und wie man Zeiger in ihrem grundlegenden Sinne verwendet. Selbst dann muss ich jedes Mal, wenn ich C-Code lese, die Zeigersyntax nachschlagen.
In meiner sehr begrenzten Erfahrung (1 Jahr reale Welt + 4 im College) verwirren mich Zeiger, weil ich sie nie wirklich in etwas anderem als einem Klassenzimmer verwenden musste. Und ich kann mit den Schülern sympathisieren, die jetzt CS mit JAVA anstelle von C oder C ++ beginnen. Wie Sie sagten, haben Sie im neolithischen Zeitalter Hinweise gelernt und verwenden sie wahrscheinlich seitdem. Für uns neuere Menschen ist der Gedanke, Speicher zuzuweisen und Zeigerarithmetik zu betreiben, wirklich fremd, weil all diese Sprachen dies weg abstrahiert haben.
PS Nachdem er den Spolsky-Aufsatz gelesen hatte, war seine Beschreibung von 'JavaSchools' nichts anderes als das, was ich am College in Cornell ('05 -'09) durchgemacht habe. Ich nahm die Strukturen und funktionale Programmierung (sml), Betriebssysteme (C), Algorithmen (Stift und Papier) und eine ganze Reihe anderer Klassen, die nicht in Java unterrichtet wurden. Alle Intro-Klassen und Wahlfächer wurden jedoch alle in Java durchgeführt, da es sinnvoll ist, das Rad nicht neu zu erfinden, wenn Sie versuchen, etwas Höheres zu tun, als eine Hashtabelle mit Zeigern zu implementieren.
quelle
void foo(Clazz obj) { obj = new Clazz(); }
ein No-Op ist, währendvoid bar(Clazz obj) { obj.quux = new Quux(); }
das Argument mutiert wird ...Hier ist eine Nichtantwort: Verwenden Sie cdecl (oder c ++ decl), um es herauszufinden:
quelle
Sie fügen dem Code eine zusätzliche Dimension hinzu, ohne die Syntax wesentlich zu ändern. Denk darüber nach:
Es gibt nur eine Sache zu ändern :
a
. Sie können schreibena = 6
und die Ergebnisse sind für die meisten Menschen offensichtlich. Aber jetzt überlegen Sie:Es gibt zwei Dinge
a
, die zu unterschiedlichen Zeiten relevant sind: den tatsächlichen Wert vona
, den Zeiger und den Wert "hinter" dem Zeiger. Sie können änderna
:... und
some_int
ist immer noch irgendwo mit dem gleichen Wert. Sie können aber auch Folgendes ändern:Es gibt eine konzeptionelle Lücke zwischen
a = 6
, die nur lokale Nebenwirkungen hat und*a = 6
die eine Reihe anderer Dinge an anderen Orten beeinflussen könnte. Mein Punkt hier ist nicht, dass das Konzept der Indirektion von Natur aus schwierig ist, sondern dass, weil Sie sowohl die unmittelbare, lokale als aucha
die indirekte Sache mit*a
... tun können, dies die Menschen verwirren könnte.quelle
Ich hatte ungefähr 2 Jahre in C ++ programmiert und dann auf Java konvertiert (5 Jahre) und habe nie zurückgeschaut. Als ich jedoch kürzlich einige native Dinge verwenden musste, stellte ich (mit Erstaunen) fest, dass ich nichts über Zeiger vergessen hatte und finde sie sogar einfach zu verwenden. Dies ist ein scharfer Kontrast zu dem, was ich vor 7 Jahren erlebt habe, als ich zum ersten Mal versuchte, das Konzept zu verstehen. Ich denke also, Verständnis und Sympathie sind eine Frage der Programmierreife? :) :)
ODER
Zeiger sind wie Fahrradfahren. Wenn Sie erst einmal herausgefunden haben, wie Sie mit ihnen arbeiten sollen, können Sie es nicht vergessen.
Alles in allem ist die gesamte Zeigeridee SEHR lehrreich und ich glaube, sie sollte von jedem Programmierer verstanden werden, unabhängig davon, ob er in einer Sprache mit Zeigern programmiert oder nicht.
quelle
Zeiger sind aufgrund der Indirektion schwierig.
quelle
Zeiger (zusammen mit einigen anderen Aspekten der Arbeit auf niedriger Ebene) erfordern, dass der Benutzer die Magie wegnimmt.
Die meisten hochrangigen Programmierer mögen die Magie.
quelle
Zeiger sind eine Möglichkeit, mit dem Unterschied zwischen einem Handle für ein Objekt und einem Objekt selbst umzugehen. (ok, nicht unbedingt Objekte, aber du weißt was ich meine und wo mein Verstand ist)
Irgendwann müssen Sie sich wahrscheinlich mit dem Unterschied zwischen den beiden auseinandersetzen. In der modernen Hochsprache wird dies zur Unterscheidung zwischen Kopie nach Wert und Kopie nach Referenz. In jedem Fall ist es ein Konzept, das für Programmierer oft schwer zu verstehen ist.
Wie bereits erwähnt, ist die Syntax zur Behandlung dieses Problems in C hässlich, inkonsistent und verwirrend. Wenn Sie wirklich versuchen, es zu verstehen, ist schließlich ein Zeiger sinnvoll. Aber wenn Sie anfangen, sich mit Zeigern auf Zeiger usw. zu befassen, wird es sowohl für mich als auch für andere Menschen sehr verwirrend.
Ein weiterer wichtiger Punkt bei Zeigern ist, dass sie gefährlich sind. C ist die Sprache eines Master-Programmierers. Es setzt voraus, dass Sie wissen, was zum Teufel Sie tun, und gibt Ihnen dadurch die Macht, Dinge wirklich durcheinander zu bringen. Während einige Arten von Programmen noch in C geschrieben werden müssen, tun dies die meisten Programme nicht. Wenn Sie eine Sprache haben, die eine bessere Abstraktion für den Unterschied zwischen einem Objekt und seinem Handle bietet, empfehle ich Ihnen, sie zu verwenden.
In der Tat ist es in vielen modernen C ++ - Anwendungen häufig der Fall, dass jede erforderliche Zeigerarithmetik gekapselt und abstrahiert wird. Wir wollen nicht, dass Entwickler überall Zeigerarithmetik betreiben. Wir wollen eine zentralisierte, gut getestete API, die Zeigerarithmetik auf der untersten Ebene ausführt. Änderungen an diesem Code müssen mit großer Sorgfalt und umfangreichen Tests vorgenommen werden.
quelle
Ich denke, ein Grund, warum C-Zeiger schwierig sind, ist, dass sie mehrere Konzepte zusammenführen, die nicht wirklich gleichwertig sind. Da sie jedoch alle mithilfe von Zeigern implementiert werden, kann es für die Benutzer schwierig sein, die Konzepte zu entwirren.
In C werden Zeiger unter anderem verwendet:
In C definieren Sie eine verknüpfte Liste von Ganzzahlen wie folgt:
Der Zeiger ist nur da, weil dies die einzige Möglichkeit ist, eine rekursive Datenstruktur in C zu definieren, wenn das Konzept wirklich nichts mit einem so niedrigen Detail wie Speicheradressen zu tun hat. Betrachten Sie das folgende Äquivalent in Haskell, für das keine Zeiger erforderlich sind:
Ziemlich einfach - eine Liste ist entweder leer oder besteht aus einem Wert und dem Rest der Liste.
So können Sie eine Funktion
foo
auf jedes Zeichen einer Zeichenfolge in C anwenden :Obwohl auch ein Zeiger als Iterator verwendet wird, hat dieses Beispiel sehr wenig mit dem vorherigen gemeinsam. Das Erstellen eines Iterators, den Sie inkrementieren können, unterscheidet sich vom Definieren einer rekursiven Datenstruktur. Keines der beiden Konzepte ist besonders an die Idee einer Speicheradresse gebunden.
Hier ist eine aktuelle Funktionssignatur in glib :
Whoa! Das ist ein ziemlicher Schluck
void*
. Und es geht nur darum, eine Funktion zu deklarieren, die über eine Liste iteriert, die alles Mögliche enthalten kann, und jedem Mitglied eine Funktion zuzuweisen. Vergleichen Sie es mit dermap
Deklaration in Haskell:Das ist viel einfacher:
map
ist eine Funktion, die eine Funktion verwendet, die eina
in ein konvertiertb
, und sie auf eine Liste vona
's anwendet, um eine Liste von ' s zu erhaltenb
. Genau wie in der C - Funktiong_list_foreach
,map
brauchte nichts in seiner eigenen Definition über die Arten kennen zu der sie angewandt werden.Um zusammenzufassen:
Ich denke, C-Zeiger wären viel weniger verwirrend, wenn die Leute zuerst rekursive Datenstrukturen, Iteratoren, Polymorphismus usw. als separate Konzepte kennenlernen und dann lernen würden, wie Zeiger verwendet werden können, um diese Ideen in C zu implementieren , anstatt all diese zu mischen Konzepte zusammen zu einem einzigen Thema von "Zeigern".
quelle
c != NULL
in Ihrem "Hallo Welt" Beispiel ... Sie meinen*c != '\0'
.Ich denke, es erfordert eine solide Grundlage, wahrscheinlich auf Maschinenebene, mit einer Einführung in Maschinencode, Baugruppen und der Darstellung von Elementen und Datenstrukturen im RAM. Es dauert ein wenig Zeit, einige Hausaufgaben oder Problemlösungsübungen und einige Überlegungen.
Aber wenn eine Person zuerst Hochsprachen beherrscht (was nichts Falsches ist - ein Zimmermann benutzt eine Axt. Eine Person, die Atom spalten muss, benutzt etwas anderes. Wir brauchen Leute, die Tischler sind, und wir haben Leute, die Atome studieren) und Diese Person, die Hochsprache beherrscht, erhält eine 2-minütige Einführung in Zeiger, und dann ist es schwer zu erwarten, dass sie Zeigerarithmetik, Zeiger auf Zeiger, eine Reihe von Zeigern auf Zeichenfolgen variabler Größe und eine Reihe von Zeichen usw. versteht. Ein niedriges solides Fundament kann sehr hilfreich sein.
quelle
Das Problem, das ich immer hatte (hauptsächlich Autodidakt), ist das "Wann", einen Zeiger zu verwenden. Ich kann mich mit der Syntax zum Erstellen eines Zeigers befassen, muss aber wissen, unter welchen Umständen ein Zeiger verwendet werden sollte.
Bin ich der einzige mit dieser Einstellung? ;-);
quelle
Es war einmal ... Wir hatten 8-Bit-Mikroprozessoren und alle schrieben in Assembly. Die meisten Prozessoren enthielten eine Art indirekte Adressierung, die für Sprungtabellen und Kernel verwendet wurde. Als übergeordnete Sprachen hinzukamen, fügten wir eine dünne Abstraktionsebene hinzu und nannten sie Zeiger. Im Laufe der Jahre haben wir uns immer mehr von der Hardware entfernt. Das ist nicht unbedingt eine schlechte Sache. Sie werden aus einem bestimmten Grund als höhere Sprachen bezeichnet. Je mehr ich mich auf das konzentrieren kann, was ich tun möchte, anstatt auf die Details, wie es gemacht wird, desto besser.
quelle
Es scheint, dass viele Schüler ein Problem mit dem Konzept der Indirektion haben, insbesondere wenn sie das Konzept der Indirektion zum ersten Mal kennenlernen. Ich erinnere mich von damals, als ich Student war, dass von den +100 Studenten meines Kurses nur eine Handvoll Leute wirklich Hinweise verstanden haben.
Das Konzept der Indirektion wird im wirklichen Leben nicht oft verwendet, und daher ist es zunächst schwer zu verstehen.
quelle
Ich hatte vor kurzem gerade den Zeigerklick-Moment und war überrascht, dass ich ihn verwirrend fand. Es war mehr so, dass alle so viel darüber sprachen, dass ich davon ausging, dass dunkle Magie vor sich ging.
So wie ich es bekam, war es so. Stellen Sie sich vor, dass alle definierten Variablen zur Kompilierungszeit (auf dem Stapel) Speicherplatz erhalten. Wenn Sie ein Programm benötigen, das große Datendateien wie Audio oder Bilder verarbeiten kann, benötigen Sie keine feste Speichermenge für diese potenziellen Strukturen. Sie warten also bis zur Laufzeit, um dem Speichern dieser Daten (auf dem Heap) eine bestimmte Speichermenge zuzuweisen.
Sobald Sie Ihre Daten im Speicher haben, möchten Sie diese Daten nicht mehr jedes Mal um Ihren Speicherbus kopieren, wenn Sie eine Operation darauf ausführen möchten. Angenommen, Sie möchten einen Filter auf Ihre Bilddaten anwenden. Sie haben einen Zeiger, der an der Vorderseite der Daten beginnt, die Sie dem Bild zugewiesen haben, und eine Funktion läuft über diese Daten und ändert sie an Ort und Stelle. Wenn Sie nicht wüssten, was Sie tun, würden Sie wahrscheinlich Duplikate von Daten erstellen, während Sie den Vorgang durchlaufen haben.
Zumindest sehe ich das im Moment so!
quelle
Hier als C ++ - Neuling sprechen:
Es hat eine Weile gedauert, bis das Zeigersystem nicht unbedingt aufgrund des Konzepts, sondern aufgrund der C ++ - Syntax in Bezug auf Java verarbeitet wurde. Ein paar Dinge, die ich verwirrend fand, sind:
(1) Variablendeklaration:
vs.
vs.
und anscheinend
ist eine Funktionsdeklaration und keine Variablendeklaration. In anderen Sprachen gibt es grundsätzlich nur eine Möglichkeit, eine Variable zu deklarieren.
(2) Das kaufmännische Und wird auf verschiedene Arten verwendet. Wenn ja
dann ist das & a eine Speicheradresse.
OTOH, wenn es so ist
dann ist & a ein Referenzparameter.
Obwohl dies trivial erscheinen mag, kann es für neue Benutzer verwirrend sein - ich stamme aus Java und Java ist eine Sprache mit einer einheitlicheren Verwendung von Operatoren
(3) Array-Zeiger-Beziehung
Eine Sache, die ein bisschen frustrierend zu verstehen ist, ist ein Zeiger
kann ein Zeiger auf ein int sein
oder
kann ein Array zu einem int sein
Und nur um die Dinge chaotischer zu machen, sind Zeiger und Array nicht in allen Fällen austauschbar und Zeiger können nicht als Array-Parameter übergeben werden.
Dies fasst einige der grundlegenden Frustrationen zusammen, die ich mit C / C ++ und seinen Zeigern hatte, die IMO durch die Tatsache, dass C / C ++ all diese sprachspezifischen Macken aufweist, erheblich verstärkt wird.
quelle
Ich persönlich habe den Zeiger auch nach meinem Abschluss und nach meinem ersten Job nicht verstanden. Das einzige, was ich wusste, ist, dass Sie es für verknüpfte Listen, Binärbäume und zum Übergeben von Arrays an Funktionen benötigen. Dies war die Situation schon bei meinem ersten Job. Erst als ich anfing, Interviews zu geben, verstehe ich, dass das Zeigerkonzept tiefgreifend ist und einen enormen Nutzen und Potenzial hat. Dann fing ich an, K & R zu lesen und ein eigenes Testprogramm zu schreiben. Mein ganzes Ziel war berufsorientiert.
Zu diesem Zeitpunkt stellte ich fest, dass Zeiger weder schlecht noch schwierig sind, wenn sie auf gute Weise vermittelt werden. Als ich C im Abschluss lernte, war unserem Lehrer der Zeiger leider nicht bekannt, und selbst die Aufgaben verwendeten weniger Zeiger. In der Graduiertenebene besteht die Verwendung von Zeigern eigentlich nur darin, Binärbäume und verknüpfte Listen zu erstellen. Dieser Gedanke, dass Sie kein richtiges Verständnis der Zeiger benötigen, um mit ihnen zu arbeiten, tötet die Idee, sie zu lernen.
quelle
Zeiger .. hah .. alles über Zeiger in meinem Kopf ist, dass es eine Speicheradresse gibt, in der die tatsächlichen Werte von was auch immer seine Referenz .. also keine Magie darüber .. wenn Sie eine Baugruppe lernen, würden Sie nicht so viel Mühe haben zu lernen wie Zeiger funktionieren .. komm schon Leute ... auch in Java ist alles eine Referenz ..
quelle
Das Hauptproblem Menschen verstehen nicht, warum sie Zeiger brauchen. Weil sie nicht klar über Stapel und Haufen sind. Es ist gut, vom 16-Bit-Assembler für x86 mit winzigem Speichermodus zu starten. Es hat vielen Menschen geholfen, sich ein Bild von Stapel, Haufen und "Adresse" zu machen. Und Byte :) Moderne Programmierer können Ihnen manchmal nicht sagen, wie viele Bytes Sie benötigen, um 32-Bit-Speicherplatz zu adressieren. Wie können sie sich ein Bild von Zeigern machen?
Der zweite Moment ist die Notation: Sie deklarieren den Zeiger als *, Sie erhalten die Adresse als & und dies ist für manche Menschen nicht leicht zu verstehen.
Und das Letzte, was ich sah, war ein Speicherproblem: Sie verstehen Heap und Stack, können sich aber keine Vorstellung von "statisch" machen.
quelle