[Powered by Google Translate] [Pekare]
[Rob Bowden] [Harvard University]
[Detta är CS50] [CS50.TV]
>> Låt oss tala om pekare.
Hittills har vi alltid bara hänvisas till saker i minnet uttryckligen med namn.
Vi har sagt int n = 42, och sedan när vi vill använda variabeln n,
vi kallar det bara vid namn vi ger det genom att skriva något i stil med N * 2.
Men den variabeln måste leva någonstans i minnet.
När du vill använda det värde som för närvarande lagras inuti N,
eller uppdatera värdet att n håller,
ditt program behöver veta var i minnet för att leta efter n..
Var i minnet en variabel liv kallas dess adress.
Det är som ett hus adress.
Jag kan hitta någons hus så länge jag vet att deras hemadress,
och ett datorprogram kan hitta en variabel så länge den känner dess minnesadress.
Vilka pekare ger är ett sätt att direkt ta itu med dessa minnesadresser.
>> En hel del av kraften i C kommer från att kunna manipulera minnet så här.
Men med stor makt kommer stort ansvar.
Pekare kan användas farligt nog att en hel del programmeringsspråk
dölja pekare helt.
Så, varför C ger oss pekare då?
Som ni vet argument till en C-funktion
kopieras alltid in parametrarna för funktionen.
Så ringer något liknande swap på vissa variabler x och y
kan inte byta värdena för x och y i den anropande funktionen,
även om det kan vara praktiskt.
Som vi kommer att se senare, skriva swap ta pekare till de platser som behöver bytas
gör det möjligt att påverka dess uppringarens variabler.
>> Låt oss gå igenom en relativt enkel exempel på vad pekare kan göra.
Låt oss säga att vi har int n = 4, och int * pointer_to_n = & n.
Whoa! Lite ny syntax för att täcka.
Först, låt oss tolka detta & n.
Kom ihåg att allt i minnet har någon adress.
Et-tecknet kallas "adress" operatör.
Så hänvisar & n till adressen i minnet där n lagras.
Nu är vi lagrar denna adress i en ny variabel, pointer_to_n.
Vilken typ av denna nya variabel?
Asterisken är en del av variabelns typ,
och vi läser den typ som int *.
Int * betyder att pointer_to_n är en variabel som lagrar adressen för ett heltal.
Vi vet att & n är en int * eftersom n är ett heltal, och vi tar adressen till n.
Int * är ett exempel på en pekare typ.
Så snart du börjar se asterisker i typ,
du vet att du har att göra med pekare.
Precis som vi kan deklarera en variabel som int x och röding y,
Vi kan säga int * z och char * w.
Int * och char * är bara nya typer för oss att använda.
Placeringen av * kan gå någonstans innan variabelnamnet.
Så både int * pointer_to_n - med * bredvid int, som vi har här -
och int * pointer_to_n med * bredvid pointer_to_n är giltiga.
Men här, jag placerar * bredvid int.
Det spelar ingen roll vilken du föredrar, bara vara konsekvent.
>> Låt oss rita ett diagram för detta.
Vi har först variabeln n, som vi kommer att dra en liten ask minne.
För detta exempel, låt oss säga att den här rutan ligger på adress 100.
Inuti denna ruta, vi lagrar värdet 4.
Nu har vi en ny variabel, pointer_to_n.
Den har sin egen låda i minnet,
som vi kommer att säga är adress 200.
Inuti denna ruta, vi lagrar adressen till n,
som vi sagt tidigare var 100.
Ofta i diagram, ser du här visas som en bokstavlig pil
lämnar pointer_to_n rutan pekar på rutan som lagrar n.
Nu, vad kan vi faktiskt göra med pointer_to_n?
Tja, om vi säger något i stil med * pointer_to_n = 8, är detta en annan användning för asterisk
som är helt separat från användningen av asterisken i förklara en variabel
av en pekare typ.
Här är asterisken kallas dereference operatör.
I vårt diagram, vad * pointer_to_n = 8 betyder är,
Gå till rutan som innehåller pointer_to_n, följ pilen,
och sedan tilldela rutan i slutet av pilen värdet 8.
Detta innebär att efter denna linje, om vi försöker använda n kommer att ha värdet 8.
Ordet "pekare" används i en mängd olika sammanhang.
Här kommer vi försöka vara konsekvent.
En pekare typ är något som int *.
I den här videon kommer en pekare endast användas för att betyda ett värde med en pekare typ,
Liksom pointer_to_n som har * typen int.
Någonstans vi brukade bara säga n kan vi nu istället säga * pointer_to_n,
och allt kommer att fungera lika bra.
>> Låt oss gå igenom en annan enkelt exempel.
Låt oss säga att vi har int n = 14, int * pekare = nanoteknik n + +, och (* pekare) + +.
Den första raden skapas en ny ruta i minnet märkt N.
Den här gången kommer vi märka inte lådan med en uttrycklig adress, men den har fortfarande en.
Inuti lådan, vi lagrar numret 14.
Nästa rad skapar en andra rutan pekare.
Och insidan av denna ruta, vi lagrar en pekare till rutan n.
Så, låt oss dra pilen från pekaren till n.
Nu, n + + ökar värdet i rutan n,
så vi går från 14 till 15.
Slutligen (* pekare) + + går till rutan pekaren,
dereferences värdet i rutan, vilket innebär att följa pilen till där det pekar,
och ökar värdet lagras där, så vi går från 15 till 16.
Och det är det.
N lagrar nu den 16 efter att ha ökats två gånger -
gång direkt med variabelnamnet n, och den andra genom en pointer_to_n.
>> Snabb frågesport. Vad tror du det innebär om jag försöker säga något i stil med && n?
Nåväl, låt oss skriva detta som & (& N), som åstadkommer samma sak.
Den (& N) returnerar adressen för variabeln n i minnet.
Men sedan då yttre-tecken försöker returnera adressen till adressen.
Det är som att försöka göra och 2.
Det är inte meningsfullt att få adressen till några nummer
eftersom det inte är lagrat i minnet.
Med hjälp av två et-tecken i rad är aldrig rätt idé.
Men nu, vad betyder det om jag försöker säga int ** double_pointer = & pekare?
Nu är jag skapar en ny låda märkt double_pointer,
och insidan av den låda jag lagra adress pekaren,
vilket betyder att jag drar en pil från double_pointer rutan till pekaren rutan.
Lägg märke till typen av double_pointer, en int **.
N var ett heltal, pekare lagrad adress n och så har * typen int.
Nu lagrar double_pointer adress pekaren, så det har typen int. **
>> Så vad vi tror att detta innebär -
** Double_pointer = 23?
Observera att jag nu är dereferencing två gånger.
Följ bara rutan-och pil diagram vi redan inrättats.
Först går vi till rutan double_pointer.
Den första * betyder följer pilen gång.
Nu är vi på rutan pekaren.
Den andra stjärnan säger följer pilen igen,
och nu är vi på rutan n och vi sätter värde rutan för att 23.
Lägg märke till att dereference och "adress" operatörer inverser av varandra.
Detta gör att jag kan göra något liknande * & * & n = 42.
Medan detta fungerar ska du aldrig göra något liknande i praktiken.
Vad gör vi egentligen här?
Den första-tecken griper adress variabeln n.
Sedan har vi en dereference operatör, vilket innebär att vi kommer till den adressen i minnet,
så vi är tillbaka på n..
Nu, ta vi adressen till n igen och omedelbart dereference,
så vi är tillbaka på n och lagra 42.
Så varje par av * & bara upphäver.
>> Det finns en speciell pekare som kallas NULL-pekare.
Detta är en pekare som vi borde aldrig dereference.
En sådan pekare är viktigt eftersom det ger oss ett sätt att skilja mellan
en pekare som bör och inte bör dereferenced.
Om du försöker dereference en nollpekare,
vanligtvis ditt program kommer att krascha med ett segmentation fault,
som du kan ha sett förut.
Så, låt oss säga att vi har koden int * x = null; * x = 4.
I detta exempel kan det tyckas självklart att vi gör något dåligt,
men kom ihåg att null verkligen kan vara ett värde som returneras från ett anrop till en funktion
Liksom malloc, är om malloc inte allokera det minne som begärs av användaren.
Av denna anledning, om vi i stället ange värdet av x från ett samtal till malloc,
som i int * x = malloc (sizeof (int)), bör vi alltid explicit kontrollera
för att se om noll returnerades.
Om (x == null) / / uhoh! avkastning, annars kan vi fortsätta och säga * x = 4.
>> Så, återigen, varför skulle vi använda någonsin pekare?
Låt oss titta på ett exempel på ett program där vi behöver använda pekare -
en enkel swap funktion.
Låt oss säga att jag har två heltal, int x = 4 och int y = 15;
och jag vill skriva en funktion som kallas swap som jag kan använda som så: swap (x, y).
Efter denna linje bör värdena insidan av variabeln x är 15,
och värdet inuti variabel y skall vara 4.
Värdena inuti x och y har bytts.
Utan pekare, kan vi prova något liknande void swap (int a, int b);
int tmp = b, b = a, a = tmp.
Men märker du problemet med detta?
Kom ihåg att det lagrade värdet i variabeln a är bara en kopia av värdet av x,
och värdet i b kopieras från y.
Alla ändringar som görs i a och b inte kommer att återspeglas i x-och y.
Så medan värdena för a och b är korrekt swappas,
x och y har inte ändras alls.
Nu ska vi ändra swap-funktionen så att dess argument
är pekare till de variabler bör Pendla, så här:
void swap (int * a, int * b); int tmp = * b, * b = * a, * a = tmp.
Kom ihåg att swappar argument nu pekare,
så vi behöver för att klara adress x och y i samtalet att byta, så här:
swap (& x, och y).
Detta nu korrekt swappar värdena för x och y.
>> Låt oss rita en ruta-och pil diagrammet för att se varför detta fungerar.
Vi börjar med våra två lådor i minnet, x och y.
Inuti lådan för x är antalet 4 och insidan av lådan för y vi 15.
Nu, inne i anropet till swap-funktionen har vi två lådor
för argumenten a och b;
en pekar på rutan för x och b pekar på rutan för y.
En ny ruta skapas för variabeln tmp,
och insidan av det vi lagrar resultatet av dereferencing b,
som betyder "följer pilen från rutan b.."
Så lagrar vi 15 inuti tmp.
Sedan följer vi pilen på b och lagra Här är resultatet av dereferencing en,
vilken är värdet 4.
Slutligen följer vi pilen på och lagra vad som för närvarande inne i TMP, vilket är 15.
Observera att rutorna x och y korrekt bytt värden.
>> När vi lär oss mer om malloc och dynamisk minneshantering,
Vi kommer att se att vi inte har något annat val än att använda pekare.
Vandring genom rutan-och pil diagram för alla program
kan hjälpa dig att räkna ut vad programmet egentligen gör.
>> Mitt namn är Rob Bowden, och detta är CS50.
[CS50.TV]
>> Detta är en annan användning för asterisk - bleah, jag hatar det ordet.
Någonstans vi brukade bara säga N, kan vi säga nu pointer_to_n - nej du Värre - * pointer_to_n.