Informatik I - Übung 11

Pascal Schärli

pascscha@student.ethz.ch

17.05.2019

Was gibts heute?

  • Best-Of Vorlesung
    • Nullpointer
    • Container
    • Iteratoren
    • Linked List
  • Vorbesprechung

Pascal Schärli 17.05.2019

Vorlesung

Pascal Schärli 17.05.2019

Nullpointer

Pascal Schärli 17.05.2019

  • Wie kann man es schaffen, dass ein Pointer auf nichts zeigt?
  • Es gibt in C++ den sogenannten null Pointer, welcher verwendet werden kann um eine Variable effektiv auf nichts zeigen zu lassen.
#include <iostream>

int main() {

    int* p = new int(10);
    std::cout << p << std::endl;

    delete p;
    std::cout << p << std::endl;

    p = nullptr;
    std::cout << p << std::endl;

    return 0;
}

0x55beafb36e70
0x55beafb36e70
0x55beafb36e70
0x55beafb36e70
0x55beafb36e70
0

Syntaktischer Zucker

Pascal Schärli 17.05.2019

#include <iostream>
#include <vector>

int main() {

    std::vector<int>* v = new std::vector<int>;

    (*v).push_back(3);

    std::cout << (*v).size() << std::endl;

    return 0;
}
#include <iostream>
#include <vector>

int main() {

    std::vector<int>* v = new std::vector<int>;

    v->push_back(3);

    std::cout << v->size() << std::endl;

    return 0;
}

Beide Schreibweisen sind äquivalent, aber das Rechte (->) wirkt eleganter

Container

  • In C++ gibt es verschiedene Arten eine Sammlung von Daten zu speichern.
  • Alle diese Arten nennt man Container
  • Ihr kennt bereits ein paar Container: Arrays, Strings, Vektoren
  • Es gibt aber noch andere Container:

Pascal Schärli 17.05.2019

Set

Pascal Schärli 15.03.2019

Ein Set ist ähnlich wie ein Vektor, aber es Speichert seine Elemente in Sortierter Reihenfolge ab.

Eine komplette Liste findet ihr hier unter "Member Functions"

Name Vektor-Pendent Funktionalität
size

Grösse (Länge) vom Set

empty

true falls Set leer ist

push_back

Element sortiert hinzufügen

pop_back

Löscht  Element mit bestimmten Wert

Iterator auf erstes Element im Set

size
empty
insert
remove
begin
end
begin
end

Iterator auf Ende (eins nach dem letzten)

Iteratoren

  • Pointers erlauben es uns zusammenhängende Speicherbereiche zu traversieren
  • Andere Container können jedoch nicht mit solchen Pointers traversiert werden
  • Aus diesem Grund stellen uns Containers Iteratoren zur Verfügung, welche uns diese Funktionalität ermöglichen.

Pascal Schärli 17.05.2019

Iteratoren

Pascal Schärli 17.05.2019

#include <vector>
#include <iostream>

int main(){
    std::vector<int> cont = {8,3,1,4,6,9};

    for (std::vector<int>::iterator it = cont.begin(); it != cont.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}
8 3 1 4 6 9 

Iteratoren

Pascal Schärli 17.05.2019

#include <set>
#include <iostream>

int main(){
    std::set<int> cont = {8,3,1,4,6,9};

    for (std::set<int>::iterator it = cont.begin(); it != cont.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}
1 3 4 6 8 9 

Der selbe Code funktioniert genau gleich bei anderen Containertypen, wie z.B. bei einem set:

Linked List

Pascal Schärli 17.05.2019

  • Letzte Woche haben wir gesehen, wie wir mit new[] Arrays mit einer bestimmten Grösse erstellt werden können.
     
  • Dies ist eher unflexibel, da die Grösse des allozierten Speicherbereichs nicht verändert werden kann.
     
  • Linked Lists erlauben einem dynamisch die grösse einer Liste beliebig zu ändern.

Linked List

Pascal Schärli 17.05.2019

struct llnode {
  int value;
  llnode* next;
};
  • Um dieses Ziel zu erreichen speichern wir unsere Werte in sogenannten "Nodes"
  • Nodes sind Structs, welche einerseits einen Wert (value) beinhalten aber auch einen Pointer (next) welcher auf die nächste Node in der Liste zeigt.
value
next
7
llnode
value
next
7
llnode
value
next
7
llnode
value
next
7
llnode
nullptr

Linked List

Pascal Schärli 17.05.2019

  • Alles diese Nodes organisieren wir in einer Klasse, welche alle Utilities Funktionen (zB push_front usw) implementiert.
  • Sie haben auch noch einen Iterator (const_iterator) implementiert, welchen ihr nicht zwingend verstehen müsst.
class llvec {

  llnode* head;

public:
  // POST: e is appended at the beginning
  //        of the vector.
  void push_front(int e);
  // PRE: begin and end are iterators
  //       pointing to the same vector
  //       and begin is before end.
  // POST: All elements between begin and
  //        end are added to the vector.
  void extend(const_iterator begin,
              const_iterator end);
  // POST: Returns an iterator that points
  //        to the first element.
  const_iterator begin() const;
  // POST: Returns an iterator that points
  //        after the last element.
  const_iterator end() const;
};

Pascal Schärli 17.05.2019

class llvec {

  llnode* head;

public:
  // POST: e is appended at the beginning
  //        of the vector.
  void push_front(int e);
  // PRE: begin and end are iterators
  //       pointing to the same vector
  //       and begin is before end.
  // POST: All elements between begin and
  //        end are added to the vector.
  void extend(const_iterator begin,
              const_iterator end);
  // POST: Returns an iterator that points
  //        to the first element.
  const_iterator begin() const;
  // POST: Returns an iterator that points
  //        after the last element.
  const_iterator end() const;
};

Implementiert die extend Funktion von dieser Linked List:

  1. Findet die letzte Node vom Vektor:
    Benutzt den Iterator begin und geht solange weiter bis ihr das Ende erreicht habt:
    node->next == nullptr

     
  2. Erstellt eine Node für jedes Element zwischen den Iteratoren begin und end.

Pascal Schärli 17.05.2019

void llvec::extend(llvec::const_iterator begin,
                   llvec::const_iterator end) {
  if (begin == end) {
    return;
  }
  llvec::const_iterator it = begin;
  if (this->head == nullptr) {
    this->head = new llnode { *it, nullptr };
    ++it;
  }
  llnode *n = this->head;
  while (n->next != nullptr) {
    n = n->next;
  }
  for (; it != end; ++it) {
    n->next = new llnode { *it, nullptr };
    n = n->next;
  }
}

Pascal Schärli 17.05.2019

  • Implementiert eine swap methode, welche eine Node mit gegebenem Index mit ihrem nachfolger swapt.
     
  • Ihr dürft davon ausgehen, dass alle Inputs so sind, dass es funktioniert.

Pascal Schärli 17.05.2019

void llvec::swap(unsigned int index) {
  if (index == 0) {
    llnode* tmp = this->head->next;
    this->head->next = this->head->next->next;
    tmp->next = this->head;
    this->head = tmp;
  } else {
    llnode* prev = nullptr;
    llnode* curr = this->head;
    
    // Find the element.
    while (index > 0) {
      prev = curr;
      curr = curr->next;
      index--;
    }
    
    // Swap with the next one.
    llnode* tmp = curr->next;
    curr->next = curr->next->next;
    tmp->next = curr;
    prev->next = tmp;
  }
}

Vorbesprechung

Pascal Schärli 17.05.2019

Pascal Schärli 17.05.2019

Schreibt ein Programm, welches Strings lexikografisch vergleichen kann.

apple

banana

web

website

bike

bicycle

<

>

<

Alphabetisch sortiert nach ASCII ordering

Man beachtet nicht nur der erste Buchstabe

Kleinere Wörter sind auch lexikografisch kleiner

Pascal Schärli 17.05.2019

bike

bicycle

first1
last1
first2
last2
first1
first1
first2
first2

c<k

bicycle < bike

bool lexicographic_compare(Iterator first1, Iterator last1,
                           Iterator first2, Iterator last2) {

Pascal Schärli 17.05.2019

  • Die Funktion lexicographic Compare gibt nur aus, ob string1 < string2 gilt.
  • Die Aufgabe verlangt aber auch, dass ihr prüft wenn zwei Strings gleich sind.
  • Wenn weder a < b noch b < a gilt, muss zwangsläufig a == b gelten.
bool lexicographic_compare(Iterator first1, Iterator last1,
                           Iterator first2, Iterator last2) {

Tipp:

Pascal Schärli 17.05.2019

3 5 8 6 5 1 2 -1
1 2 3 5 5 6 8
([1,3]U[5,6]U[8,8])

input:

std::set<int>:

output:

  • In dieser Aufgabe implementieren wir eine eigene Queue für Integer.
  • Eine Queue ist eine Warteschlange, wer zuerst reinkommt kommt auch zuerst wieder raus.
  • Wir implementieren die Funktion mit einer Linked-List

Pascal Schärli 17.05.2019

Pascal Schärli 17.05.2019

struct Node {
    Node(const int _value, Node* _next) : value(_value), next(_next) { }
    const int value;
    Node* next;
};
class Queue {
private:

    // Class invariant: first == nullptr iff last == nullptr
    Node* first; // pointer to first element of queue or nullptr is queue is empty.
    Node* last;  // pointer to last element of queue or nullptr if queue is empty.

    // PRE: -
    // POST: Ensures class invariant.
    void is_valid() const;

public:
    // PRE:  -
    // POST: Valid and empty queue.
    Queue();

    // PRE:  Valid queue.
    // POST: Valid queue with new node having value i added at end of queue.
    void enqueue(int value);

    // PRE:  Valid and non-empty queue.
    // POST: Removes first node from queue frees the unused memory.
    //       Returns content of first node.
    int dequeue();

    // PRE:  Valid queue.
    // POST: Returns true if queue is empty, false otherwise.
    bool is_empty() const;

    // PRE:  Valid queue.
    // POST: Outputs queue content to os in sequence from first to last.
    void print(std::ostream& os) const;
    
    // PRE: Valid queue
    // POST: Outputs queue content to os in reverse order, from last to first.
    void print_reverse(std::ostream& os) const;


};

Pascal Schärli 17.05.2019

value
next
7
Node
#include <iostream>
#include "queue.h"

int main() {
  Queue q;

  q.enqueue(7);
  q.enqueue(5);
  std::cout << q.dequeue() << std::endl;
  q.enqueue(3);
  q.print(std::cout);
  std::cout << std::endl;
  q.print_reverse(std::cout);
  std::cout << std::endl;
  std::cout << q.dequeue() << std::endl;
  std::cout << q.dequeue() << std::endl;
  q.enqueue(2);
  std::cout << q.dequeue() << std::endl;

  return 0;
}
 

Output:

value
next
5
Node
value
next
3
Node
value
next
2
Node
first
last
7
7
[5 3]
[3 5]
7
[5 3]
[3 5]
5
3
2
7
[5 3]
[3 5]
5
3
7
[5 3]
[3 5]
5
7
[5 3]
first
last
first
last
first
last
nullptr
nullptr
nullptr

Viel Spass!

Pascal Schärli 17.05.2019