Waarom pointers gebruiken?

Ik weet dat dit een heel basale vraag is, maar ik ben net begonnen met wat basis C++-programmering na het coderen van een paar projecten met talen op hoog niveau.

Ik heb eigenlijk drie vragen:

  1. Waarom pointers gebruiken over normale variabelen?
  2. Wanneer en waar moet ik aanwijzers gebruiken?
  3. Hoe gebruik je pointers met arrays?

Antwoord 1, autoriteit 100%

  • Waarom pointers gebruiken over normale variabelen?

Het korte antwoord is: niet doen. 😉 Pointers worden gebruikt waar je niets anders kunt gebruiken. Het is ofwel vanwege het ontbreken van de juiste functionaliteit, ontbrekende gegevenstypen of voor pure prestaties. Hieronder meer…

  • Wanneer en waar moet ik pointers gebruiken?

Kort antwoord hier is: Waar je niets anders kunt gebruiken. In C heb je geen ondersteuning voor complexe datatypes zoals een string. Er is ook geen manier om een variabele “door verwijzing” door te geven aan een functie. Dat is waar je pointers moet gebruiken. Je kunt ze ook naar vrijwel alles laten verwijzen, gelinkte lijsten, leden van structs enzovoort. Maar laten we daar hier niet op ingaan.

  • Hoe gebruik je pointers met arrays?

Met weinig moeite en veel verwarring. 😉 Als we het hebben over simpele datatypes zoals int en char is er weinig verschil tussen een array en een pointer.
Deze verklaringen lijken erg op elkaar (maar zijn niet hetzelfde – bijv. sizeofgeeft verschillende waarden terug):

char* a = "Hello";
char a[] = "Hello";

Je kunt elk element in de array op deze manier bereiken

printf("Second char is: %c", a[1]);

Index 1 aangezien de array begint met element 0. 🙂

Of je zou dit ook kunnen doen

printf("Second char is: %c", *(a+1));

De pointer-operator (de *) is nodig omdat we printf vertellen dat we een teken willen afdrukken. Zonder de * zou de tekenweergave van het geheugenadres zelf worden afgedrukt. Nu gebruiken we in plaats daarvan het karakter zelf. Als we %s hadden gebruikt in plaats van %c, hadden we printf gevraagd om de inhoud van het geheugenadres waarnaar wordt verwezen met ‘a’ plus één (in dit voorbeeld hierboven) af te drukken en hadden we de * vooraan:

printf("Second char is: %s", (a+1)); /* WRONG */

Maar dit zou niet alleen het tweede teken hebben afgedrukt, maar in plaats daarvan alle tekens in de volgende geheugenadressen, totdat een null-teken (\0) werd gevonden. En dit is waar dingen gevaarlijk beginnen te worden. Wat als u per ongeluk een variabele van het type integer probeert af te drukken in plaats van een char pointer met de %s formatter?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

Hiermee zou alles worden afgedrukt wat op geheugenadres 120 is gevonden en doorgaan met afdrukken totdat er een null-teken werd gevonden. Het is verkeerd en illegaal om dit printf-statement uit te voeren, maar het zou hoe dan ook werken, aangezien een pointer in veel omgevingen eigenlijk van het type int is. Stel je de problemen voor die je zou kunnen veroorzaken als je in plaats daarvan sprintf() zou gebruiken en deze veel te lange “char array” zou toewijzen aan een andere variabele, die maar een bepaalde beperkte ruimte toegewezen kreeg. U zou hoogstwaarschijnlijk over iets anders in het geheugen schrijven en uw programma laten crashen (als u geluk heeft).

O, en als je geen stringwaarde toewijst aan de char array / pointer wanneer je deze declareert, MOET je er voldoende geheugen aan toewijzen voordat je er een waarde aan geeft. Malloc, calloc of iets dergelijks gebruiken. Dit omdat je slechts één element in je array / één enkel geheugenadres hebt aangegeven om naar te verwijzen. Dus hier zijn een paar voorbeelden:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

Houd er rekening mee dat je de variabele x nog steeds kunt gebruiken nadat je een free() van het toegewezen geheugen hebt uitgevoerd, maar je weet niet wat erin staat. Merk ook op dat de twee printf() u verschillende adressen kunnen geven, aangezien er geen garantie is dat de tweede toewijzing van geheugen in dezelfde ruimte wordt uitgevoerd als de eerste.


Antwoord 2, autoriteit 27%

Een reden om aanwijzers te gebruiken, is dat een variabele of een object kan worden gewijzigd in een aangeroepen functie.

In C++ is het een betere praktijk om referenties te gebruiken dan de wijzers. Hoewel referenties in wezen wijzen zijn, verbergt C++ tot op zekere hoogte het feit en zorgt ervoor dat het lijkt alsof u per waarde passeert. Dit maakt het eenvoudig om de manier waarop de oproepfunctie de waarde ontvangt te wijzigen zonder de semantiek van het passeren ervan te hoeven wijzigen.

Overweeg de volgende voorbeelden:

Referenties gebruiken:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}
public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

Pointers gebruiken:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}
public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

Antwoord 3, Autoriteit 23%

  1. Pointers stellen u toe om te verwijzen naar dezelfde ruimte in het geheugen van meerdere locaties. Dit betekent dat u het geheugen op één locatie kunt bijwerken en de wijziging van een andere locatie in uw programma kunt bekijken. U zult ook ruimte besparen door componenten in uw gegevensstructuren te delen.
  2. U moet aanwijzingen op elke plaats gebruiken waar u het adres moet verkrijgen en doorgeven aan een specifieke spot in het geheugen. U kunt ook aanwijzingen gebruiken om aanarrays te navigeren:
  3. Een array is een blok aaneengesloten geheugen dat is toegewezen aan een specifiek type. De naam van de array bevat de waarde van de startplaats van de array. Als je er 1 optelt, kom je op de tweede plek. Dit stelt je in staat om loops te schrijven die een aanwijzer verhogen die naar beneden schuift in de array zonder een expliciete teller te hebben voor gebruik bij toegang tot de array.

Hier is een voorbeeld in C:

char hello[] = "hello";
char *p = hello;
while (*p)
{
    *p += 1; // increase the character by one
    p += 1; // move to the next spot
}
printf(hello);

afdrukken

ifmmp

omdat het de waarde voor elk teken neemt en het met één verhoogt.


Antwoord 4, autoriteit 16%

Aanwijzers zijn een manier om een indirecte verwijzing naar een andere variabele te krijgen. In plaats van de waardevan een variabele vast te houden, vertellen ze je het adres. Dit is met name handig bij het omgaan met arrays, omdat je door een pointer naar het eerste element in een array (zijn adres) te gebruiken snel het volgende element kunt vinden door de pointer te verhogen (naar de volgende adreslocatie).

De beste uitleg van aanwijzers en aanwijzerberekeningen die ik heb gelezen, staat in K & R’s De C-programmeertaal. Een goed boek om te beginnen met het leren van C++ is C++ Primer.


Antwoord 5, autoriteit 13%

Laat me proberen dit ook te beantwoorden.

Aanwijzers zijn vergelijkbaar met verwijzingen. Met andere woorden, het zijn geen kopieën, maar eerder een manier om naar de oorspronkelijke waarde te verwijzen.

Allereerst, een plaats waar u doorgaans veel pointers moet gebruiken, is wanneer u met embedded hardwarete maken heeft. Misschien moet u de status van een digitale IO-pin omschakelen. Misschien verwerkt u een interrupt en moet u een waarde op een specifieke locatie opslaan. Je krijgt het beeld. Als je echter niet direct met hardware te maken hebt en je je afvraagt welke typen je moet gebruiken, lees dan verder.

Waarom pointers gebruiken in plaats van normale variabelen? Het antwoord wordt duidelijker als je te maken hebt met complexe typen, zoals klassen, structuren en arrays. Als je een normale variabele zou gebruiken, zou je uiteindelijk een kopie kunnen maken (compilers zijn slim genoeg om dit in sommige situaties te voorkomen en C++11 helpt ook, maar we zullen voorlopig wegblijven van die discussie).

Wat gebeurt er nu als u de oorspronkelijke waarde wilt wijzigen? Je zou zoiets als dit kunnen gebruiken:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

Dat werkt prima en als je niet precies weet waarom je pointers gebruikt, moet je ze niet gebruiken. Pas op voor de reden “ze zijn waarschijnlijk sneller”. Voer je eigen tests uit en als ze echt sneller zijn, gebruik ze dan.

Stel echter dat u een probleem oplost waarbij u geheugen moet toewijzen. Wanneer u geheugen toewijst, moet u de toewijzing ervan ongedaan maken. De geheugentoewijzing kan al dan niet succesvol zijn. Dit is waar aanwijzersvan pas komen – ze laten je toe om te testen op het bestaan van het objectdat je hebt toegewezen en ze geven je toegang tot het object waarvoor het geheugen is toegewezen door de verwijzing van de aanwijzer te verwijderen.

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

Dit is de sleutel tot waarom u de wijzers zou gebruiken – Referenties Neem aan dat het element waaruit u verwijst, bestaat al . Een aanwijzer doet dat niet.

De andere reden waarom u aanwijzingen (of ten minste uiteindelijk met hen moet gebruiken) is omdat ze een gegevenstype zijn dat bestond vóór referenties. Daarom, als u uiteindelijk bibliotheken gebruikt om de dingen te doen waarvan u weet dat ze beter zijn, zul je merken dat veel van deze bibliotheken aanwijzers overal gebruiken, simpelweg vanwege hoe lang ze zijn geweest (veel van hen zijn geschreven vóór C++).

Als u geen bibliotheken hebt gebruikt, kunt u uw code op een zodanige manier ontwerpen dat u weg kunt blijven van aanwijzingen, maar gezien de aanwijzingen zijn een van de basistypen van de taal, hoe sneller u ze comfortabel voelt , hoe draagbaarder uw C++ vaardigheden zijn.

Vanuit een onderhoudbaarheid oogpunt, ik wil ook vermelden dat wanneer u gebruik pointers, ofwel moet je test voor de geldigheid en behandelen de zaak als ze niet geldig zijn, of, gewoon aannemen dat ze geldig zijn en accepteer de feit dat uw programma of slechter zal crashen wanneer die aanname is gebroken. Anders gezegd, uw keuze met pointers is om ofwel te introduceren complexiteit van de code of meer onderhoud inspanning als er iets breekt en je probeert op te sporen een bug die behoort tot een hele klas van fouten die pointers te introduceren, zoals geheugen corruptie.

Dus als u al uw code te controleren, blijf weg van pointers en in plaats daarvan gebruik maken van referenties, waardoor ze const wanneer je kunt. Dit zal je dwingen om na te denken over het leven momenten van uw objecten en zullen eindigen met het houden van uw code gemakkelijker te begrijpen.

Bedenk dit verschil: Een verwijzing is in wezen een geldige pointer. Een pointer is niet altijd geldig.

Dus zeg ik dat het onmogelijk is om een ​​ongeldige verwijzing te maken? Nee. Het is volledig mogelijk, omdat C++ laat je bijna niets te doen. Het is gewoon moeilijker om onbedoeld te doen en je zult versteld staan ​​hoeveel bugs zijn onbedoeld:)


Antwoord 6, gezag 7%

Hier is een iets anders, maar inzichtelijke kijk op de reden waarom vele functies van C zinvol: http://steve.yegge.googlepages.com/tour-de-babel#C

In principe is de standaard CPU architectuur is een Von Neumann architectuur, en het is enorm handig zijn om te verwijzen naar de locatie van een data-item in het geheugen, en rekenen met het, op een dergelijke machine. Als u een variant van de assembler weet, zul je snel zien hoe belangrijk dit is op het lage niveau.

C++ maakt verwijzingen een beetje verwarrend, omdat het ze soms voor je beheert en hun effect verbergt in de vorm van ‘verwijzingen’. Als je rechte C gebruikt, is de behoefte aan pointers veel duidelijker: er is geen andere manier om call-by-reference te doen, het is de beste manier om een string op te slaan, het is de beste manier om door een array te itereren, enz.


Antwoord 7, autoriteit 6%

Een gebruik van pointers (ik zal geen dingen noemen die al in de berichten van anderen zijn behandeld) is om toegang te krijgen tot geheugen dat je niet hebt toegewezen. Dit is niet erg handig voor pc-programmering, maar het wordt gebruikt in embedded programmering om toegang te krijgen tot hardware-apparaten die zijn toegewezen aan het geheugen.

Vroeger in de oude tijd van DOS had je rechtstreeks toegang tot het videogeheugen van de videokaart door een aanwijzer te declareren naar:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

Veel embedded apparaten gebruiken deze techniek nog steeds.


Antwoord 8, autoriteit 5%

Aanwijzers zijn grotendeels arrays (in C/C++) – het zijn adressen in het geheugen en kunnen desgewenst als een array worden benaderd (in “normale” gevallen).

Omdat ze het adres van een item zijn, zijn ze klein: ze nemen alleen de ruimte van een adres in beslag. Omdat ze klein zijn, is het goedkoop om ze naar een functie te sturen. En dan laten ze die functie werken aan het eigenlijke item in plaats van een kopie.

Als je dynamische opslagtoewijzing wilt doen (zoals voor een gekoppelde lijst), moet je aanwijzers gebruiken, omdat dit de enige manier is om geheugen uit de heap te halen.


Antwoord 9, autoriteit 5%

Aanwijzers zijn belangrijk in veel datastructuren waarvan het ontwerp de mogelijkheid vereist om het ene “knooppunt” efficiënt aan het andere te koppelen of te ketenen. Je zou een aanwijzer niet “kiezen” boven een normaal gegevenstype zoals float, ze hebben gewoon verschillende doelen.

Aanwijzingen zijn handig wanneer u hoge prestaties en/of compact geheugen nodig heeft.

Het adres van het eerste element in uw array kan worden toegewezen aan een pointer. Hierdoor heb je dan direct toegang tot de onderliggende toegewezen bytes. Het hele punt van een array is echter om te voorkomen dat je dit moet doen.


Antwoord 10, autoriteit 5%

Een manier om aanwijzers over variabelen te gebruiken, is door dubbel geheugen te elimineren. Als u bijvoorbeeld een groot complex object heeft, kunt u een aanwijzer gebruiken om naar die variabele te verwijzen voor elke verwijzing die u maakt. Met een variabele moet je het geheugen voor elke kopie dupliceren.


Antwoord 11, autoriteit 4%

Als u in C++ het subtype polymorfismewilt gebruiken, moetenaanwijzers gebruiken. Zie dit bericht: C++ Polymorphism without pointers.

Echt, als je erover nadenkt, is dit logisch. Wanneer u subtype polymorfisme gebruikt, weet u uiteindelijk niet van tevoren welke klasse of subklasse de implementatie van de methode zal aanroepen, omdat u niet weet wat de werkelijke klasse is.

Dit idee om een variabele te hebben die een object van een onbekende klasse bevat, is incompatibel met de standaard (non-pointer) modus van C++ voor het opslaan van objecten op de stapel, waarbij de hoeveelheid toegewezen ruimte direct overeenkomt met de klasse. Opmerking: als een klasse 5 instantievelden heeft in plaats van 3, moet er meer ruimte worden toegewezen.


Merk op dat als u ‘& amp;’ gebruikt Om argumenten door verwijzing door te geven, is Indirection (d.w.z. Pointers) nog steeds betrokken bij de schermen. De ‘& amp; Is gewoon syntactische suiker die (1) u de problemen opslaat om aanwijzer syntaxis te gebruiken en (2) kan de compiler strenger zijn (zoals het verbieden van null-aanwijzingen).


Antwoord 12, Autoriteit 3%

omdat het kopiëren van grote objecten over de hele plaatsen tijd en geheugen verspilt.


Antwoord 13, Autoriteit 3%

De behoefte aan aanwijzingen in C-taal is beschreven hier

Het basisidee is dat veel beperkingen in de taal (zoals het gebruik van arrays, snaren en het modificeren van meerdere variabelen in functies) kunnen worden verwijderd door te manipuleren met de geheugenlocatie van de gegevens. Om deze beperkingen te overwinnen, werden aanwijzingen geïntroduceerd in c.

Verder is het ook gezien dat het gebruik van aanwijzers uw code sneller kunnen uitvoeren en het geheugen opslaan in gevallen waarin u grote gegevenstypen passeert (zoals een structuur met veel velden) naar een functie. Het maken van een kopie van dergelijke gegevenstypen voordat het doorgegeven zou duren en zou het geheugen consumeren. Dit is een andere reden waarom programmeurs de voorkeur geven aan pointers voor grote gegevenstypen.

PS: zie de link verstrekt voor gedetailleerde uitleg met voorbeeldcode.


Antwoord 14, Autoriteit 3%

Hier is mijn Anwser, en ik zal niet promère een expert zijn, maar ik heb aanwijzingen gevonden om geweldig te zijn in een van mijn bibliotheken die ik probeer te schrijven. In deze bibliotheek (het zijn een grafische API met OpenGL :-)) Je kunt een driehoek creëren met vertex-objecten die erin zijn doorgegeven. De Draw-methode neemt deze driehoeksobjecten en goed .. trekt ze op basis van de vertex-objecten die ik heb gemaakt. Nou, het is goed.

Maar wat als ik een hoekpuntcoördinaat verander? Verplaats het of iets met moveX() in de vertex-klasse? Nou, ok, nu moet ik de driehoek bijwerken, meer methoden toevoegen en de prestaties worden verspild omdat ik de driehoek moet bijwerken elke keer dat een hoekpunt beweegt. Nog steeds geen big deal, maar het is niet zo geweldig.

Wat als ik een mesh heb met tonnen hoekpunten en tonnen driehoeken, en de mesh draait en beweegt, en dergelijke. Ik moet elke driehoek bijwerken die deze hoekpunten gebruikt, en waarschijnlijk elke driehoek in de scène, omdat ik niet zou weten welke welke hoekpunten gebruiken. Dat is enorm computerintensief, en als ik meerdere mazen op een landschap heb, oh god! Ik zit in de problemen, want ik update elke driehoek, bijna elk frame, omdat deze hoekpunten voortdurend veranderen!

Met aanwijzers hoeft u de driehoeken niet bij te werken.

Als ik drie *Vertex-objecten per driehoeksklasse had, bespaar ik niet alleen ruimte omdat een ontelbaar aantal driehoeken niet drie vertex-objecten hebben die zelf groot zijn, maar deze wijzers zullen ook altijd wijzen naar de hoekpunten waarnaar ze zijn bedoeld , ongeacht hoe vaak de hoekpunten veranderen. Omdat de wijzers nog steeds naar hetzelfde hoekpunt wijzen, veranderen de driehoeken niet en is het updateproces gemakkelijker te hanteren. Als ik je in de war zou brengen, zou ik er niet aan twijfelen, ik pretendeer geen expert te zijn, ik gooi gewoon mijn twee cent in de discussie.


Antwoord 15, autoriteit 2%

In java en C# zijn alle objectreferenties pointers, het ding met c++ is dat je meer controle hebt over waar je pointers naartoe verwijzen. Onthoud Met grote kracht komt grote verantwoordelijkheid.


Antwoord 16

Wat betreft uw tweede vraag, u hoeft over het algemeen geen pointers te gebruiken tijdens het programmeren, maar er is één uitzondering hierop en dat is wanneer u een openbare API maakt.

Het probleem met C++ constructeert dat mensen over het algemeen gebruiken om aanwijzers te vervangen, zijn zeer afhankelijk van de toolset die u gebruikt, wat goed is wanneer u alle controle hebt die u hebt over de broncode, maar als u een statische bibliotheek compileert met visuele studio 2008 bijvoorbeeld en probeer het te gebruiken in een Visual Studio 2010 krijgt u een hoop linkerfouten omdat het nieuwe project is gekoppeld aan een nieuwere versie van STL die niet achterwaarts compatibel is. Dingen worden zelfs NAAFIER als u een DLL compileert en een importbibliotheek geeft die mensen in een andere toolset gebruiken, omdat uw programma in dat geval sneller of later zal botseren zonder duidelijke reden.

Dus voor het bewersten van grote gegevenssets van de ene bibliotheek naar het andere, kunt u overwegen om een ​​aanwijzer te geven aan een array op de functie die de gegevens moet kopiëren als u anderen niet wilt forceren om dezelfde tools te gebruiken die u gebruikt. Het goede deel hierover is dat het niet eens een C-stijl-array hoeft te zijn, u kunt een STD :: Vector gebruiken en de aanwijzer geven door het adres van het eerste element & amp; Vector [0] bijvoorbeeld, En gebruik de STD :: Vector om de array intern te beheren.

Een andere goede reden om pointers in C++ weer te gebruiken, heeft betrekking op bibliotheken. Overweeg een dll te hebben die niet kan worden geladen wanneer uw programma wordt uitgevoerd, dus als u een importbibliotheek gebruikt, wordt niet aan de afhankelijkheid voldaan en crasht het programma. Dit is bijvoorbeeld het geval wanneer u naast uw applicatie een publieke api in een dll geeft en deze vanuit andere applicaties wilt benaderen. In dit geval, om de API te gebruiken, moet je de dll laden vanaf zijn ‘locatie (meestal is het in een registersleutel) en dan moet je een functieaanwijzer gebruiken om functies binnen de DLL te kunnen aanroepen. Soms zijn de mensen die de API maken aardig genoeg om je een .h-bestand te geven dat helperfuncties bevat om dit proces te automatiseren en je alle functiewijzers te geven die je nodig hebt, maar zo niet, dan kun je LoadLibrary en GetProcAddress gebruiken op windows en dlopen en dlsym op unix om ze te krijgen (aangezien je de volledige handtekening van de functie kent).


Antwoord 17

  • In sommige gevallen zijn functieaanwijzers vereist om functies te gebruiken die zich in een gedeelde bibliotheek (.DLL of .so) bevinden. Dit omvat het uitvoeren van dingen in verschillende talen, waar vaak een DLL-interface wordt geboden.
  • Compilers maken
  • Wetenschappelijke rekenmachines maken, waar je een matrix- of vector- of stringkaart van functiewijzers hebt?
  • Proberen het videogeheugen rechtstreeks te wijzigen – uw eigen grafische pakket maken
  • Een API maken!
  • Gegevensstructuren – knooppuntlink-pointers voor speciale bomen die u maakt

Er zijn veel redenen voor pointers. Vooral het mangelen van C-namen is belangrijk in DLL’s als je de compatibiliteit tussen talen wilt behouden.

Other episodes