Föreläsning 9 Pekare, länkade noder, länkade listor TDDD86: DALP Utskriftsversion av föreläsning i Datastrukturer, algoritmer och programmeringsparadigm 25 september 2015 Tommy Färnqvist, IDA, Linköpings universitet 9.1 Innehåll Innehåll 1 Pekare och länkade noder 1 1.1 Introduktion.......................................... 1 1.2 Pekare............................................. 1 1.3 Objekt............................................. 4 1.4 Länkade noder........................................ 5 2 Länkade listor 7 9.2 1 Pekare och länkade noder 1.1 Introduktion Arraybaserade datastrukturer Flera containrar använder en array/ett fält av element för lagring Vilka för/nackdelar finns det med att använda en array? Fördelar: snabbt att lägga till/ta bort i slutet; snabb åtkomst till elementen Nackdelar: långsamt att lägga till/ta bort i mitten av arrayen; ofylld array slösar med minne; behöver öka kapaciteten när den blir full 9.3 1.2 Pekare Länkade datastrukturer Andra containrar använder sammanlänkade nodobjekt för att lagra data Varje nodobjekt lagrar ett dataelement och en länk till någon annan nod Fördelar: snabbt att lägga till/ta bort data i alla positioner Nackdelar: långsamt att komma åt vissa delar av datastrukturen För att förstå länkade strukturer måste vi förstå pekare... 9.4 1
Minnesadresser När en variabel deklarerats lagras den någonstans i minnet Vi kan be om en variabels minnesadress med &-operatorn Minnesadresser skrivs vanligen som hexadecimala (bas-16) tal Många vanliga datatyper använder 4 bytes (32 bitar) minne int x = 42; int y = 17; int a[3] = {91, -3, 85; cout << x << endl; // 42 cout << &x << endl; // 0x7f8e20 cout << y << endl; // 17 cout << &y << endl; // 0x7f8e24 cout << &a[0] << endl; // 0x7f8e28 cout << &a[1] << endl; // 0x7f8e2c cout << &a[2] << endl; // 0x7f8e30 9.5 Pekare pekare: En variabel som lagrar en minnesadress Pekare deklareras med * efter typen de pekar till Vi kan referera till värdet pekaren pekar på genom att skriva * före pekaren int x = 42; int* p = &x; cout << p << endl; // 0x7f8e20 cout << *p << endl; // 42 *p = 99; cout << x << endl; // 99 9.6 Pekararitmetik Vi kan ändra vad en pekare pekar på med +, -, ++, --, etc. Att inkrementera en T* med 1 flyttar fram pekaren motsvarande storleken av en T i minnet Vi kan använda syntaxen [k] för att komma åt minne k platser bort från pekaren (En arrayvariabel är egentligen bara en pekare till arrayens första element) int a[4] = {91, -2, 85, 17; int* p = a; // p = &a[0]; p[1] = 5; // a[1] = 5; p++; // p = &a[1]; cout << *p << endl; // 5 *(p + 2) = 26; // a[3] = 26; cout << p[2] << endl; // 26 2
9.7 Skräppekare Om de är oinitierade pekar pekare på godtyckliga platser i minnet. Om vi följer en sådan pekare kraschar antagligen programmet! int x = 42; int* p; cout << p << endl; cout << *p << endl; int a[3] = {91, -2, 85; int* p2 = a; cout << p2[5] << endl; // lagrar skräp // 0xd34d00 // KABOOM // BOOM 9.8 nullptr nullptr: Pekarliteral som betyder pekar på ingenting Tänkt att användas som ett tomvärde att initera pekare med Att avreferera en tompekare kraschar programmet! int x = 42; int* p = nullptr; cout << p << endl; // 0 cout << *p << endl; // KABOOM // testa för tomhet if (p == nullptr) {... // true 9.9 3
1.3 Objekt Pekare till objekt Vi kan peka på objekt För att referera till medlemmar i objektet, skriv object->member Samma sak som (*object).member Date d(9, 19); Date* p = &d; cout << p->daysinmonth() << endl; // 30 p->nextday(); cout << *p << endl; // 20/9 9.10 Objekts livstid Deklarerade objekt lever till slutet av nuvarande funktions definitionsområde Detta kallas statisk allokering eller allokering på stacken Om vi returnerar ett objekt kopieras det; originalet förstörs fortfarande Hur tillverkar vi ett objekt som överlever funktionen det skapas i? void foo() { int x = 42; Date d1(9, 19);... // x, d1 förstörs int main() { int a = 17; foo(); Date d2(10, 14);... return 0; // a, d2 förstörs 9.11 Dynamisk allokering Operatorn new allokerar långtidsminne för ett objekt Kallas dynamisk allokering eller allokering på heapen Låter objektet leva även efter funktionens definitionsområde tagit slut Minne för objekt allokerade på heapen måste frigöras explicit med delete void foo() { Date* d = new Date(10, 14); cout << d->daysinmonth() << endl; // 31 d->nextday(); cout << d << endl; // 0x3a67f4 cout << *d << endl; // 15/10... delete d; 9.12 4
1.4 Länkade noder Datastruktur för länkade noder struct ListNode { int data; ListNode* next; ; Varje listnodsobjekt lagrar: ett heltalsvärde en pekare till en listnod Listnoder kan länkas samman i kedjor för att lagra en lista av värden 9.13 Listnodsklient int main() { ListNode* list = new ListNode(); list->data = 42; list->next = new ListNode(); list->next->data = -3; list->next->next = new ListNode(); list->next->next->data = 17; list->next->next->next = nullptr; cout << list->data << " " << list->next->data << " " << list->next->next->data << endl; // 42-3 17... return 0; 9.14 Länkade noder: problem 1 Vilken sekvens av operationer omvandlar den här bilden: Till den här bilden? 9.15 Länkade noder: problem 2 Vilken sekvens av operationer omvandlar den här bilden: Till den här bilden? 9.16 5
Länkade noder: problem 3 Vilken sekvens av operationer omvandlar den här bilden: Till den här bilden? 9.17 Länkade noder: problem 4 Vilken sekvens av operationer omvandlar den här bilden: Till den här bilden? 9.18 Länkade noder: problem 5 Antag att vi har en lång kedja av listnoder ListNode* list = new ListNode(10); list->next = new ListNode(20); list->next->next =...; Vi vet inte exakt hur lång kedjan är Hur gör vi för att skriva ut datavärdena i alla noder? 9.19 Traversera en lista? Ett (dåligt) sätt att skriva ut alla värden i listan: while (list!= nullptr) { cout << list->data << endl; list = list->next; // flytta till nästa nod Vad är problemet med att göra på det här sättet? Vi tappar bort listan samtidigt som vi skriver ut den! 9.20 6
Listtraversering Rätt sätt att skriva ut alla värden i listan: ListNode* current = list; while (current!= nullptr) { cout << current->data << endl; current = current->next; // flytta till nästa nod Att ändra current skadar inte listan 9.21 2 Länkade listor LinkedList Låt oss skriva en containerklass LinkedList Har samma medlemmar som ArrayList add, clear, get, insert, isempty, remove, size, tostring Listan representeras internt som en kedja av länkade noder Listan har en pekare till den första noden i listan nullptr signalerar slutet av listan; är första noden nullptr är listan tom 9.22 LinkedList.h class LinkedList { public: LinkedList(); ~LinkedList(); void add(int value); void clear(); int get(int index) const; void insert(int index, int value); bool isempty() const; void remove(int index); void set(int index, int value); int size() const; string tostring() const; private: int m_size; ListNode* m_front; ; 9.23 7
Implementera add // Appends the given value to the end of the list. void LinkedList::add(int value) {... Hur adderar vi en ny nod till slutet av listan? Spelar listans innehåll innan operation någon roll? 9.24 Add på en tom lista Före add(20): Efter: 9.25 Add på en icke-tom lista Före add(20) Efter add(20) 9.26 Kod för add // Adds the given value to the end of the list. void LinkedList::add(int value) { if (m_front == nullptr) { // adding to an empty list m_front = new ListNode(value); else { // adding to the end of an existing list ListNode* current = m_front; while (current->next!= nullptr) { current = current->next; current->next = new ListNode(value); 9.27 8
Kod för get // Returns value in list at given index. // Precondition: 0 <= index < size() int LinkedList::get(int index) { ListNode* current = m_front; for (int i = 0; i < index; i++) { current = current->next; return current->data; Kod för insert // Inserts the given value at the given index. // Precondition: 0 <= index <= size() void LinkedList::insert(int index, int value) { if (index == 0) { // inserting at front of list m_front = new ListNode(value, m_front); else { // inserting in general position in list ListNode* current = m_front; for (int i = 0; i < index - 1; i++) { current = current->next; current->next = new ListNode(value, current->next); 9.28 9.29 Implementera remove // Removes value at given index from list. // Precondition: 0 <= index < size void LinkedList::remove(int index) {... Hur tar vi bort en nod från en given position i listan? Spelar listans innehåll innan operation någon roll? 9.30 Remove på en lista Före borttagning av elementet på index 1: Efter: 9.31 9
Remove i början av en lista Före borttagning av elementet på index 0: Efter: 9.32 Remove då det bara finns ett element Före: Efter: Vi måste ändra front till att lagra nullptr istället för en nod Behöver vi ett specialfall för att hantera detta? 9.33 Kod för remove // Removes value at given index from list. // Precondition: 0 <= index < size() void LinkedList::remove(int index) { ListNode* trash; if (index == 0) { // removing first element trash = m_front; m_front = m_front->next; else { // removing elsewhere in the list ListNode* current = m_front; for (int i = 0; i < index - 1; i++) { current = current->next; trash = current->next; current->next = current->next->next; delete trash; 9.34 10