Uppföljning till lektion 5 om pekare Pekare, structar och rekursiva funktioner kan sannerligen vara lite knepigt att förstå. Denna lilla skrift är ett försök att me hjälp av många illustrationer göra et hela något klarare. Grunläggane symboler Ett ataobjekt är en plats i minnet är vären av en viss bestäm typ kan lagras. I etta sammanhang beskrivs ataobjekt me e här symbolerna, som anger atatyp och aktuellt väre. Dataobjekt behöver e vara associerae me ett varibelnamn. 36 char P Variabler ritas så här, vilket ska visa att et finns en association mellan ett namn och ett ataobjekt. Till vänster har Value ännu e fått något väre (et är oefinierat), till höger har Value fått väret -19. Value? Value -19 Dataobjekt som är pekarvären visas me essa symboler. Här heter en tänkta pekartypen Ref, och från vänster till höger har vi ett oefinierat pekarväre, icke-pekaren (väret 0), och ett pekare till något annat ataobjekt (e visat). Ref? Ref Ref En struct, en pekartyp och lite variabler Vi gör följane efinitioner: typeef struct Noe* ; struct Noe { ata; next; }; Noe Ett ataobjekt av typen Noe tecknas å som till höger. Det består ju av två inre variabler me varsina namn och atatyper. Från värena bortser vi för ögonblicket. I något sammanhang görs essa två variabeleklarationer: Noe my_noe; = 0; Innehållet i my_noes två komponenter är förstås oefinierat. Observera också att my_noe här är (irekt) en Noe, en är e en pekare till en sån. my_noe Noe??
Vi tänker oss nu att et är main vi betraktar. Där görs etta: = new Noe; -> ata = 8; -> next = 0; För att tyligt visa var variabler och ataobjekt hör hemma införs också e streckae boxarna u ser här. Namnet hör till main, och är finns också et associerae ataobjektet. Noen har äremot skapats i et ynamiska utrymmet (heapen), är objekt alrig får namn automatiskt. Noe 8 Parameteröverföring Detta lilla avsnitt använer symbolerna ovan för att illustrera en väsentliga skillnaen mellan vanliga parametrar (väreparametrar) och referensparametrar. Vi utgår från situationen närmast ovan, är mains variabel pekar på en Noe. Vi antar också att et existerar en funktion me specifikationen voi foo( pnt); Va en gör är utan resse, eftersom enast et som häner vi anropet och överföring av parametern ska beskrivas. main gör foo(), och när foo sean startar gäller figuren nean. Det karakteristiska är att pnt skapas som en ny variabel i foo, och att ess väre är blir en kopia av s väre. Om väret av pnt föränras påverkar et e s väre! foos variabler pnt Noe 8 Nean råer samma utgångsläge, men main gör i stället anropet fie(), är fie har specifikationen voi fie(& pnt). I etta fall blir fies pnt e en ny variabel, utan bara ett nytt (tillfälligt) namn på ett rean existerane ataobjekt. I et fall pnt ges något nytt väre blir etta också nytt väre i mains! fies variabler pnt Noe 8
Funktionen insert_first Den funktion som lektionen hanlae om kommer här. Kanhäna namnen på parametrarna är anra, men uppträanet är etsamma. Om l är en är avsikten att insert_first(l,v) ska sätta in en ny no innehållane :en v först i l. voi insert_first(&, ) { = new Noe; -> ata = ; -> next = ; = ; } Här följer nu en genomgång av hur insert_first jobbar, steg för steg, i ett par olika fall. main gör först = 0; och ärefter anropet insert_first(, ); voi insert_first(&, ) { I insert_first blir givetvis bara ett annat namn på ataobjektet i main (ett alias), mean blir en helt ny variabel vars väre är en kopia av et som main skickar. = new Noe; -> ata = ; En ny Noe skapas, och sätts att peka på en. Därefter får ata-fältet i en nya noen sitt väre. Noe? -> next = ; Här får en nya noens next-fält samma väre som (vs icke-pekaren 0). Noe
= ; När sätts att peka på samma no som innebär et att också i main får enna pekare som väre. Därefter lämnas insert_first. Noe Tillbaka i main. main gör nu ett nytt anrop, insert_first(, ); Alleles efter att anropet är bilen enna: Noe = new Noe; -> ata = ; sätts att peka på en ny no, vars ata-fält sen får sitt väre. Noe? Noe
-> next = ; Nu sätts en nya noen in före en rean existerane, genom att en nya noens next-fält sätts att peka på samma väre som har. Noe Noe = ; Slutligen kopieras s väre till (och ärme till!), och i och me etta har en senaste nya noen hamnat före alla tiigare. Noe Noe
Sammanfattning av insert_first insert_first tar emot a) ett -objekt och en, b) skapar via en ny Noe me :en och s väre som innehåll och c) kopierar till sist pekaren till nya Noen (vs ) till. Nean följer en illustration av en serie av tre anrop av insert_first me hjälp av lite enklare symboler: a) b) c) anrop 1 14 ny no efter före anrop 2 22 ny no efter före 14 anrop 3 8 ny no efter före 22 14 Det är nu e növänigt att betecknar starten på en lista. Den kan vara ett -objekt var som he i en keja av noer. Skissen här visar att är associera me next-fältet i en anra av noerna i en längre lista. Effekten av insert_first i et här fallet blir att en ny no läggs in irekt efter en no är råkar vara placera, och före återstoen av listan. Detta är viktigt att inse för att förstå nästa funktion. huvulista huvulista 13 13 23 ny no 18 29 44 före 23 ny no 18 29 44 efter Funktionen insert Denna funktion presenteraes också på lektionen. insert(l, v) ska sortera in en ny no me v som innehåll på rätt plats i listan l. l förväntas i förväg vara en tom lista eller vara sortera i stigane orning, och ska alltså bibehålla sorteringsorningen. voi insert(& hea, ) { if(hea == 0 < hea -> ata) insert_first(hea, ); else insert(hea -> next, ); }
main gör nu på nytt = 0; för att starta me en tom lista, och gör sen anropet insert(, ); voi insert(& hea, ) { Direkt efter anropet ser et ut så här. hea blir alias för. inserts variabler hea if(hea == 0 < hea -> ata) insert_first(hea, ata); Eftersom hea verkligen är 0 sker här anropet till insert_first. voi insert_first(&, ) { blir nytt namn på et ataobjekt som hör till hea, som fortfarane är etsamma som s. Alltså, samma ataobjekt har nu tre olika namn, i olika sammanhang. inserts variabler hea När insert_first gjort sitt jobb, så när som på en sista raen, har vi följane situation. inserts variabler hea Noe
= ; Till sist får /hea/ sitt väre från, varefter insert_first lämnas. inserts variabler hea Noe Nu lämnas också insert, och vi är tillbaka i main. Där är nu en lista me väret som ena innehåll. main gör sen anropet insert(, 49); if(hea == 0 < hea -> ata) insert_first(hea, ata); hea är e 0 (vs är e tom) och (49) är > hea->ata () så villkoret är e uppfyllt. else insert(hea -> next, ); Det betyer att insert anropar sig själv, rekursion uppstår alltså!
voi insert(& hea, ) { // anra gången! I figuren nean visar en övre insert (1) läget när main anropae insert. hea är är samma ataobjekt som mains, eftersom et var et objektet som main överföre till hea. När insert anropar sig själv gäller insert(2). När et anropet sker är et hea->next i version 1 som överförs till hea i version 2 av insert. Det förekommer alltså 2 hea, men alla utom en senaste är ola. inserts (1) variabler hea 49 inserts (2) variabler hea 49 Noe if(hea == 0 < hea -> ata) insert_first(hea, ata); Det är nu insert(2) som är en exekverane insert, mean insert(1) och ess hea vilar. heas väre är å 0, villkoret alltså uppfyllt och ärme anropas insert_first. insert_first kommer som vanligt att lägga en ny no (nu me 49) först i et -objekt en får som parameter. Efter att insert_first gjort sitt jobb och vi återvänt till insert(2) ser et ärme ut så här: inserts (1) variabler hea 49 inserts (2) variabler hea 49 Noe Noe 49
insert(2) har jobbat klart och vi återväner till en som gjort anropet. Detta är insert(1). Den är nu också färig, och vi återväner å till ess anropare, som är main. På vägen tillbaka har ingenting räffat som påverkat hea, och sluteffekten är att nu består av och 49, i en orningen. Sammanfattning av insert Som u tiigare insett är et insert_first som gör jobbet att skapa en ny no och placera in en på en ut plats. Det insert gör är egentligen bara att hitta en position (en no) är en ny no ska in. Därefter överlåts jobbet åt insert_first. insert arbetar rekursivt, och i varje varv i rekursionen (i varje ny version av insert) har variabeln hea stegats fram till nästa -objekt i listan. Det första -objektet är et som utifrån pekar på listans första no, e återståene finns i e successiva noerna. Detta stegane pågår till ess hea är antingen 1) ett -objekt me icke-pekaren, eller 2) ett -objekt som pekar ut en no före vilken en nya noen ska placeras in. Det ska ha framgått av figurerna ovan, men kan förtjäna att förtyligas: varje ny version av insert har också en ny, egen version av variabeln/parametern hea. De olika versionernas resp hea får e förväxlas me varanra. Det är allti en exekverane versionens hea som gäller e älre versionernas öljs tillsviare. När insert(2) lämnas försvinner ess hea, insert(1) väcks upp och ess hea blir i stället en råane. Anropet av insert_first är alltså slutvillkoret i rekursionen; när et gjorts sker e längre några ytterligare anrop av insert, eftersom jobbet har blivit gjort. På återvägen från båe anropet av insert_first och varje rekursivt anrop gör insert ingenting alls, så ingen ytterligare påverkan på hea kommer att ske.