Structs en gekoppelde datastructuren in C

Leer samenhangende gegevens te groeperen met structs en typedef, verzamelingen waarden een naam te geven met enumeraties en alternatieven op te slaan in unions. Bouw daarna de klassieke gekoppelde datastructuren met knopen die met malloc zijn gereserveerd: gekoppelde lijsten, stapels, wachtrijen, binaire zoekbomen en hashtabellen. Bij elke structuur voeg je items in, zoek en verwijder je ze, doorloop je de structuur, handel je lege situaties en randgevallen af en geef je elke knoop vrij zonder geheugenlekken.

BeginnerLes 6 van 8 in Cursus Programmeren in C: pointers, geheugenbeheer en debuggen

Wat je leert

  1. Definieer structtypen, geef ze korte namen met typedef, benader hun velden via variabelen en pointers en bewaar meerdere records in arrays van structs.
    • Definieer een struct en werk met de velden
      Declareer een structtype, maak en initialiseer structvariabelen, lees en wijzig velden en begrijp hoe kopiëren, vergelijken en de geheugenindeling werken.
    • Geef typen een naam met typedef en geef structs door aan functies
      Maak typealiassen met typedef, geef structs op waarde of via pointers door, gebruik de pijloperator en reserveer geheugen voor structs op de heap.
    • Sla records op in arrays van structs en verwerk ze
      Bewaar meerdere records in een array van structs, doorloop de array, zoek en sorteer records, vergroot de array op de heap en werk met structs die eigenaar zijn van ander geheugen.
  2. Vervang magische getallen door enumeraties en gebruik unions en getagde unions om één waarde uit verschillende soorten in hetzelfde geheugen op te slaan.
    • Gebruik enumeraties voor verzamelingen benoemde waarden
      Definieer enumeraties, bepaal hun waarden, gebruik ze in voorwaarden en switch-instructies en weet wat C er wel en niet aan controleert.
    • Laat alternatieven dezelfde opslag gebruiken met unions en getagde unions
      Declareer unions, weet wat het lezen en schrijven van hun velden doet, combineer een union met een enumeratietag om waarden van verschillende soorten veilig op te slaan en pak kleine vlaggen samen in bitvelden.
  3. Koppel dynamisch gereserveerde knopen tot lijsten, doorloop de lijsten, voeg knopen op willekeurige posities in en verwijder ze, geef alle knopen vrij en bepaal wanneer een lijst beter bij een taak past dan een array.
    • Maak en doorloop een enkelvoudig gekoppelde lijst en geef het geheugen vrij
      Definieer een knooptype, voeg knopen toe aan het begin en einde van een lijst, bezoek elke knoop, schrijf functies die de koppointer wijzigen en geef alle knopen vrij.
    • Zoek knopen, voeg ze op volgorde in en verwijder ze
      Zoek een waarde in een lijst, voeg een knoop op de juiste plaats in een gesorteerde lijst in en koppel een knoop los en geef die vrij. Handel daarbij de eerste knoop, de laatste knoop en ontbrekende waarden af.
    • Vergelijk lijsten met arrays en breid de lijsten uit
      Weeg gekoppelde lijsten af tegen arrays, houd een staartpointer en een aantal bij, maak dubbel gekoppelde lijsten en geef knopen vrij die eigenaar zijn van ander geheugen.
  4. Implementeer stapels volgens het principe laatst erin, eerst eruit en wachtrijen volgens het principe eerst erin, eerst eruit, met arrays en met gekoppelde knopen. Bescherm tegen lege en volle structuren en pas ze toe op echte taken.
    • Maak een stapel en pas die toe
      Beschrijf de stapelbewerkingen, implementeer een stapel met een array en met gekoppelde knopen, handel lege en volle stapels af en gebruik stapels om haakjes te controleren en postfixexpressies te evalueren.
    • Maak een wachtrij met gekoppelde knopen of een ringbuffer
      Beschrijf de wachtrijbewerkingen, implementeer een wachtrij met kop- en staartpointers en met een circulaire array van vaste grootte en onderscheid een lege wachtrij van een volle.
  5. Stel binaire bomen voor met gekoppelde knopen, zoek sleutels in een binaire zoekboom, voeg ze in en verwijder ze, bezoek de knopen in verschillende volgordes en geef een hele boom vrij.
    • Stel een binaire boom voor en doorzoek een binaire zoekboom
      Gebruik de begrippen van binaire bomen, beschrijf de eigenschap van een binaire zoekboom en doorzoek een boom recursief en met een lus.
    • Voeg boomknopen in, doorloop en verwijder ze en geef hun geheugen vrij
      Voeg sleutels in een binaire zoekboom in, bezoek knopen in inorde, preorde en postorde, bereken het aantal knopen en de hoogte, verwijder sleutels en geef elke knoop vrij.
  6. Zet sleutels met een hashfunctie om in bucketindexen, handel botsingen af met ketens van knopen en implementeer en gebruik een hashtabel in C en geef het geheugen ervan vrij.
    • Wijs sleutels aan buckets toe met een hashfunctie
      Leg uit wat een hashfunctie doet, bereken bucketindexen voor stringsleutels, herken botsingen en een ongelijke verdeling en gebruik de bezettingsgraad om te bepalen wanneer je een tabel moet vergroten.
    • Implementeer een hashtabel met ketens in C
      Bouw een hashtabel met een array van buckets die gekoppelde lijsten bevatten: zoek sleutels op, voeg ze in en tel ze, kopieer stringsleutels veilig en geef al het gereserveerde geheugen vrij.

Structs en gekoppelde datastructuren: uitleg

6 hoofdstukken

1

Groepeer samenhangende gegevens met structs

Samenhangende waarden zijn gemakkelijker te verwerken als ze samen één record vormen. Structs geven die waarden namen en typealiassen maken de declaraties gemakkelijker leesbaar. Bij functieaanroepen ontstaat dan een praktische keuze: geef je een kopie door of een adres waarmee je het origineel bereikt? Met arrays kun je hetzelfde doen voor meerdere records. Bij het sorteren, aanpassen van de grootte en kopiëren van pointervelden moet je goed bijhouden welke objecten nog bestaan en welke code eigenaar ervan is.

1.1 Structvelden, kopieën en geheugenindeling

Een struct groepeert samenhangende waarden in één record. De velden hebben namen en kunnen verschillende typen hebben. Een sensormeting heeft bijvoorbeeld zowel een identifier als een temperatuur nodig.

Een record definiëren en initialiseren
struct reading {
    int id;
    double temp_c;
};
struct reading r = {.temp_c = 21.5, .id = 7};
r.temp_c += 0.5;

De definitie introduceert het type struct reading; reading is de structtag. De afsluitende puntkomma hoort na de sluitende accolade. Het definiëren van het type reserveert geen object. De declaratie van r maakt één object en . selecteert de velden ervan. Hier wordt de temperatuur 22,0 °C. Als alleen een tag is gedeclareerd, bevat de typenaam struct: voor reading r; is een afzonderlijke typedef nodig.

Een initialisator zoals {7, 21.5} volgt de declaratievolgorde. Met veldaanduidingen zoals .id = 7 kun je velden in een andere volgorde noemen. Velden die in een initialisator ontbreken, krijgen nulwaarden, dus initialiseert {0} het hele record. Een lokale declaratie struct reading r; zonder initialisator laat de velden onbepaald totdat er waarden aan zijn toegewezen.

Een veld heeft een eigen type en adres. Om een temperatuur met scanf te lezen, gebruik je scanf("%lf", &r.temp_c) en controleer je of de retourwaarde 1 is. De punt bindt vóór &, dus geeft dit het adres van het double-veld door.

De waarden kopiëren en vergelijken
struct reading s = r;
s.temp_c = -4.0;
int same = r.id == s.id && r.temp_c == s.temp_c;

Een toewijzing kopieert alle velden. Als je s.temp_c wijzigt, blijft r.temp_c op 22,0 staan. Vaste arrays binnen structs worden ook gekopieerd: een veld char name[32] hoort bij het record. Dit werkt ook al kunnen afzonderlijke C-arrays niet met = worden toegewezen. Pointervelden vragen extra aandacht, omdat de gekopieerde pointer nog naar hetzelfde object wijst.

C definieert == niet voor hele structs. Vergelijk de velden die voor je taak de gelijkheid bepalen. memcmp vergelijkt ruwe bytes, inclusief opvulling, en kan een verschil melden tussen records waarvan de veldwaarden gelijk zijn.

De geheugenindeling volgen

Velden staan in declaratievolgorde, zonder opvulling vóór het eerste veld. De compiler kan ongebruikte bytes tussen velden en aan het einde toevoegen om aan uitlijningseisen te voldoen. Op een gangbaar 64-bitsplatform met een int van 4 bytes en een uitlijning van 4 bytes neemt struct { char c; int i; char d; } 12 bytes in. De velden beginnen op offsets 0, 4 en 8. Als je de volgorde wijzigt naar int i; char c; char d;, wordt de grootte doorgaans 8 bytes. Gebruik sizeof voor de werkelijke grootte en offsetof uit <stddef.h> voor een offset.

Structs kunnen genest zijn. Met struct point { double x_m, y_m; }; en struct rect { struct point corner; double width_m, height_m; }; bereik je met box.corner.x_m de x-coördinaat van het hoekpunt in meters. Een struct kan geen volledig veld van zijn eigen type bevatten: daarvoor zou een oneindig groot object nodig zijn. Een pointer naar zijn eigen type heeft een vaste grootte en maakt gekoppelde knopen mogelijk.

Belangrijkste punten

  • Gebruik . om een veld van een structobject te selecteren.
  • Een initialisator zet weggelaten velden op nul; een niet-geïnitialiseerde lokale struct heeft onbepaalde velden.
  • Een toewijzing van een hele struct kopieert de velden, inclusief ingebedde arrays.
  • Vergelijk veldwaarden en gebruik sizeof om de opvulling mee te tellen.
  • Een struct kan een pointer naar zijn eigen type bevatten.

1.2 Typenamen, functieaanroepen en structpointers

Een typedef geeft een bestaand type een andere naam. Die verandert hoe je het type schrijft, niet hoe de objecten van dat type zich gedragen. typedef double meters; maakt meters tot een andere naam voor double.

Een struct een naam geven en op waarde doorgeven
typedef struct {
    int id;
    double temp_c;
} reading;

reading make_reading(int id, double t) {
    reading r = {id, t};
    return r;
}
void warm_copy(reading r) {
    r.temp_c += 1.0;
}

Je kunt nu reading r = make_reading(7, 21.5); declareren. Een struct teruggeven geeft zijn waarde terug. De aanroep warm_copy(r) geeft de functie een kopie, zodat de temperatuur van de aanroepende functie 21,5 °C blijft. Een kopie van een klein record is vaak de duidelijkste interface als het object van de aanroepende functie ongewijzigd moet blijven.

Een type dat naar zichzelf verwijst, heeft zijn tag binnen de definitie nodig:

typedef struct node {
    int number;
    struct node *next;
} node;

De alias node komt beschikbaar na de declaratie. Binnen deze accolades benoemt struct node het onvolledige type al en kan het voor een pointer worden gebruikt. De tag en de alias mogen dezelfde schrijfwijze hebben, omdat tags een eigen naamruimte gebruiken.

Het object van de aanroepende functie via een pointer bereiken
void warm(reading *p) {
    p->temp_c += 1.0;
}
reading r = {7, 21.5};
reading *p = &r;
p->temp_c -= 1.5;
warm(&r);

De aftrekking verandert r.temp_c in 20,0; warm verandert de waarde daarna in 21,0. De functie ontvangt een kopie van het adres en beide pointers bereiken nog steeds hetzelfde record. Gebruik -> met een pointer en . met een structobject. p->temp_c betekent (*p).temp_c. De haakjes zijn nodig omdat veldtoegang sterker bindt dan derefereren: *p.temp_c probeert een veld van p te selecteren voordat * wordt toegepast.

Om te lezen zonder de struct te kopiëren, laat je de functie const reading *p ontvangen. Een toewijzing aan p->temp_c via die pointer wordt afgewezen. De pointervariabele kan nog wel veranderen, bijvoorbeeld als die door een array beweegt. reading *const p maakt daarentegen de pointer zelf vast. Bij structs met pointervelden maakt een pointer naar een const-gekwalificeerde struct de afzonderlijk gereserveerde gegevens waarnaar die velden wijzen niet alleen-lezen.

Een heaprecord een eigenaar geven

Neem <stdlib.h> op voor geheugenallocatie en <stdbool.h> voor dit resultaat dat aangeeft of de bewerking is geslaagd:

bool make_heap_reading(reading **out, int id, double t) {
    reading *p = malloc(sizeof *p);
    if (p == NULL) return false;
    *p = (reading){.id = id, .temp_c = t};
    *out = p;
    return true;
}

De functie schrijft pas naar *out nadat de geheugenallocatie is geslaagd. De aanroepende functie kan reading *item = NULL; declareren, make_heap_reading(&item, 8, -2.0) aanroepen, een mislukt resultaat afhandelen en later free(item) aanroepen. sizeof *p bepaalt de grootte van het type waarnaar wordt gewezen, ook voordat p naar een geïnitialiseerd record wijst; deze expressie leest dat record niet.

Een pointer naar een automatische lokale struct wordt ongeldig wanneer de functie terugkeert. Geef de structwaarde of een heapallocatie met een duidelijke eigenaar terug. Door &r terug te geven vanuit een functie waarvan de lokale variabele r is, kun je de levensduur van die variabele niet verlengen.

Belangrijkste punten

  • typedef maakt een alias en een definitie die naar zichzelf verwijst, gebruikt de structtag.
  • Geef een struct op waarde door voor een kopie of geef het adres door om het origineel te wijzigen.
  • p->member is gelijkwaardig aan (*p).member.
  • Een pointer naar een const-gekwalificeerd object voorkomt wijzigingen aan velden via die pointer.
  • Controleer de geheugenallocatie vóór initialisatie en geef elk heaprecord een eigenaar.
  • Een lokale struct op waarde teruggeven is geldig; het adres teruggeven laat een pointer naar een niet meer bestaand object achter.

1.3 Arrays van records en strings in eigendom

Een array van structs bewaart records naast elkaar. Gebruik een index om een record te selecteren en daarna een punt om een veld te selecteren. Daardoor werken doorlopen, zoeken en sorteren grotendeels zoals bij een array van getallen.

Records doorlopen met een expliciet aantal
reading readings[4] = {
    {1, 21.5}, {2, -3.0}, {3, 18.25}, {4, 30.0}
};
size_t n = sizeof readings / sizeof readings[0];
size_t best = 0;
for (size_t i = 1; i < n; ++i) {
    if (readings[i].temp_c > readings[best].temp_c)
        best = i;
}

Hier is reading de alias voor de struct met een identifier en een temperatuur. De beste index loopt op van 0 naar 3, dus heeft het warmste record id 4 en temperatuur 30,0 °C. Beginnen met een bestaand element werkt ook als elke temperatuur negatief is; beginnen met nul als verondersteld maximum kan misgaan. Deze doorloop vereist minstens één element. Een herbruikbare zoekfunctie moet een lege array melden voordat die element 0 leest.

Het aantal met sizeof werkt waar readings een array is. In een functieparameter wordt een arraydeclaratie een pointer, dus geef je n afzonderlijk door. (items + i)->temp_c en items[i].temp_c bereiken hetzelfde veld.

Een vergelijkingsfunctie voor qsort aanleveren

Neem <stdlib.h> op. Voor eindige temperatuurwaarden geeft deze vergelijkingsfunctie een teken terug zonder een klein temperatuurverschil om te zetten naar een geheel getal:

int cmp_temp(const void *a, const void *b) {
    const reading *x = a;
    const reading *y = b;
    return (x->temp_c > y->temp_c)
         - (x->temp_c < y->temp_c);
}
/* With the array above: */
qsort(readings, n, sizeof readings[0], cmp_temp);

De vergelijkingsfunctie ontvangt adressen van elementen. Negatief betekent dat het eerste eerder komt, nul betekent gelijk en positief betekent later. De gesorteerde id-volgorde is 2, 3, 1, 4. Als je het onbewerkte double-verschil teruggeeft, wordt een verschil zoals −0,25 afgekapt tot nul. Geheeltallige sleutels van elkaar aftrekken kan overlopen. Door eerst te vergelijken voorkom je die fouten. qsort behoudt de oorspronkelijke volgorde van gelijke elementen niet.

Een array vergroten zonder het geheugen te verliezen

Houd het aantal gebruikte elementen en de capaciteit afzonderlijk bij. Gebruik voor een gereserveerde array met positieve cap een tijdelijke pointer wanneer je de capaciteit verdubbelt. Neem <stdint.h> op voor SIZE_MAX:

if (cap > SIZE_MAX / 2 / sizeof *items) return false;
size_t new_cap = 2 * cap;
reading *tmp = realloc(items, new_cap * sizeof *items);
if (tmp == NULL) return false;
items = tmp;
cap = new_cap;

Deze instructies horen binnen een functie die bool teruggeeft; items is de arraypointer en cap de positieve capaciteit. Bij een fout blijft het oude geheugen beschikbaar. Als de bewerking slaagt, kan het geheugenblok verplaatst zijn, dus moeten oude pointers naar de elementen worden vervangen. Een begincapaciteit van nul heeft een eigen eerste geheugenallocatie nodig.

Bepalen wat een kopie bezit

Met typedef struct { char *name; int age; } person; kopieert person b = a; het adres in name. Beide records bereiken dezelfde string. Als je beide kopieën als eigenaar zou vrijgeven, zou je hetzelfde geheugen twee keer vrijgeven. Voor een onafhankelijke kopie reserveer je strlen(a.name) + 1 bytes, controleer je of dat is gelukt en gebruik je strcpy uit <string.h>. Een vast veld char name[32] wordt met de struct gekopieerd. strdup reserveert geheugen en kopieert, maar hoort bij POSIX en C23, niet bij de C17-standaardbibliotheek.

Belangrijkste punten

  • Gebruik items[i].member en geef een aantal door aan functies die arrays ontvangen.
  • Begin bij het zoeken naar een maximum met een bestaand element, nadat je op een lege array hebt gecontroleerd.
  • Een vergelijkingsfunctie voor qsort geeft een teken terug; sleutels aftrekken kan afkappen of overlopen.
  • Controleer de berekening van de allocatiegrootte en sla het resultaat van realloc op in een tijdelijke pointer.
  • Een gekopieerd pointerveld verwijst nog steeds naar het oorspronkelijk gereserveerde geheugen.
2

Geef keuzewaarden een naam met enumeraties en deel opslag met unions

Een programma heeft vaak een keuze met een leesbare naam nodig, of een waarde die verschillende vormen kan aannemen. Enumeraties geven de keuzes een naam; unions delen opslag tussen alternatieven. Door ze te combineren beschrijft elke opgeslagen waarde zijn eigen soort. Het is belangrijk om de naam en de gegevensinhoud consistent te houden en te beseffen dat C de invoervalidatie en keuzes voor de representatie aan het programma overlaat.

2.1 Enumeratiewaarden en gecontroleerde keuzes

Een enumeratie geeft leesbare namen aan geheeltallige keuzewaarden. Namen zoals HIGH maken het doel van een voorwaarde zichtbaar zonder dat de lezer hoeft te onthouden wat het getal 10 voorstelt.

De nummering volgen
enum weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
enum level { LOW = 1, MEDIUM, HIGH = 10, CRITICAL };
enum level alert = HIGH;

Een weggelaten eerste waarde is nul. Bij later weggelaten waarden wordt de vorige waarde telkens met één verhoogd. Zo is MON 0 en SUN 6; de niveaus zijn 1, 2, 10 en 11. Een expliciete waarde laat het tellen op dat punt opnieuw beginnen. Verschillende constanten kunnen een waarde delen, maar identieke waarden kunnen niet als afzonderlijke case-labels in dezelfde switch voorkomen.

Een alias kan een declaratie verkorten: typedef enum { RED, YELLOW, GREEN } light; maakt light signal = RED; mogelijk. Enumeratieconstanten delen de gewone naamruimte voor identifiers binnen hun geldigheidsgebied. Twee verschillende enumeraties kunnen niet allebei RED declareren in hetzelfde geldigheidsgebied. Voorvoegsels zoals in LIGHT_RED en PAINT_RED houden de namen afzonderlijk.

Elke keuze afhandelen
const char *level_name(enum level value) {
    switch (value) {
        case LOW: return "low";
        case MEDIUM: return "medium";
        case HIGH: return "high";
        case CRITICAL: return "critical";
    }
    return "unknown";
}

Elke return beëindigt zijn geval. Als een geval in plaats daarvan een actie uitvoert en daarna moet stoppen, geef je het een break; als de uitvoering het volgende label bereikt, gaat die daar verder. Bij GCC schakelt -Wall waarschuwingen in voor ontbrekende enumeratiegevallen als er geen default is; Clang meldt zulke weglatingen ook standaard. Een waarschuwing kan een ontbrekend geval aan het licht brengen als je later een constante toevoegt.

Invoer valideren als een verzameling keuzes

Het enumeratietype dwingt niet af dat een waarde tot de benoemde verzameling behoort. C17 staat toewijzing vanuit een geheel getal toe, dus kan enum level x = 42; compileren. Een controle of het getal van 1 tot en met 11 loopt, laat nog steeds onbenoemde waarden zoals 6 toe. Controleer de werkelijk toegestane waarden:

#include <stdbool.h>
bool valid_level(int value) {
    return value == LOW || value == MEDIUM
        || value == HIGH || value == CRITICAL;
}

Pas na die controle mag invoer een niveau worden. Voor de opeenvolgende weekdagen volstaat een bereikcontrole van MON tot en met SUN. In een gewone C17-enumeratie hebben de enumeratieconstanten het type int. C23 ondersteunt daarnaast een opgegeven onderliggend type, zoals enum color : unsigned char; die syntaxis hoort niet bij C17.

Namen afdrukken en aantallen gebruiken

printf("%d\n", SUN) drukt “6” af, niet “SUN”. Voor opeenvolgende waarden die bij nul beginnen, geeft een namenarray de tekst: const char *day_names[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};. Na validatie van een index is day_names[SUN] gelijk aan “Sun”. Om zo’n array met een niveauwaarde zoals HIGH te indexeren, is een andere koppeling tussen waarden en namen nodig omdat de niveauwaarden niet opeenvolgend zijn.

Als je DAY_COUNT direct na SUN toevoegt, krijgt die de waarde 7, bruikbaar als lusgrens of arraygrootte. De constante telt dagen, maar is zelf geen geldige dag. Enumeratienamen volgen de regels van C voor geldigheidsgebieden en kunnen door debuggers worden getoond; een #define voert daarentegen tekstvervanging door de preprocessor uit.

Belangrijkste punten

  • De nummering van een enumeratie begint bij nul tenzij een waarde is opgegeven, en loopt na elke expliciete waarde verder.
  • Een enumeratievariabele kan een onbenoemde waarde bevatten, dus valideer externe invoer.
  • Gebruik break of return om een geval te beëindigen als doorvallen ongewenst is.
  • Een array die indexen aan namen koppelt, heeft gevalideerde, opeenvolgende indexen nodig die bij nul beginnen.

2.2 Unions met een bijpassende tag

Een union geeft meerdere velden dezelfde opslag. Die past bij een waarde die een van verschillende soorten kan zijn. Een struct geeft elk veld een eigen opslagplaats en past bij een record dat al die waarden samen nodig heeft.

Eén alternatief opslaan
union value {
    int i;
    double d;
    char c;
};
union value v = {.d = 2.5};

Alle velden beginnen op hetzelfde adres. De union moet ruimte bieden aan het grootste veld en aan de uitlijningseisen voldoen; met een double van 8 bytes neemt deze union op gangbare platforms 8 bytes in. Een gewone initialisator {42} initialiseert het eerste veld, i; .d = 2.5 kiest expliciet d.

Als je na de initialisator v.i = 42 schrijft, vervang je het opgeslagen alternatief. Als je daarna v.d leest, wordt 42 niet omgezet naar een double: de opgeslagen bytes worden met een andere representatie geïnterpreteerd. Die representatie kan ongeldig zijn. Lees het veld dat hoort bij de waarde die je hebt opgeslagen.

Vastleggen welk veld geldig is

Combineer de union met een enumeratie. De enumeratie is de tag en de union is de gegevensinhoud:

enum kind { K_INT, K_DOUBLE, K_CHAR };
struct tagged {
    enum kind kind;
    union value as;
};
struct tagged make_double(double d) {
    return (struct tagged){.kind = K_DOUBLE, .as = {.d = d}};
}

Een hulpfunctie stelt beide onderdelen samen in. Code die schrijft, houdt de twee velden op elkaar afgestemd. Als code t.as.i schrijft maar t.kind op K_DOUBLE laat staan, kiest code die later leest de verkeerde representatie.

#include <stdio.h>
void print_cell(struct tagged t) {
    switch (t.kind) {
        case K_INT: printf("%d", t.as.i); break;
        case K_DOUBLE: printf("%.1f", t.as.d); break;
        case K_CHAR: printf("%c", t.as.c); break;
    }
}

Als je deze functie aanroept voor cellen met 42, 2,5 en 'x', en tussen de aanroepen spaties afdrukt, levert dat “42 2.5 x” op. Elk geval kiest zowel het bijpassende veld als de bijpassende opmaak. Een token van een rekenprogramma kan op dezelfde manier een getal of een operator bevatten.

Op gangbare 64-bitsplatforms nemen een enumeratie van 4 bytes, opvulling en deze union van 8 bytes samen 16 bytes in voor struct tagged. Een struct die de enumeratie plus alle drie de alternatieven afzonderlijk opslaat, neemt doorgaans 24 bytes in. Dit zijn voorbeelden van geheugenindeling; vraag de werkelijke grootten van de typen op met sizeof.

Kleine vlaggen in bitvelden samenpakken
struct flags {
    unsigned int ready : 1;
    unsigned int mode : 3;
};

De breedte volgt op de dubbele punt. Een veld zonder teken van 1 bit bevat 0 of 1; een veld van 3 bits bevat 0 tot en met 7. Als je 9 in mode opslaat, blijven de drie laagste bits behouden en wordt 1 opgeslagen. Hierbij gaat informatie verloren, dus valideer een modus vóór opslag als grotere waarden op een fout wijzen.

Een bitveld heeft geen afzonderlijk adresseerbare locatie, dus is &flags.mode ongeldig voor een variabele met de naam flags. De volgorde en opslagindeling hangen af van de implementatie. Gebruik een bitvelddeclaratie niet als overdraagbare garantie voor een exacte bestands- of apparaatindeling. Een opgegeven breedte bepaalt het waardenbereik van het veld, niet de indeling van elke byte eromheen.

Belangrijkste punten

  • Unionvelden overlappen, terwijl structvelden afzonderlijke opslag hebben.
  • Een tag moet overeenkomen met het unionveld dat de opgeslagen waarde bevat.
  • Een switch selecteert het bijpassende veld en uitvoerformaat.
  • De breedte van bitvelden zonder teken beperkt de opgeslagen waarden; overtollige hoge bits gaan verloren.
  • De indeling van bitvelden wordt door de implementatie bepaald en velden hebben geen eigen adres.
3

Maak en onderhoud gekoppelde lijsten

Gekoppelde structuren groeien door afzonderlijk gereserveerde geheugenblokken te verbinden. Een lijst maakt die verbindingen zichtbaar: één pointer begint de keten en elke knoop leidt naar de volgende. Het werk zit vooral in het wijzigen van verwijzingen, met name bij de eerste of laatste knoop. Door de juiste inkomende verwijzing bij te houden kan dezelfde bewerking meerdere posities afhandelen, terwijl staartpointers en tweerichtingsverbindingen veranderen voor welke bewerkingen je moet zoeken.

3.1 Maak een lijst en houd elke knoop bereikbaar

Een enkelvoudig gekoppelde lijst slaat elk item op in een afzonderlijk gereserveerde knoop. Elke knoop bevat zijn waarde en het adres van de volgende knoop. De koppointer wijst naar de eerste knoop; NULL markeert zowel een lege lijst als het einde van een keten.

De verwijzing definiëren en vooraan invoegen
#include <stdbool.h>
#include <stdlib.h>
typedef struct node {
    int number;
    struct node *next;
} node;

bool prepend(node **head, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    n->number = value;
    n->next = *head;
    *head = n;
    return true;
}

Begin met node *list = NULL;. Roep prepend(&list, value) aan en handel een mislukt resultaat af. De functie ontvangt het adres van de koppointer van de aanroepende functie. *head duidt daardoor de pointer aan die moet veranderen. Als de functie alleen node *head zou ontvangen en aan die lokale pointer zou toewijzen, zou de koppointer van de aanroepende functie ongewijzigd blijven.

De volgorde van de laatste toewijzingen behoudt de oude lijst. Verbind eerst de nieuwe knoop met de oude eerste knoop en vervang daarna de koppointer. Als je die instructies omkeert, wordt n->next op n zelf gezet en gaat de oude keten verloren. Als 1, 2 en 3 binnenkomen, levert herhaald vooraan invoegen eerst 1 op, dan 2 → 1 en dan 3 → 2 → 1 → NULL. Vooraan invoegen keert de volgorde van binnenkomst om.

Doorlopen zonder de eigenaar te verplaatsen
#include <stdio.h>
void print_list(const node *head) {
    for (const node *p = head; p != NULL; p = p->next)
        printf("%d ", p->number);
}

De doorlooppointer p beweegt terwijl head beschikbaar blijft. Het voorbeeld drukt “3 2 1 ” af met een spatie aan het einde. Bij een lege lijst wordt de lusinhoud nul keer uitgevoerd. Er is geen arrayindex: elke stap leest één verwijzing om de volgende knoop te vinden. De gereserveerde geheugenblokken kunnen verspreid in het geheugen liggen en bij het invoegen van een knoop worden bestaande knopen niet verplaatst.

Achteraan toevoegen
bool append(node **head, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    *n = (node){.number = value, .next = NULL};
    node **link = head;
    while (*link != NULL) link = &(*link)->next;
    *link = n;
    return true;
}

link begint bij de koppointer van de aanroepende functie. Daarna gaat de pointer naar het adres van elk next-veld totdat die de afsluitende nulverwijzing aanduidt. Daar toewijzen werkt ook voor een lege lijst. Zonder bewaarde staartpointer moet je bij elke toevoeging achteraan eerst de bestaande keten tot het einde volgen. Een functie kan ook een nieuwe koppointer teruggeven, mits de aanroepende functie die opslaat en een mislukte geheugenallocatie afhandelt zonder de oude lijst te verliezen.

Knopen in een veilige volgorde vrijgeven
void clear_list(node **head) {
    while (*head != NULL) {
        node *old = *head;
        *head = old->next;
        free(old);
    }
}

Lees de opvolger terwijl old nog bestaat, verplaats de koppointer en geef daarna de oude knoop vrij. Na de lus is de koppointer van de aanroepende functie NULL. Als je old->next leest nadat old is vrijgegeven, is dat gebruik na vrijgave, ook als het geheugen ongewijzigd lijkt. Een geheugencontroleprogramma kan helpen ongeldige leesbewerkingen en vergeten geheugenallocaties aan het licht te brengen; ook als een latere bewerking mislukt, moet het opruimen worden uitgevoerd.

Belangrijkste punten

  • Een lege lijst heeft een nulkoppointer en de laatste knoop heeft een nulverwijzing als volgende verwijzing.
  • Reserveer geheugen voor een nieuwe knoop en initialiseer die voordat je de bestaande keten wijzigt.
  • Geef functies die de koppointer wijzigen het adres van de koppointer of sla de teruggegeven koppointer op.
  • Doorloop de lijst met een afzonderlijke pointer zodat de lijst bereikbaar blijft.
  • Zonder staartpointer moet je voor een toevoeging achteraan naar het einde lopen.
  • Lees de opvolger voordat je een knoop vrijgeeft.

3.2 Wijzig de inkomende verwijzing bij invoegen en verwijderen

Bij het doorzoeken van een lijst volg je steeds één verwijzing. Voor invoegen en verwijderen is meer nodig: het adres van de verwijzing die moet veranderen. Die verwijzing kan de koppointer zijn of een next-veld in een eerdere knoop.

Zoeken op waarde

Met het type node met de velden number en next:

node *find(node *head, int value) {
    for (node *p = head; p != NULL; p = p->next)
        if (p->number == value) return p;
    return NULL;
}

Bij een overeenkomst wordt het adres van de gevonden knoop teruggegeven. Bij een ontbrekende waarde wordt NULL teruggegeven. Om de 1.000e knoop te bereiken, moet je vanaf de koppointer 999 verwijzingen volgen, ook als je de positie van tevoren weet.

Een pointer bijhouden naar de verwijzing die verandert

Een node ** kan naar de koppointer of naar het next-veld van een knoop wijzen. Noem die link: *link is de knoop die momenteel via die verwijzing wordt bereikt. Als je verdergaat met link = &(*link)->next, verandert welke verwijzing je bekijkt; de lijst verandert niet.

bool insert_sorted(node **head, int value) {
    node **link = head;
    while (*link != NULL && (*link)->number < value)
        link = &(*link)->next;
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    n->number = value;
    n->next = *link;
    *link = n;
    return true;
}

Neem <stdbool.h> en <stdlib.h> op. De invoerlijst moet al oplopend zijn gesorteerd. Stop vóór de eerste waarde die niet kleiner is dan de nieuwe waarde, zodat dubbele waarden vóór gelijke waarden komen. De linkerkant van && controleert op NULL voordat de rechterkant de knoop derefereert. Als je die controles omwisselt, wordt aan het einde een nulpointer gederefereerd.

Als je 5, 2, 8 en 4 invoegt, is de uiteindelijke lijst 2 → 4 → 5 → 8. Bij het invoegen van 4 in 2 → 5 → 8 gaat link van de koppointer naar 2.next. De nieuwe knoop wijst naar 5 en 2.next wordt omgeleid naar de nieuwe knoop. Als het nieuwe item het kleinste is, duidt link nog steeds de koppointer aan. Aan het einde duidt die de afsluitende nulverwijzing aan.

Loskoppelen vóór vrijgeven
bool remove_first(node **head, int value) {
    node **link = head;
    while (*link != NULL && (*link)->number != value)
        link = &(*link)->next;
    if (*link == NULL) return false;
    node *old = *link;
    *link = old->next;
    free(old);
    return true;
}

De lus vindt de inkomende verwijzing. Door het doel daarvan als old te bewaren, blijft de knoop beschikbaar terwijl *link wordt gewijzigd zodat die naar de opvolger wijst. Pas daarna wordt de verwijderde knoop vrijgegeven. Als je 5 uit 2 → 4 → 5 → 8 verwijdert, krijg je 2 → 4 → 8. Verwijder je daarna 2, dan verandert de koppointer en blijft 4 → 8 over. Als de waarde ontbreekt, geeft de functie onwaar terug zonder de lijst te wijzigen.

Een versie met twee pointers houdt prev en cur bij. Als prev een nulpointer is, is de gevonden knoop de eerste en moet de koppointer veranderen; werk anders prev->next bij. Als je het geval van de eerste knoop vergeet, blijft een pointer naar vrijgegeven geheugen achter. De versie met het adres van de verwijzing handelt beide gevallen af met dezelfde toewijzing.

Controleer lege invoer, een lijst met één knoop, een overeenkomst bij de eerste en laatste knoop en een ontbrekende waarde. Bepaal bij elke wijziging de inkomende verwijzing, de betrokken knoop en zijn opvolger voordat je een toewijzing schrijft. Geen enkele overgebleven knoop mag onbereikbaar worden en geen enkele verwijzing mag naar een vrijgegeven knoop wijzen.

Belangrijkste punten

  • Zoeken geeft een passende knoop of NULL terug nadat het einde is bereikt.
  • Een pointer naar een verwijzing kan de koppointer of een volgend veld aanduiden.
  • Bij gesorteerd invoegen wordt de nieuwe knoop aan zijn opvolger gekoppeld voordat de inkomende verwijzing wordt omgeleid.
  • Bij verwijderen wordt de inkomende verwijzing omgeleid voordat de verwijderde knoop wordt vrijgegeven.
  • Test de gevallen met de eerste knoop, de laatste knoop, een lege lijst, één knoop en een ontbrekende waarde.

3.3 Kies een array, een staartpointer of verwijzingen in twee richtingen

De vraag is welke bewerkingen je programma uitvoert. Arrays en lijsten kunnen dezelfde waarden bevatten, maar bereiken en verplaatsen die waarden op verschillende manieren.

De bewerkingen vergelijken
TaakArrayEnkelvoudig gekoppelde lijst
Positie 500 bereikenGebruik a[500] directVolg verwijzingen vanaf de koppointer
Invoegen na een bekende positieVerschuif latere elementenWijzig na geheugenallocatie twee verwijzingen
Alle waarden doorlopenLees aaneengesloten elementenVolg mogelijk verspreide knopen
Een bekende knoop verwijderenVerschuif latere elementenZoek ook de inkomende verwijzing

Bij een lijst kost het vinden van een positie nog steeds werk. Invoegen na een knoop waarvan je al een pointer hebt, gaat direct, maar voor verwijderen heb je ook de voorganger of de verwijzing naar de knoop nodig. Met alleen het adres van de knoop moet je bij een enkelvoudig gekoppelde lijst doorgaans vanaf de koppointer lopen om die inkomende verwijzing te vinden.

Ook opslag is van belang. Op een typisch 64-bitsplatform neemt een knoop met een int van 4 bytes en een pointer van 8 bytes na opvulling 16 bytes in. Voor 1.000 gehele getallen gebruiken knopen 16.000 bytes plus de administratie van de geheugenallocatie; een aaneengesloten array van gehele getallen gebruikt 4.000 bytes. De array doorlopen is meestal sneller omdat waarden die dicht bij elkaar liggen samen worden opgeslagen.

Lijstgegevens bij elkaar houden
typedef struct {
    node *head;
    node *tail;
    size_t count;
} list_t;

Initialiseer een omhullende struct met list_t list = {0};. De regels voor een lege lijst zijn head == NULL, tail == NULL en count == 0. Elke geslaagde invoeging of verwijdering moet zowel het aantal als de verwijzingen bijwerken. Een mislukte geheugenallocatie verandert geen van beide.

Met een staartpointer zijn na een gecontroleerde geheugenallocatie deze toewijzingen nodig om achteraan toe te voegen:

n->next = NULL;
if (list.tail == NULL) list.head = n;
else list.tail->next = n;
list.tail = n;
++list.count;

Hier is n een geïnitialiseerde knoop. Voor de eerste knoop worden kop- en staartpointer gelijk. Bij het verwijderen van de laatste knoop moet de staartpointer terug worden verplaatst; bij een enkelvoudig gekoppelde lijst is daarvoor de voorganger nodig. Als je de enige knoop verwijdert, zet je beide uiteinden op NULL en het aantal op nul.

In beide richtingen verbinden
typedef struct dnode {
    int value;
    struct dnode *prev, *next;
} dnode;
typedef struct {
    dnode *head, *tail;
} dlist;

void unlink_node(dlist *list, dnode *d) {
    if (d->prev != NULL) d->prev->next = d->next;
    else list->head = d->next;
    if (d->next != NULL) d->next->prev = d->prev;
    else list->tail = d->prev;
    free(d);
}

Deze functie ontvangt een bestaande knoop die bij de lijst hoort. Beide buren worden bijgewerkt voordat de knoop wordt vrijgegeven. Aan de uiteinden verandert de functie de kop- of staartpointer in plaats van een ontbrekende buur. Een dubbel gekoppelde lijst kan achteruit worden doorlopen en een bekende knoop verwijderen zonder naar zijn voorganger te zoeken, maar vereist een extra pointer per knoop en meer te onderhouden verwijzingen.

Gegevens in eigendom vóór hun knoop vrijgeven

Een word_node kan eigenaar zijn van zowel een gereserveerde knoop als een via word bereikbare gereserveerde string. Bewaar next, roep free(n->word) aan, daarna free(n), en ga naar de bewaarde opvolger. Als je alleen de knoop vrijgeeft, ontstaat een geheugenlek voor de string. n->word lezen nadat de knoop is vrijgegeven, is ongeldig. Eigenaarschap beschrijft welke code elk gereserveerd geheugenblok moet vrijgeven, ook als meerdere pointers het kunnen bereiken.

Belangrijkste punten

  • Arrays bieden directe toegang via een index en compacte aaneengesloten opslag.
  • Bij lijsten kun je verwijzingen plaatselijk wijzigen zodra de vereiste positie of inkomende verwijzing bekend is.
  • Een staartpointer maakt direct achteraan toevoegen mogelijk, maar elke bewerking moet beide uiteinden bijhouden.
  • Dubbel gekoppelde knopen slaan de voorganger en opvolger op.
  • Geef gegevens in eigendom vrij voordat je de knoop vrijgeeft die het adres ervan bevat.
  • Een opgeslagen aantal moet bij elke geslaagde invoeging en verwijdering veranderen.
4

Implementeer stapels en wachtrijen

Stapels en wachtrijen beschrijven de volgorde waarin werk wordt afgehandeld. Een stapel houdt het nieuwste onafgemaakte item gereed, wat past bij nesting en het evalueren van expressies. Een wachtrij houdt het item dat het langst wacht gereed, wat past bij verwerking op volgorde van binnenkomst. Beide kunnen arrays of gekoppelde knopen gebruiken. Hun implementaties moeten lege en volle toestanden expliciet maken en elk aantal, uiteinde en elke index na een wijziging consistent houden.

4.1 Stapelbewerkingen en genest werk

Een stapel verwijdert het laatst toegevoegde item als eerste: laatst erin, eerst eruit (LIFO). De bewerkingen definiëren een abstract datatype, ongeacht of een array of gekoppelde knopen de waarden bevatten. push voegt bovenaan toe, pop verwijdert het bovenste item en peek leest het bovenste item zonder het te verwijderen.

Een array en een aantal gebruiken
#include <stdbool.h>
#define STACK_CAP 100
typedef struct {
    int items[STACK_CAP];
    int top;
} stack;

bool push(stack *s, int value) {
    if (s->top == STACK_CAP) return false;
    s->items[s->top++] = value;
    return true;
}
bool pop(stack *s, int *out) {
    if (s->top == 0) return false;
    *out = s->items[--s->top];
    return true;
}

Initialiseer met stack s = {0};. Hier is top het aantal opgeslagen items, dus is de hoogste bezette index top - 1. Een geslaagde push schrijft naar de volgende vrije plaats en verhoogt het aantal. Een geslaagde pop verlaagt eerst het aantal en leest daarna die index. De aanroepende functie levert een geldige out-pointer en controleert het resultaat voordat zij de waarde gebruikt.

Als je 4, 7 en 1 op de stapel zet en vervolgens twee keer verwijdert, krijg je 1 en 7 terug en blijft 4 over. Zonder capaciteitscontrole schrijft een push op een volle stapel naar items[STACK_CAP], voorbij het einde. Zonder controle op een lege stapel daalt de top met teken van 0 naar −1 en leest pop items[-1]. Beide toegangen zijn ongedefinieerd gedrag.

De eerste knoop van een lijst als bovenkant gebruiken

Een gekoppelde stapel voegt bij een push een nieuwe knoop vooraan in, nadat de geheugenallocatie is gecontroleerd. Pop verwijdert de eerste knoop:

bool pop_linked(node **top, int *out) {
    if (*top == NULL) return false;
    node *old = *top;
    *out = old->number;
    *top = old->next;
    free(old);
    return true;
}

node heeft een geheel getal number en een next-pointer. Een gekoppelde stapel heeft geen vaste arraycapaciteit, maar een push kan mislukken als de geheugenallocatie mislukt. Er zijn nog steeds controles op een lege stapel nodig en knopen die aan het einde overblijven, moeten worden vrijgegeven.

Geneste haakjes controleren

Lees de invoer van links naar rechts. Zet elk openingshaakje, elke openingsblokhaak of openingsaccolade op de stapel. Haal bij elk sluitend teken een teken van de stapel en controleer of de soorten overeenkomen. Een sluitend teken zonder openingsteken gaat direct mis. Controleer ten slotte of de stapel leeg is: overgebleven openingstekens betekenen ook dat de tekens niet kloppen.

De tekst “( [ ] { } )” is in balans. Bij “( ]” past het sluitende teken niet bij het verwijderde openingsteken. Bij “( ( )” blijft na het lezen één openingsteken over. Als je alleen openings- en sluitingstekens telt, mis je een onjuiste nesting zoals “( [ ) ]”.

Postfixexpressies evalueren

Zet getallen op de stapel zodra ze binnenkomen. Haal bij een binaire operator eerst de rechteroperand en daarna de linkeroperand van de stapel, bereken left operator right en zet het resultaat op de stapel. Voor “3 4 + 2 *” combineert + 3 en 4 tot 7 en combineert * daarna 7 en 2 tot 14. Voor “9 4 -” haal je 4 als rechteroperand en 9 als linkeroperand van de stapel, wat 5 oplevert. Omkeren levert −5 op. Wijs onvoldoende operanden af en vereis één eindresultaat; bij deling moet de rechteroperand bovendien ongelijk aan nul zijn.

Functieaanroepen hebben levensduren in de vorm van een stapel: een aangeroepen functie keert terug voordat de aanroepende functie verdergaat. Elke actieve aanroep heeft een stackframe nodig, dus kan zeer diepe recursie de aanroepstapel uitputten, ook als heapallocaties slagen.

Belangrijkste punten

  • LIFO betekent dat het nieuwste item als eerste wordt verwijderd.
  • In de arrayimplementatie telt top de items; controleer vóór toegang of de stapel leeg of vol is.
  • Een gekoppelde stapel voegt toe en verwijdert bij de eerste knoop van de lijst.
  • Haakjescontrole vereist zowel overeenkomende soorten als een lege stapel aan het einde.
  • Bij aftrekken of delen in postfixnotatie is de eerste verwijderde waarde de rechteroperand.

4.2 Wachtrijuiteinden en circulaire opslag

Een wachtrij verwijdert het vroegst toegevoegde item als eerste: eerst erin, eerst eruit (FIFO). Toevoegen gebeurt achteraan; verwijderen gebeurt vooraan. Nadat je A, B en C hebt toegevoegd, geven de volgende twee verwijderingen A en B terug. Dit past bij werk dat op volgorde van binnenkomst moet worden afgehandeld.

Beide uiteinden van een gekoppelde wachtrij bijhouden

Met knopen die de velden number en next bevatten en met <stdbool.h> en <stdlib.h> opgenomen:

typedef struct { node *head, *tail; } queue;

bool enqueue(queue *q, int value) {
    node *n = malloc(sizeof *n);
    if (n == NULL) return false;
    *n = (node){.number = value, .next = NULL};
    if (q->tail == NULL) q->head = n;
    else q->tail->next = n;
    q->tail = n;
    return true;
}
bool dequeue(queue *q, int *out) {
    if (q->head == NULL) return false;
    node *old = q->head;
    *out = old->number;
    q->head = old->next;
    if (q->head == NULL) q->tail = NULL;
    free(old);
    return true;
}

Initialiseer met queue q = {0};. Toevoegen stelt de verwijzing van de oude laatste knoop in voordat de staartpointer wordt verplaatst. Bij de eerste knoop wordt ook de koppointer ingesteld. Verwijderen verplaatst de koppointer naar voren. Als daarmee de enige knoop wordt verwijderd, moet ook de staartpointer NULL worden. Anders schrijft de volgende toevoeging via een staartpointer naar een niet meer bestaand object. Voor geen van beide bewerkingen hoeft de lijst te worden doorlopen.

Een vaste array als ring hergebruiken

Bij een gewone arraywachtrij die altijd van index 0 verwijdert, moeten de overgebleven waarden worden verschoven. Een ringbuffer houdt in plaats daarvan een index voor de voorkant en een aantal items bij:

#define CAP 4
typedef struct {
    char items[CAP];
    unsigned head, count;
} ring;

bool ring_put(ring *q, char value) {
    if (q->count == CAP) return false;
    q->items[(q->head + q->count) % CAP] = value;
    ++q->count;
    return true;
}
bool ring_get(ring *q, char *out) {
    if (q->count == 0) return false;
    *out = q->items[q->head];
    q->head = (q->head + 1) % CAP;
    --q->count;
    return true;
}

Initialiseer een ring met {0}. head duidt altijd het voorste item aan als de wachtrij niet leeg is. De volgende invoegindex is de voorkant plus het aantal items, met terugloop door % CAP. Lees het voorste item voordat je head verplaatst. Het aantal onderscheidt leeg van vol zonder een plaats op te offeren.

De terugloop volgen

Voeg A, B en C toe op plaatsen 0, 1 en 2. Verwijder A: de kopindex wordt 1 en het aantal wordt 2. D komt op plaats 3; E komt op plaats 0. De fysieke array bevat nu E, B, C, D, maar de logische verwijdervolgorde is B, C, D, E. De ring is vol omdat het aantal 4 is, ook al staat de voorkant op index 1.

Een ander ontwerp houdt de kopindex en de index van de volgende vrije plaats achteraan bij, zonder een aantal. In dat ontwerp kunnen gelijke indexen leeg of vol betekenen, dus een veelgebruikte werkwijze laat één plaats ongebruikt. Leeg is head == tail; vol is (tail + 1) % CAP == head. Meng die werkwijze niet met de capaciteitsregels van de versie met een aantal. Wachtrijen ondersteunen ook het niveau voor niveau bezoeken van bomen door knopen te bewaren die op hun beurt wachten.

Belangrijkste punten

  • FIFO verwijdert de waarde die het langst in de wachtrij staat.
  • Een gekoppelde wachtrij voegt achteraan toe en verwijdert vooraan.
  • Bij het verwijderen van de enige knoop moeten zowel kop- als staartpointer worden teruggezet.
  • Een circulaire wachtrij laat indexen teruglopen en bewaart de logische volgorde onafhankelijk van de fysieke volgorde.
  • Gebruik een aantal of laat één plaats ongebruikt om leeg en vol te onderscheiden.
  • Lees het voorste item voordat je de kopindex verplaatst.
5

Maak en doorloop binaire bomen

Bomen vervangen één keten door vertakkingen. Een zoekboom gebruikt de volgorde van sleutels om een tak te kiezen, maar de vorm bepaalt hoeveel knopen een zoekopdracht bereikt. Hetzelfde idee van deelbomen stuurt het invoegen, doorlopen, tellen en verwijderen. Elke bewerking heeft een leeg geval nodig, een regel voor de huidige knoop en een duidelijke keuze welke ouderpointer een gewijzigde wortel van een deelboom moet ontvangen.

5.1 Doorzoek een boom met de ordeningsregel

Een binaire boom geeft elke knoop hoogstens twee kinderen, links en rechts genoemd. De wortel is de bovenste knoop. Een blad heeft geen kinderen en een knoop vormt samen met al zijn nakomelingen een deelboom. Een ontbrekend kind en een lege boom worden voorgesteld door NULL.

De vertakkingen voorstellen
typedef struct tnode {
    int key;
    struct tnode *left;
    struct tnode *right;
} tnode;

Elke kindpointer leidt naar de wortel van een andere boom. Dezelfde definitie beschrijft daardoor een blad, een grote vertakte boom of een lange keten. Hoogte telt verbindingen op het langste pad van wortel naar blad: een wortel alleen heeft hoogte 0. Gebruik −1 voor een lege boom, zodat de twee lege deelbomen van een blad vanzelf tot hoogte 0 leiden. Als je in plaats daarvan niveaus telt, krijg je één meer.

De regel op hele deelbomen toepassen

Een binaire zoekboom ordent de sleutels. Voor elke knoop zijn alle sleutels in zijn linkerdeelboom kleiner en alle sleutels in zijn rechterdeelboom groter. Sla elke sleutel één keer op. Deze voorwaarde geldt ook voor nakomelingen verderop. Een wortel 50 met linkerkind 30 en een rechterkind 60 onder die 30 is ongeldig: 60 staat nog steeds in de linkerdeelboom van 50.

Voeg 50, 30, 70, 20, 40, 60 en 80 in een lege binaire zoekboom in. De wortel is 50; 30 en 70 zijn de kinderen. De kinderen van 30 zijn 20 en 40 en die van 70 zijn 60 en 80. Elke keuze voor een tak behoudt de grenzen die van de voorouders zijn geërfd.

Bij elke stap één tak kiezen
#include <stddef.h>
tnode *search(tnode *t, int key) {
    while (t != NULL) {
        if (key == t->key) return t;
        if (key < t->key) t = t->left;
        else t = t->right;
    }
    return NULL;
}

Zoeken naar 60 in het voorbeeld bezoekt 50, gaat naar rechts naar 70 en dan naar links naar 60: drie knopen. Zoeken naar 65 volgt hetzelfde pad en probeert dan het rechterkind van 60. Dat kind is NULL, dus ontbreekt 65. Je hoeft de linkerdeelboom van 50 niet te bekijken, omdat elke sleutel daar kleiner is dan 50.

De recursieve vorm handelt eerst t == NULL en een passende sleutel af en doorzoekt daarna het gekozen kind. De controle op een lege deelboom is het basisgeval. Zonder die controle derefereert een mislukte zoekopdracht uiteindelijk een nulpointer. De versie met een lus stopt op dezelfde grens zonder recursieve aanroepen toe te voegen.

De vorm het werk laten verklaren

Een perfect gebalanceerde boom met 20 niveaus bevat 1.048.575 sleutels en een zoekopdracht bezoekt hoogstens 20 knopen. Als je in plaats daarvan de al gesorteerde sleutels 10, 20, 30, 40, 50 invoegt, ontstaat een keten naar rechts. De hoogte is 4 en bij het zoeken naar 50 bezoek je alle vijf de knopen. De ordeningsregel van een binaire zoekboom alleen houdt de boom niet kort. Zelfbalancerende bomen zoals AVL-bomen herschikken takken om de hoogte te beheersen.

Om een minimum te vinden, volg je linkerkinderen totdat de volgende linkerverwijzing NULL is. Voor een maximum volg je rechterkinderen. In het voorbeeld met zeven knopen zijn deze sleutels 20 en 80. Dezelfde richtingskeuzes werken bij elke wortel van een deelboom, omdat de ordeningsregel daar ook geldt.

Belangrijkste punten

  • Een knoop in een binaire boom heeft hoogstens twee kinderen; ontbrekende kinderen zijn nulpointers.
  • De ordening van een binaire zoekboom geldt voor elke nakomeling in elke deelboom.
  • Zoeken gaat naar links voor een kleinere sleutel en naar rechts voor een grotere sleutel.
  • De hoogte telt hier verbindingen, met een hoogte van −1 voor een lege boom.
  • Een lage boom vermindert het aantal bezoeken; een binaire zoekboom kan toch een keten worden.

5.2 Werk een zoekboom bij, doorloop die en geef het geheugen vrij

Boomfuncties werken op een deelboom en verbinden hun resultaat met de ouder. Maak het geval van een lege deelboom expliciet en onderscheid bewerkingen die verwijzingen wijzigen van bewerkingen die alleen knopen bezoeken.

Via de deelboomverwijzing invoegen

Met tnode dat key, left en right bevat, neem je <stdbool.h> en <stdlib.h> op:

bool insert(tnode **link, int key) {
    if (*link == NULL) {
        tnode *n = malloc(sizeof *n);
        if (n == NULL) return false;
        *n = (tnode){.key = key, .left = NULL, .right = NULL};
        *link = n;
        return true;
    }
    if (key < (*link)->key) return insert(&(*link)->left, key);
    if (key > (*link)->key) return insert(&(*link)->right, key);
    return true;
}

Roep aan met insert(&root, key) en controleer het resultaat. Een bestaande sleutel laat de boom ongewijzigd en geldt als succes. Via het adres van de verwijzing kan de invoeging zowel de wortel van de aanroepende functie als een kindpointer bijwerken. Bij een interface die de wortel teruggeeft, sla je elk resultaat op: t->left = insert(t->left, key) en root = insert(root, key). Als je een teruggegeven geheugenallocatie negeert terwijl de oorspronkelijke deelboom leeg was, blijft die knoop ongekoppeld en ontstaat een geheugenlek.

Kiezen wanneer je de wortel bezoekt
DoorloopBezoekvolgordeGebruikelijk doel
InordeLinks, wortel, rechtsSleutels van een binaire zoekboom gesorteerd afdrukken
PreordeWortel, links, rechtsOuders vóór kinderen bezoeken
PostordeLinks, rechts, wortelKinderen afhandelen vóór het vrijgeven van een ouder

Bij het invoegen van 50, 30, 70, 20, 40, 60, 80 is de uitvoer in inorde “20 30 40 50 60 70 80”. Preorde geeft “50 30 20 40 70 60 80”; postorde geeft “20 40 30 60 80 70 50”.

void free_tree(tnode *t) {
    if (t == NULL) return;
    free_tree(t->left);
    free_tree(t->right);
    free(t);
}

Stel na free_tree(root) bij de eigenaar root = NULL in. De functie geeft eerst de kinderen vrij, zodat de functie hun adressen uit de nog bestaande ouder kan lezen. Het aantal knopen is nul voor een lege deelboom, anders één plus het aantal van beide kinderen. De hoogte is −1 voor een lege deelboom, anders één plus de grootste hoogte van de kinderen. Het voorbeeld heeft 7 knopen en hoogte 2.

Verwijderen zonder de volgorde te verstoren

Een blad wordt NULL. Een knoop met één kind wordt vervangen door dat kind. Bij twee kinderen kopieer je de kleinste sleutel uit de rechterdeelboom naar de knoop en verwijder je die opvolger daarna van zijn oorspronkelijke positie:

tnode *erase(tnode *t, int key) {
    if (t == NULL) return NULL;
    if (key < t->key) t->left = erase(t->left, key);
    else if (key > t->key) t->right = erase(t->right, key);
    else {
        if (t->left == NULL || t->right == NULL) {
            tnode *child = t->left != NULL ? t->left : t->right;
            free(t);
            return child;
        }
        tnode *s = t->right;
        while (s->left != NULL) s = s->left;
        t->key = s->key;
        t->right = erase(t->right, s->key);
    }
    return t;
}

Roep aan als root = erase(root, key). Als je 50 uit het voorbeeld verwijdert, komt 60 in de wortel en wordt de oude knoop met 60 verwijderd. De uitvoer in inorde wordt “20 30 40 60 70 80”; preorde wordt “60 30 20 40 70 80”. Een opvolger kan een rechterkind hebben, dus moet bij het verwijderen dat kind opnieuw worden gekoppeld.

Gebruik voor woordsleutels het teken van strcmp, geen pointergelijkheid, en geef elk woord in eigendom zijn eigen geheugenallocatie. Geef het woord vóór zijn knoop vrij. Als je bij zulke records een knoop met twee kinderen verwijdert, heb je ook een eigendomsplan nodig voor het verplaatste woord en een eventuele bijbehorende waarde; als je een pointer met eigenaarschap kopieert en daarna het woord van de oorspronkelijke knoop vrijgeeft, blijft een pointer naar een niet meer bestaande sleutel achter.

Belangrijkste punten

  • Bij invoegen moet een nieuwe wortel van de deelboom worden ingesteld en een mislukte geheugenallocatie worden gemeld.
  • Inorde drukt sleutels van een binaire zoekboom gesorteerd af; preorde begint met de ouder.
  • Opruimen in postorde geeft beide kinderen vóór hun ouder vrij.
  • Het aantal knopen telt de aantallen van beide kinderen op; de hoogte neemt de grootste hoogte van de kinderen.
  • Bij verwijderen worden nul of één kind opnieuw gekoppeld, of wordt de sleutel van een knoop met twee kinderen vervangen door zijn opvolger.
  • Sla de teruggegeven wortel na verwijdering op, ook als de oorspronkelijke wortel is verwijderd.
  • Bomen met stringsleutels vergelijken tekst en beheren elke stringallocatie waarvan ze eigenaar zijn.
6

Sla sleutels op en zoek ze op met hashtabellen

Hashtabellen zetten een sleutel om in een startlocatie en lossen daar eventuele botsingen op. De hash en de gelijkheidstest moeten overeenstemmen over welke sleutels hetzelfde betekenen. Een implementatie met ketens brengt arrays, invoegen in lijsten, stringkopieën en opruimen samen. Het nut hangt af van de verdeling van sleutels over buckets en het opnieuw bepalen van die locaties wanneer het aantal buckets verandert.

6.1 Hash sleutels naar buckets en handel botsingen af

Een hashtabel kiest een startlocatie op basis van een sleutel. Een hashfunctie levert een getal op; dat getal modulo een positief aantal buckets geeft een index. Bij botsingsafhandeling met ketens staat in elke bucket een gekoppelde lijst, zodat een zoekopdracht de gekozen keten doorzoekt.

Hashen en gelijkheid onderscheiden

Een bruikbare hash is deterministisch, snel te berekenen en verdeelt de verwachte sleutels over de buckets. Gelijke sleutels moeten gelijke hashes opleveren. Verschillende sleutels kunnen nog steeds een index delen: dat is een botsing en elke tabel moet die afhandelen. Als er meer mogelijke sleutels dan buckets zijn, zijn sommige botsingen onvermijdelijk.

Voor woorden die met een letter beginnen op een ASCII-compatibel systeem wijst toupper((unsigned char)word[0]) - 'A' 26 letterbuckets toe; “Luigi” begint in bucket 11. Veel Engelse woorden beginnen met “s” en weinig met “x”, dus kan deze regel zeer ongelijke ketens opleveren. De expliciete typeconversie is nodig omdat functies uit <ctype.h> een waarde vereisen die als een teken zonder teken kan worden weergegeven, of EOF. Controleer de beginletter en de aanname over de tekenset voordat je deze indexregel gebruikt.

Een hash op basis van de som van tekencodes heeft ook zwakke punten. Met ASCII-codes en tien buckets geeft de som voor “cat” 99 + 97 + 116 = 312 en komt die in bucket 2; de som voor “dog” is 314 en komt in bucket 4. “act” geeft ook 312. Bij optellen gaat de lettervolgorde verloren, dus veroorzaakt elke herschikking een botsing.

Tekenvolgorde in de hash verwerken
unsigned long hash_word(const char *word) {
    unsigned long h = 5381;
    const unsigned char *p = (const unsigned char *)word;
    while (*p != '\0') {
        h = h * 33 + *p;
        ++p;
    }
    return h;
}

Dit djb2-patroon gebruikt rekenkunde zonder teken, waarbij overloop op een vastgelegde manier terugloopt. Overloop bij getallen met teken zou ongedefinieerd gedrag zijn. Voor de ASCII-strings “cat” en “act” zijn de waarden 193.488.125 en 193.486.013. Bereken de rest met hash_word(word) % N, waarbij N het positieve aantal buckets is. De lettervolgorde verandert deze hashes, hoewel de restberekening nog steeds een botsing kan opleveren.

Als een tabel “Cat” en “cat” als gelijk behandelt, moeten zowel het hashen als het vergelijken dezelfde regel voor hoofdletters en kleine letters gebruiken. Als je dat onderscheid alleen bij het vergelijken negeert, kunnen gelijke sleutels in verschillende buckets terechtkomen, waar de zoekopdracht ze niet zal vergelijken. Een negatieve rest van een hash met teken is ook ongeschikt als arrayindex.

Een strategie voor botsingen kiezen

Botsingsafhandeling met ketens slaat elke botsing op in de gekoppelde lijst van een bucket. De zoekopdracht vergelijkt sleutels totdat een overeenkomst wordt gevonden of het einde is bereikt. Open adressering slaat tabelitems op in de array zelf. Bij lineair sonderen probeert de functie bij een botsing de volgende plaats en loopt aan het einde terug. Het zoeken volgt dezelfde sondeervolgorde en kan dus meerdere plaatsen in de array bekijken.

Indexen opnieuw berekenen als de tabel groeit

De bezettingsgraad is het aantal opgeslagen sleutels gedeeld door het aantal buckets. Twaalf sleutels in acht buckets geven gemiddeld 1,5 tabelitems per bucket. Zestien buckets verlagen dat gemiddelde tot 0,75. Dit gemiddelde garandeert niet dat elke keten kort is; een slechte hash kan alle sleutels bij elkaar plaatsen.

Een bucketindex hangt af van het aantal buckets. Nadat je geheugen voor een grotere bucketarray hebt gereserveerd en die hebt geïnitialiseerd, bereken je de index van elke opgeslagen sleutel opnieuw voor het nieuwe aantal en koppel je de sleutel daar opnieuw. Als je de oude koppointers naar de nieuwe buckets met hetzelfde nummer kopieert, blijven sleutels op plaatsen waar de nieuwe zoekopdracht niet zal zoeken. Een tabel met ketens kan meer tabelitems dan buckets bevatten; open adressering heeft ongebruikte plaatsen nodig voor nieuwe tabelitems en betrouwbaar sonderen.

Belangrijkste punten

  • Bereken een deterministische hash modulo een positief aantal buckets om een index te krijgen.
  • Gelijke sleutels vereisen gelijke hashes; verschillende sleutels kunnen botsen.
  • Rekenkunde zonder teken voorkomt ongedefinieerde overloop met teken bij de hashberekening.
  • Bij ketens doorloop je één bucketlijst; bij open adressering volg je een sondeervolgorde.
  • De bezettingsgraad meet tabelitems per bucket, niet de langste keten.
  • Hash elke sleutel opnieuw wanneer het aantal buckets verandert.

6.2 Maak een woordteltabel die eigenaar is van zijn sleutels

Een tabel met ketens combineert een array, lijstverwijzingen, stringvergelijking en eigenaarschap. Elk tabelitem is eigenaar van zijn gekopieerde sleutel. De bucketarray is eigenaar van de ketens en elke keten eindigt bij NULL.

Buckets initialiseren en tekst opzoeken
#include <stdbool.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#define N 26
typedef struct entry {
    char *key;
    int count;
    struct entry *next;
} entry;
static entry *table[N];

Statische opslag begint met nulpointers in elke bucket. Een lokale array heeft daarentegen een initialisator nodig zoals entry *table[N] = {NULL};. Gebruik de functie hash_word zonder teken die begint bij 5381 en elke byte met vermenigvuldiger 33 verwerkt.

entry *find_word(const char *key) {
    size_t i = hash_word(key) % N;
    for (entry *e = table[i]; e != NULL; e = e->next)
        if (strcmp(e->key, key) == 0) return e;
    return NULL;
}

strcmp vergelijkt tekst. e->key == key zou controleren of de pointers dezelfde locatie aanduiden. Twee afzonderlijke buffers kunnen gelijke woorden bevatten, dus is pointergelijkheid geen manier om een woord op te zoeken.

Een bestaande sleutel tellen of een nieuwe plaatsen
bool add_word(const char *key) {
    entry *e = find_word(key);
    if (e != NULL) {
        if (e->count == INT_MAX) return false;
        ++e->count;
        return true;
    }
    e = malloc(sizeof *e);
    if (e == NULL) return false;
    e->key = malloc(strlen(key) + 1);
    if (e->key == NULL) {
        free(e);
        return false;
    }
    strcpy(e->key, key);
    e->count = 1;
    size_t i = hash_word(key) % N;
    e->next = table[i];
    table[i] = e;
    return true;
}

De aanroepende functie levert een string die eindigt op een nulteken. De controle op INT_MAX meldt een fout voordat een aantal buiten het bereik van de int-opslag zou lopen. De extra byte in de stringallocatie bevat '\0'. Als de tweede geheugenallocatie mislukt, geef je het nog niet gekoppelde tabelitem vrij. De koppointer van de bucket verandert pas nadat beide geheugenallocaties en de initialisatie zijn geslaagd.

Als je in plaats daarvan e->key = key opslaat, behoud je de opslag van de aanroepende functie. Als invoer herhaaldelijk één buffer hergebruikt, kan elke opgeslagen pointer het nieuwste woord tonen. Door de tekst te kopiëren krijgt elk tabelitem een levensduur die onafhankelijk is van die buffer. strdup biedt die bewerking in POSIX en C23; expliciete geheugenallocatie en strcpy werken in C17.

In een voorbeeld met tien buckets en de som van tekencodes laat het invoegen van “cat”, “dog”, “act” de keten “act” → “cat” achter in bucket 2 en “dog” in bucket 4. Zoeken naar “tac” doorzoekt bucket 2, vergelijkt beide woorden en bereikt NULL. Een passende bucket is alleen een mogelijke locatie, geen bewijs van een gelijke sleutel.

Gegevensinhoud, knopen en koppointers van buckets vrijgeven
void clear_table(void) {
    for (size_t i = 0; i < N; ++i) {
        entry *e = table[i];
        while (e != NULL) {
            entry *next = e->next;
            free(e->key);
            free(e);
            e = next;
        }
        table[i] = NULL;
    }
}

Het bewaren van next en het vrijgeven van de sleutel gebeuren terwijl het tabelitem nog bestaat. Door elke bucket terug te zetten, kan die opnieuw worden gebruikt. Geheugencontroleprogramma’s helpen de uitgevoerde paden te controleren; ook paden bij mislukte geheugenallocatie moeten opruimen. Hashvolgorde is geen gesorteerde volgorde. Om alfabetisch af te drukken, verzamel je sleutels om te sorteren of sla je ze op in een binaire zoekboom.

Bij open adressering is voor verwijderen een verwijderingsmarkering nodig, zodat zoeken via een verwijderde plaats doorgaat naar latere botsende sleutels. Een werkelijk ongebruikte plaats kan een mislukte zoekopdracht beëindigen. Sondeervolgordes moeten begrensd zijn en de tabel moet groeien voordat elke plaats bezet is.

Belangrijkste punten

  • Initialiseer elke koppointer van een bucket op NULL.
  • De hash kiest een keten; strcmp bevestigt of een sleutel overeenkomt.
  • Verhoog de teller van een bestaande sleutel in plaats van een dubbel tabelitem toe te voegen.
  • Kopieer invoerstrings en ruim het tabelitem op als de geheugenallocatie voor de sleutel mislukt.
  • Geef de sleutel vóór zijn tabelitem vrij, bewaar de opvolger en zet elke bucket terug.
  • Bij verwijderen met open adressering behouden verwijderingsmarkeringen de zoekpaden.

Structs en gekoppelde datastructuren: voorbeelden

6 Walkthroughs

1

Volg records via kopieën en pointers

15 stappen
Maak een meetrecord, geef het door aan functies, doorloop een array en onderscheid ingebedde gegevens van pointers in eigendom.
  1. 1
    IntroductieHoud één meting bij elkaar

    Een meting combineert een identifier en een temperatuur. Als je ze in één record bewaart, een C-struct, kan code het paar als één waarde doorgeven of opslaan.

    Volg dat record vanaf de definitie naar een functieaanroep en vervolgens naar een array. Onderscheid bij elke stap het object van de pointers die het bereiken: dat onderscheid bepaalt of een wijziging een kopie of het origineel beïnvloedt en of een ondiepe kopie van een pointerveld opslag deelt.

  2. 2
    VoorbeeldDefinieer het record

    De definitie introduceert een type; de volgende declaratie maakt een object van dat type.

    struct reading { int id; double temp_c; };
    struct reading r = {.id = 24, .temp_c = 16.5};
    r.temp_c += 0.5;
    

    De punt selecteert een veld van r, dus is de temperatuur nu 17,0 °C. Elk veld heeft een eigen type en de puntkomma na de sluitende accolade beëindigt de typedefinitie.

  3. 3
    KernpuntKopieer de veldwaarden

    Maak nu een tweede record met struct reading s = r;. Die toewijzing kopieert beide veldwaarden. Als je daarna s.id of s.temp_c wijzigt, blijft r ongewijzigd.

    Een array die in een struct is opgeslagen, wordt ook gekopieerd. Opvulling kan nog steeds tussen objecten verschillen, dus om gelijkheid te testen vergelijk je de velden die relevant zijn voor de taak. C biedt geen ==-operator voor hele structs.

  4. 4
    UitdagingLees een weggelaten veld

    Wat zet struct reading a = {.id = 31}; in a.temp_c en mag code die waarde lezen?

    ---reveal---

    Het weggelaten veld wordt op nul geïnitialiseerd, dus is de temperatuur 0.0 en kan die worden gelezen. Er is een initialisator aanwezig, ook al noemt die alleen id. Een declaratie van een lokale a zonder initialisator zou daarentegen toewijzingen vereisen voordat de velden worden gelezen.

  5. 5
    UitlegGeef het type een korte naam

    Een typedef biedt een alias zonder het gedrag van objecten te veranderen.

    typedef struct { int id; double temp_c; } reading;
    reading make(int id, double t) {
        return (reading){.id = id, .temp_c = t};
    }
    

    Een aanroep kan het volledige record op waarde teruggeven. Als een knoop binnen een definitie naar zijn eigen type moet verwijzen, geef je dat type een tag en schrijf je die zelfverwijzing met struct node *next.

  6. 6
    VoorbeeldKies een kopie of een adres

    Vergelijk twee functie-interfaces:

    void warm_copy(reading x) { x.temp_c += 1.0; }
    void warm(reading *p) { p->temp_c += 1.0; }
    

    warm_copy(r) wijzigt een lokale kopie. warm(&r) bereikt het record van de aanroepende functie en wijzigt het veld. De pijl betekent (*p).temp_c; de haakjes zorgen dat het derefereren gebeurt voordat de punt het veld selecteert.

  7. 7
    TipReserveer een record met een duidelijke eigenaar

    Neem <stdlib.h> op en reserveer geheugen met reading *p = malloc(sizeof *p);. Controleer voordat je via p schrijft of de pointer NULL is en handel een fout af.

    Initialiseer na succes de velden en zorg voor een eigenaar die verantwoordelijk is voor free(p). Een structwaarde teruggeven is veilig; het adres van een lokale struct teruggeven laat een pointer naar een niet meer bestaand object achter, omdat dat lokale object ophoudt te bestaan wanneer zijn functie terugkeert.

  8. 8
    UitdagingVoorspel welke temperatuur verandert

    Een aanroepende functie begint met reading r = {24, 17.0};, roept warm_copy(r) aan en daarna warm(&r) met de twee getoonde functies. Welke temperatuur bevat r?

    ---reveal---

    De temperatuur is 18,0 °C. De eerste aanroep wijzigt alleen zijn eigen kopie, dus heeft de aanroepende functie nog 17,0 wanneer de tweede aanroep begint. De tweede ontvangt het adres van het record van de aanroepende functie en telt via die pointer één op.

  9. 9
    VoorbeeldDoorloop een verzameling records

    Stel dat een array de temperaturen −5,5, −1,0 en −7,5 °C bevat. Nadat je hebt gecontroleerd dat het aantal niet nul is, laat je best bij index 0 beginnen en vergelijk je elk later record met het huidige beste record.

    Index 1 vervangt index 0 omdat −1,0 warmer is dan −5,5. Index 2 vervangt die niet. Beginnen met een bestaand element werkt als alle waarden onder nul liggen; een verzonnen beginmaximum van nul zou ze missen.

  10. 10
    TipGeef een teken terug uit de vergelijkingsfunctie

    Voor eindige temperaturen kan een vergelijkingsfunctie (x->temp_c > y->temp_c) - (x->temp_c < y->temp_c) teruggeven. Het resultaat is negatief, nul of positief.

    Als je het temperatuurverschil als int teruggeeft, kan een klein verschil nul worden. Bij extreme sleutels kan het aftrekken van gehele getallen overlopen. Een teken op basis van vergelijkingen voorkomt beide fouten en qsort garandeert nog steeds niet dat de volgorde van gelijke elementen behouden blijft.

  11. 11
    UitlegZorg dat herstel mogelijk blijft bij een mislukte groottewijziging

    Houd voor een heaparray het aantal gebruikte elementen afzonderlijk van de capaciteit bij. Controleer of het gevraagde aantal bytes past en wijs daarna het resultaat van realloc met een grootte anders dan nul toe aan een tijdelijke pointer.

    Bij een fout blijft de oorspronkelijke array beschikbaar. Stel bij succes het nieuwe basisadres en de nieuwe capaciteit samen in. Bouw elementpointers opnieuw op vanuit hun bewaarde indexen, omdat de geheugenallocatie kan zijn verplaatst of de eerdere opslag kan hebben vervangen.

  12. 12
    VerbandBekijk wat een pointerkopie deelt

    Een persoonsrecord met char *name bevat een adres, dus zorgt person b = a; ervoor dat beide naamvelden naar één string wijzen. Dat is een ondiepe kopie.

    Als elk record eigenaar moet zijn van een onafhankelijke string, geef je b.name een eigen gecontroleerde geheugenallocatie en kopieer je de tekst, inclusief de afsluitende nulbyte. Elke eigenaar kan dan zijn eigen string één keer vrijgeven. Het kopiëren van een ingebedde tekenarray levert al afzonderlijke tekens op.

  13. 13
    UitdagingHerstel de naamkopie

    De name van een record is een heapstring waarvan het record eigenaar is. Code kopieert het record en is daarna van plan beide namen onafhankelijk vrij te geven. Wat moet vóór dat opruimen gebeuren?

    ---reveal---

    Reserveer een afzonderlijke naam voor de kopie, controleer of dat is gelukt en kopieer de oorspronkelijke tekst voordat een van beide strings wordt vrijgegeven. De twee records delen momenteel een adres. Als je beide gedeelde pointers vrijgeeft, wordt hetzelfde blok twee keer vrijgegeven; de pointer nogmaals kopiëren helpt niet.

  14. 14
    UitdagingGeef een record met een ingebed label terug

    Een functie geeft een struct met char label[12] op waarde terug. Wordt het label ongeldig omdat de oorspronkelijke struct lokaal was? Leg uit met behulp van zowel levensduur als kopiëren.

    ---reveal---

    Nee. Bij teruggeven op waarde worden de veldwaarden van de struct, inclusief de ingebedde array, naar de retourwaarde gekopieerd. De aanroepende functie is eigenaar van het resulterende record. Als je een pointer naar de lokale struct zou teruggeven, zou de aanroepende functie toegang moeten krijgen tot een object waarvan de levensduur is geëindigd.

  15. 15
    SamenvattingHoud waarden en eigenaars bij

    Een structobject slaat veldwaarden op. Een pointer duidt een object aan en een gekopieerde pointer duidt nog steeds hetzelfde doel aan. Functieparameters en retourwaarden volgen dat onderscheid.

    Houd bij arrays aantallen expliciet bij en behoud de eigenaar tijdens het aanpassen van de grootte. Bepaal bij records met pointers in eigendom of een kopie opslag deelt of onafhankelijke geheugenallocaties krijgt. Die keuzes bepalen zowel wijzigingen als opruimen.

2

Houd keuzes en gegevensinhoud van unions consistent

11 stappen
Volg enumeratiewaarden, wijs ontbrekende waarden af, maak een getagde cel en kies tussen gelijktijdige en alternatieve velden.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBenoem de keuze en bepaal de gegevensinhoud
  2. 2
    VoorbeeldVolg expliciete enumeratiewaarden
  3. 3
    KernpuntControleer de toegestane verzameling
  4. 4
    TipVoorkom dat gevallen doorvallen
  5. 5
    UitdagingValideer een fasewaarde
  6. 6
    UitlegLaat de alternatieve waarden overlappen
  7. 7
    VoorbeeldStel beide delen van een getagde waarde in
  8. 8
    UitdagingHerken de niet-overeenkomende wijziging
  9. 9
    TipSla een klein bereik voor een vlag op
  10. 10
    UitdagingKies opslag voor een record
  11. 11
    SamenvattingHoud elke beschrijving in overeenstemming met de gegevens
3

Volg het maken, invoegen en verwijderen van lijsten

16 stappen
Gebruik inkomende verwijzingen om een keten te behouden en houd daarna uiteinden, aantallen en gegevensinhoud in eigendom bij.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieWijzig een keten verwijzing voor verwijzing
  2. 2
    UitlegBegin met een lege koppointer
  3. 3
    VoorbeeldVoeg vooraan in zonder de keten te verliezen
  4. 4
    KernpuntLaat de aanroepende functie de nieuwe koppointer bewaren
  5. 5
    UitdagingVoorspel de volgorde bij vooraan invoegen
  6. 6
    UitlegVolg het adres van een verwijzing
  7. 7
    VoorbeeldZoek een invoegpositie in een gesorteerde lijst
  8. 8
    UitdagingVoeg vóór de eerste knoop in
  9. 9
    VoorbeeldVerwijder veilig een knoop uit het midden
  10. 10
    TipStop veilig als een waarde ontbreekt
  11. 11
    UitlegVoeg een staartpointer en een aantal toe
  12. 12
    VerbandGebruik een terugverwijzing wanneer verwijderen die vereist
  13. 13
    UitdagingKies opslag voor herhaald lezen via een index
  14. 14
    TipGeef gegevensinhoud vóór de knoop vrij
  15. 15
    UitdagingCombineer verwijderen en metagegevens
  16. 16
    SamenvattingHoud het inkomende pad geldig
4

Volg stapelbewerkingen en terugloop in een wachtrij

14 stappen
Evalueer geneste bewerkingen, houd de uiteinden van een gekoppelde wachtrij bij en volg de logische volgorde van een circulaire wachtrij.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieKies welk wachtend item aan de beurt is
  2. 2
    UitlegGebruik top als aantal items
  3. 3
    VoorbeeldVolg de stapelvolgorde
  4. 4
    VoorbeeldVergelijk het sluitingsteken met het bovenste openingsteken
  5. 5
    VoorbeeldHoud postfixoperanden op volgorde
  6. 6
    UitdagingControleer een gewijzigde postfixexpressie
  7. 7
    UitlegOnderscheid voorkant en achterkant
  8. 8
    TipHerstel het geval met één knoop
  9. 9
    UitlegGebruik een ring in plaats van te verschuiven
  10. 10
    VoorbeeldVolg een indeling met terugloop
  11. 11
    UitdagingZoek de volgende invoegpositie
  12. 12
    KernpuntHoud één regel voor een volle wachtrij aan
  13. 13
    UitdagingKies de verwerkingsvolgorde
  14. 14
    SamenvattingLaat volgorde en toestand overeenkomen
5

Maak en doorzoek een binaire zoekboom, doorloop die en verwijder eruit

15 stappen
Volg één boom bij de keuze van takken, invoegen, doorloopvolgordes, vervangen door een opvolger en opruimen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVolg een boom van wortel naar deelboom
  2. 2
    KernpuntPas de regel onder elke voorouder toe
  3. 3
    VoorbeeldMaak de voorbeeldboom
  4. 4
    VoorbeeldDoorzoek steeds één tak
  5. 5
    UitdagingStel een overtreding bij een nakomeling vast
  6. 6
    VerbandMeet de vorm
  7. 7
    VoorbeeldKoppel een invoeging waar het zoeken stopt
  8. 8
    VoorbeeldKies een bezoekvolgorde
  9. 9
    UitlegTel takken en bepaal de padlengte
  10. 10
    UitlegHandel nul of één kind af
  11. 11
    VoorbeeldVervang de sleutel van een knoop met twee kinderen
  12. 12
    UitdagingBehoud het kind van de opvolger
  13. 13
    TipGeef nakomelingen vóór hun ouder vrij
  14. 14
    UitdagingControleer doorloop en hoogte samen
  15. 15
    SamenvattingVerbind elke gewijzigde deelboom opnieuw
6

Maak een woordteller met een hashtabel

14 stappen
Volg botsingen, gelijkheid, gekopieerde sleutels, groei, mislukte geheugenallocaties en volledig opruimen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieGebruik een sleutel om het zoekgebied te beperken
  2. 2
    VoorbeeldBereken een startbucket
  3. 3
    KernpuntHoud gelijkheid en hashen consistent
  4. 4
    UitdagingOnderscheid een botsing van een overeenkomst
  5. 5
    UitlegGebruik de bezettingsgraad zonder de verdeling uit het oog te verliezen
  6. 6
    VoorbeeldBereken plaatsing opnieuw na groei
  7. 7
    UitlegZoek tekst op in een keten
  8. 8
    VoorbeeldPlaats een nieuwe sleutel in eigendom
  9. 9
    UitdagingHandel een gedeeltelijke geheugenallocatie af
  10. 10
    TipBewaar geen hergebruikte invoerbuffer
  11. 11
    UitlegBehoud sondeerpaden na verwijderen
  12. 12
    UitdagingCombineer botsingsafhandeling en woordtellingen
  13. 13
    VerbandGeef elke sleutel en knoop vrij
  14. 14
    SamenvattingLaat locatie, gelijkheid en levensduur overeenstemmen

Structs en gekoppelde datastructuren: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Lees records en volg basisbewerkingen10 Oefenvragen

Vraag 1 van 10

Welke waarde heeft r.temp_c na struct reading { int id; double temp_c; }; struct reading r = {.id = 12};?
  1. Een onbepaalde waarde
  2. 0.0
  3. 12.0
  4. Een initialisatiefout
Toon antwoordVerberg antwoord
Antwoord: B
De benoemde initialisator stelt id in en initialiseert het weggelaten veld temp_c op nul, dus is de waarde 0.0. Dit verschilt van het declareren van een lokale struct zonder enige initialisator. Door de opgegeven initialisator is het weggelaten veld bruikbaar zonder latere toewijzing.

Vraag 2 van 10

Een geldige reading *p wijst naar een temperatuurrecord. Welke expressie geeft toegang tot het veld temp_c?
  1. p.temp_c
  2. *p.temp_c
  3. &p.temp_c
  4. p->temp_c
Toon antwoordVerberg antwoord
Antwoord: D
De pijloperator selecteert een veld via een pointer en is gelijkwaardig aan (*p).temp_c. Een punt heeft links een structobject nodig, dus is p.temp_c hier ongeldig. *p.temp_c schrijven lost dat probleem niet op omdat de punt vóór het derefereren wordt toegepast.

Vraag 3 van 10

Records hebben eindige temperaturen −8,0, −2,5 en −6,0 °C op indexen 0, 1 en 2. Een zoekopdracht naar het maximum begint met best = 0 en werkt die bij voor een strikt warmer record. Welke index blijft over?
  1. 1
  2. 0
  3. 2
  4. Geen index; het maximum blijft nul
Toon antwoordVerberg antwoord
Antwoord: A
De zoekopdracht begint met het record op index 0 en vervangt dat daarna door index 1 omdat −2,5 warmer is dan −8,0. De laatste temperatuur, −6,0, is niet warmer dan −2,5, dus blijft index 1 over. Negatieve temperaturen geven geen bijzonder probleem wanneer het aanvankelijke beste record een bestaand record is.

Vraag 4 van 10

Welke waarde heeft PAUSED in enum state { IDLE = 3, RUNNING, FAILED = 9, PAUSED };?
  1. 5
  2. 4
  3. 10
  4. 9
Toon antwoordVerberg antwoord
Antwoord: C
Een impliciete enumeratiewaarde is één hoger dan de direct voorgaande enumeratieconstante. FAILED stelt de reeks expliciet in op 9, dus is PAUSED 10. Als je PAUSED vanaf het begin van de declaratie zou tellen, zou je de nieuwe telling door de expliciete waarde negeren.

Vraag 5 van 10

Een getagde union heeft enumeratietag kind en union as met velden int i en double d. Een bestaande waarde heeft tag K_DOUBLE. Welke wijziging slaat het gehele getal 18 consistent op?
  1. t.kind = K_DOUBLE; t.as.i = 18;
  2. t.kind = K_INT; t.as.d = 18.0;
  3. t.kind = K_INT; t.as.i = 18;
  4. t.kind = K_DOUBLE; t.as.d = 18.0;
Toon antwoordVerberg antwoord
Antwoord: C
De tag moet aangeven welk veld de opgeslagen waarde bevat. Door K_INT toe te wijzen en as.i te schrijven, beschrijven beide delen het gehele getal 18. Als je het geheelgetalveld schrijft terwijl de double-tag blijft staan, leest latere code de verkeerde representatie; de tag verandert niet automatisch.

Vraag 6 van 10

In een lege lijst worden eerst 6, dan 11 en daarna 3 vooraan ingevoegd. Welke waarden verschijnen als je de lijst vanaf de koppointer doorloopt?
  1. 6 → 11 → 3
  2. 3 → 11 → 6
  3. 11 → 6 → 3
  4. 3 → 6 → 11
Toon antwoordVerberg antwoord
Antwoord: B
Elke invoeging vooraan plaatst de nieuwe knoop vóór de huidige eerste knoop. De eerste knopen zijn dus achtereenvolgens 6, 11 en 3, met eerdere knopen daarachter gekoppeld. De volgorde van binnenkomst 6 → 11 → 3 zou ontstaan door achteraan toe te voegen, niet door de genoemde invoegingen vooraan.

Vraag 7 van 10

Een gesorteerde lijst bevat 1 → 7 → 12. Een invoeglus gaat verder zolang de huidige waarde kleiner is dan 9. Bij welke inkomende verwijzing stopt de lus?
  1. De koppointer naar de knoop met 1
  2. Het next-veld van de knoop met 1
  3. Het next-veld van de knoop met 12
  4. Het next-veld van de knoop met 7
Toon antwoordVerberg antwoord
Antwoord: D
De lus gaat voorbij 1 en 7 en stopt dan vóór 12 omdat 12 niet kleiner is dan 9. De verwijzing die momenteel 12 bereikt, is het next-veld van de knoop met 7. Stoppen bij de verwijzing na 12 zou 9 achteraan toevoegen en de oplopende volgorde van de lijst verstoren.

Vraag 8 van 10

Een lege stapel voert push 8, push 2, pop, push 5, pop, pop uit. Welke waarden geven de drie popbewerkingen terug?
  1. 2, 5, 8
  2. 8, 2, 5
  3. 2, 8, 5
  4. 5, 2, 8
Toon antwoordVerberg antwoord
Antwoord: A
De eerste pop verwijdert 2, op dat moment de meest recente waarde. Door 5 op de stapel te zetten komt die boven de overgebleven 8, dus geven de volgende popbewerkingen 5 en 8 terug. Eerst 8 teruggeven zou de volgorde van een wachtrij volgen en niet passen bij het principe laatst erin, eerst eruit van de stapel.

Vraag 9 van 10

Een binaire zoekboom wordt opgebouwd door 42, 18, 63, 27 en 21 in te voegen. Welke knopen bezoekt een zoekopdracht naar 21?
  1. 42 → 18 → 21
  2. 42 → 63 → 27 → 21
  3. 18 → 27 → 21
  4. 42 → 18 → 27 → 21
Toon antwoordVerberg antwoord
Antwoord: D
Bij 42 is 21 kleiner, dus gaat de zoekopdracht naar links naar 18; daarna gaat die naar rechts naar 27 en naar links naar 21. Bij het invoegen van 21 kwam die onder 27 terecht, niet direct onder 18. Een zoekopdracht volgt bestaande kindverwijzingen en kan niet naar een kleinere sleutel springen alleen omdat bekend is dat die aanwezig is.

Vraag 10 van 10

Een gevulde tabel maakt bij ASCII-woorden geen onderscheid tussen hoofdletters en kleine letters, maar hasht de oorspronkelijke bytes. Een zoekopdracht naar “mint” mist een bestaand tabelitem “Mint” zonder het te bezoeken. Welke volledige reparatie ondersteunt zowel bestaande als toekomstige tabelitems?
  1. Blijf ruwe bytes hashen, negeer hoofdletters bij gelijkheid en bouw dezelfde plaatsingen opnieuw op.
  2. Negeer hoofdletters bij hashen, behoud die gelijkheidstest en bouw de plaatsingen opnieuw op.
  3. Negeer hoofdletters bij hashen, behoud die gelijkheidstest en bewaar de oude plaatsingen.
  4. Blijf ruwe bytes hashen, vergelijk adressen en vergroot de bucketarray.
Toon antwoordVerberg antwoord
Antwoord: B
Hashen moet gelijke schrijfwijzen gelijk behandelen, dus normaliseer hoofdletters en kleine letters vóór het hashen en behoud de vergelijking zonder onderscheid daartussen. Bestaande tabelitems moeten ook volgens de gecorrigeerde regel worden geplaatst. Als alleen toekomstige hashes veranderen, blijven oude tabelitems op locaties die de gecorrigeerde zoekopdrachten mogelijk nooit bekijken.
Set 2: Pas regels voor toestand en opslag toe10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

struct tag { char name[8]; int id; }; wordt gebruikt voor a en b. Wat gebeurt er na b = a; b.name[0] = 'Z'; met a.name[0]?
  1. Het verandert in het teken Z.
  2. Het krijgt een onbepaalde waarde.
  3. Het behoudt het oorspronkelijke teken.
  4. De structtoewijzing kan niet compileren.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Gegeven void cool(reading r) { r.temp_c -= 2.0; }, stelt een aanroepende functie reading a = {9, 14.0}; in en roept cool(a); aan. Wat is daarna a.temp_c van de aanroepende functie?
  1. 14.0
  2. 12.0
  3. 16.0
  4. Een onbepaalde waarde
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Welke tekst drukt printf("%d", WED); af voor enum day { MON, TUE, WED, THU };?
  1. “WED”
  2. “3”
  3. “Wednesday”
  4. “2”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een getagde union slaat een double op onder tag K_DOUBLE. Latere code verandert alleen de tag in K_INT en een afdrukfunctie leest as.i. Wat is de fout?
  1. Het wijzigen van de tag zet de opgeslagen waarde om naar een geheel getal.
  2. De nieuwe tag selecteert een veld dat niet is opgeslagen.
  3. De union bewaart beide getypeerde waarden totdat die wordt vrijgegeven.
  4. De afdrukfunctie moet de oude tag behouden en het formaat wijzigen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een enkelvoudig gekoppelde lijst bewaart een kop- en staartpointer en een aantal. De enige knoop wordt verwijderd en vrijgegeven. Welke toestand moet overblijven?
  1. head = NULL; tail = NULL; count = 0;
  2. head = NULL; tail = old; count = 0;
  3. head = old; tail = NULL; count = 0;
  4. head = NULL; tail = NULL; count = 1;
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Welk resultaat levert de postfixexpressie “15 6 - 3 /” op met geheeltallige operanden?
  1. -3
  2. 7
  3. -7
  4. 3
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een circulaire wachtrij met een teller heeft capaciteit 5, head = 4 en count = 2. Naar welke plaats schrijft de volgende toevoeging?
  1. Plaats 0
  2. Plaats 2
  3. Plaats 1
  4. Plaats 4
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Welke doorloop geeft elke kinddeelboom vrij voordat de ouder wordt vrijgegeven?
  1. Preordedoorloop
  2. Postordedoorloop
  3. Inordedoorloop
  4. Doorloop van alleen de wortel
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een tabel met ketens en geheeltallige sleutels gebruikt de sleutel modulo het aantal buckets. De tabel voegt 13, 25 en 23 vooraan in 6 lege buckets in. Daarna groeit de tabel naar 10 buckets. Daarbij worden de oude buckets op oplopende index en elke keten vanaf de eerste knoop doorlopen, en wordt elk tabelitem vooraan in zijn nieuwe bucket ingevoegd. Welke ketens ontstaan?
  1. Bucket 3: 13 → 23; bucket 5: 25
  2. Bucket 1: 25 → 13; bucket 5: 23
  3. Bucket 3: 23 → 13; bucket 5: 25
  4. Bucket 3: 23; bucket 5: 25 → 13
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een aanvankelijk lege woordteller met ketens gebruikt een hash die “ash” en “elm” naar dezelfde bucket stuurt. De teller leest in één hergebruikte buffer, zoekt die tekst vervolgens op en verhoogt bij een overeenkomst de teller of reserveert een knoop met aantal 1. Nieuwe knopen slaan ten onrechte key = buffer op. De teller leest eerst “ash” en daarna “elm”. Wat blijft over vóór het opruimen?
  1. Eén tabelitem met “elm” en aantal 2
  2. Twee tabelitems, “ash” en “elm”, elk met aantal 1
  3. Eén tabelitem met “ash” en aantal 2
  4. Twee tabelitems met beide “elm”, elk met aantal 1
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Volg verwijzingen, terugloop en botsingen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een vergelijkingsfunctie voor eindige temperaturen geeft x->temp_c - y->temp_c terug als int. Twee verschillende records hebben temperaturen van 6,20 en 6,45 °C. Waarom kan sorteren ze als gelijk behandelen?
  1. De argumenten const void * dwingen beide invoerwaarden naar gehele getallen.
  2. De vergelijkingsfunctie moet precies het temperatuurverschil teruggeven.
  3. De twee eindige waarden zijn gelijk nadat qsort ze kopieert.
  4. Het verschil kleiner dan één wordt nul tijdens de conversie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welk bereik past in unsigned int mode : 4?
  1. 0 tot en met 4
  2. 0 tot en met 15
  3. 1 tot en met 16
  4. −8 tot en met 7
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

In een niet-lege lijst wordt na geheugenallocatie voor n vooraan ingevoegd met head = n; n->next = head;. Waarnaar wijst de next van de nieuwe knoop?
  1. De oude eerste knoop
  2. De oude laatste knoop
  3. De nieuwe knoop zelf
  4. De afsluitende nulmarkering
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een oplopende lijst begint met 3 → 8 → 12. Voeg 6 in, verwijder de eerste knoop en voeg daarna 8 in met een lus die verdergaat zolang de huidige waarde strikt kleiner is dan de ingevoegde waarde. Waar wordt de nieuwe 8 gekoppeld?
  1. Na 6 en vóór de bestaande 8
  2. Na de bestaande 8 en vóór 12
  3. Vóór 6 en na de verwijderde 3
  4. Na 12 en vóór de nulverwijzing
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een programma leest herhaaldelijk records via een numerieke index en doorloopt alle records, met weinig invoegingen. Welke opslag past het best bij deze bewerkingen?
  1. Een enkelvoudig gekoppelde lijst van records
  2. Een dubbel gekoppelde lijst van records
  3. Een gekoppelde wachtrij van recordknopen
  4. Een aaneengesloten array van records
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een lege gekoppelde wachtrij voegt 12 en 30 met succes toe, verwijdert één keer, kan bij het toevoegen van 41 geen geheugen reserveren, verwijdert opnieuw en voegt daarna 44 met succes toe. Correcte bewerkingen behouden de toestand bij een fout. Wat zijn de uiteindelijke uiteinden en wachtende waarden?
  1. De koppointer bereikt 30, de staartpointer 44; waarden 30 → 44.
  2. De koppointer bereikt 44 terwijl de staartpointer nul is.
  3. Beide uiteinden bereiken de enige knoop, 44.
  4. De koppointer bereikt 12, de staartpointer 44; waarden 12 → 44.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een boom heeft wortel 36, linkerkind 17 en 41 als rechterkind van 17. Waarom voldoet die niet aan de regel van een binaire zoekboom?
  1. 17 is kleiner dan zijn ouder en staat links.
  2. 41 staat in de linkerdeelboom van 36 maar is groter dan 36.
  3. 41 is groter dan zijn ouder en staat rechts.
  4. 36 heeft slechts één direct kind in plaats van twee kinderen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een binaire zoekboom wordt opgebouwd uit 48, 24, 75, 62, 68, 90. Verwijder 48 met behulp van zijn inordeopvolger. Wat zijn de nieuwe wortel en het overgebleven linkerkind van 75?
  1. Wortel 62; linkerkind 68
  2. Wortel 62; linkerkind NULL
  3. Wortel 75; linkerkind 62
  4. Wortel 68; linkerkind 62
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een tabel met ketens heeft 21 opgeslagen sleutels en 14 buckets. Wat is de bezettingsgraad?
  1. 1.5
  2. 0.67
  3. 7
  4. 35
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een hashzoekopdracht bereikt bij het zoeken naar “oak” een keten met “elm” en “ash”. Beide tekstvergelijkingen mislukken. Wat moet de zoekopdracht teruggeven nadat de keten eindigt?
  1. Het eerste tabelitem voor “elm”
  2. Het laatste tabelitem voor “ash”
  3. NULL
  4. Een nieuw tabelitem voor “oak”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Achterhaal kopieerfouten en grensfouten10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Op een platform met char van 1 byte, int van 4 bytes uitgelijnd op 4 bytes en structgrootten afgerond op een veelvoud van 4 heeft A de velden char, int, char in die volgorde; B heeft int, char, char. Hoeveel totale opslag gebruikt een array van vijf A-records vergeleken met vijf B-records?
  1. A gebruikt 60 bytes; B gebruikt 40 bytes.
  2. A gebruikt 30 bytes; B gebruikt 30 bytes.
  3. A gebruikt 45 bytes; B gebruikt 30 bytes.
  4. A gebruikt 60 bytes; B gebruikt 30 bytes.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een functieparameter is const reading *p. Welke toewijzing kan de lokale pointer wijzigen zonder een record te wijzigen?
  1. p->id = 19;
  2. (*p).temp_c = 8.0;
  3. p = another_reading_pointer;
  4. p->temp_c += 0.5;
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

a.name is eigenaar van een heapstring. Na person b = a; reserveert code met succes een tijdelijke string copy, maar heeft die nog niet gevuld en geen van beide records gewijzigd. Beide records moeten uiteindelijk onafhankelijke eigenaars zijn. Welke volgorde bereikt dat zonder gegevens of een geheugenallocatie te verliezen?
  1. Stel a.name = copy in; kopieer b.name naar a.name; geef a.name twee keer vrij.
  2. Kopieer a.name naar copy; stel b.name = copy in; geef elke naam één keer vrij.
  3. Geef a.name vrij; kopieer b.name naar copy; stel b.name = copy in.
  4. Kopieer a.name naar copy; laat b.name gedeeld; geef beide namen vrij.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Invoer is bedoeld voor enum mode { M_IDLE = 0, M_READ = 4, M_WRITE = 5 };. Welke test accepteert precies de benoemde waarden?
  1. x >= 0 && x <= 5
  2. x >= 4 && x <= 5
  3. x == 0 || x == 4 || x == 6
  4. x == 0 || x == 4 || x == 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Tijdens het opruimen van een lijst bestaat p nog en voert de code free(p); p = p->next; uit. Welke wijziging behoudt de bedoelde doorloop op een veilige manier?
  1. Wijs eerst p = p->next toe en geef daarna de nieuwe p vrij.
  2. Bewaar eerst next, geef p vrij en wijs daarna p = next toe.
  3. Geef eerst p->next vrij en wijs daarna p = p->next toe.
  4. Stel eerst p->next = NULL in en ga na het vrijgeven van p verder.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een gesorteerde lijst 2 → 6 → 6 → 14 gebruikt een lus die verdergaat zolang number < value. Er wordt een nieuwe 6 ingevoegd. Waar komt die?
  1. Tussen 2 en de eerste bestaande 6
  2. Tussen de twee bestaande knopen met 6
  3. Tussen de tweede 6 en 14
  4. Na 14 aan het einde van de lijst
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een arraystapel heeft top = 0 met teken en pop leest items[--top] zonder controle. Welke index probeert de bewerking te lezen?
  1. 0
  2. 1
  3. -1
  4. De laatste geldige arrayindex
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een circulaire wachtrij met capaciteit 6 gebruikt een teller. De wachtrij begint met head = 5, count = 3, met waarden P, Q, R in wachtrijvolgorde. Er wordt twee keer verwijderd en daarna worden S en T toegevoegd. Wat zijn de uiteindelijke kopindex, het aantal en de voorste waarde?
  1. Kopindex 0; aantal 3; vooraan Q
  2. Kopindex 1; aantal 5; vooraan R
  3. Kopindex 3; aantal 3; vooraan T
  4. Kopindex 1; aantal 3; vooraan R
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een binaire zoekboom bevat wortel 31, linkerkind 12 met rechterkind 19 en rechterkind 54. Welke uitvoer in preorde blijft over na het verwijderen van 12?
  1. 19, 31, 54
  2. 31, 54, 19
  3. 31, 19, 54
  4. 31, 12, 19, 54
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Er wordt geheugen voor een nieuw tabelitem gereserveerd, maar de geheugenallocatie voor de sleutelstring mislukt voordat het item wordt gekoppeld. Welke opruiming behoudt de tabel en voorkomt een geheugenlek?
  1. Geef de sleutel van de aanroepende functie vrij en geef succes terug.
  2. Geef het nieuwe tabelitem vrij en geef een fout terug.
  3. Koppel het item met een nulsleutel en geef succes terug.
  4. Geef een fout terug maar behoud het nieuwe tabelitem.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Combineer regels in onbekende gevallen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een lus probeert elke 5 uit 5 → 5 → 9 te verwijderen:

node **link = &head;
while (*link != NULL) {
    if ((*link)->number == 5) {
        node *old = *link;
        *link = old->next;
        free(old);
    }
    if (*link != NULL) link = &(*link)->next;
}

Waarom blijft er een 5 over en welke wijziging herstelt de lus?

  1. De lus slaat een vrijgegeven knoop over; geef de opvolger vrij voordat je verdergaat.
  2. De lus slaat een behouden overeenkomst over; ga alleen na een niet-overeenkomst verder.
  3. De lus verliest de koppointer; start de lus na elke niet-overeenkomst opnieuw.
  4. De lus verwijdert de verkeerde knoop; geef die vrij voordat je de opvolger leest.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een heaparray heeft drie gebruikte records, die elk eigenaar zijn van een afzonderlijke naamstring, en een bewaarde pointer naar record 1. Een groottewijziging naar capaciteit 6 slaagt. Het programma wil record 0 verwijderen door de andere twee records naar links te verschuiven en het aantal gebruikte records te verlagen naar 2. Welk plan behoudt toegang tot het oorspronkelijke record 1 en geeft alle opslag in eigendom vrij?
  1. Geef de verwijderde naam vrij; verschuif; bouw de pointer opnieuw op bij index 0; geef later actieve namen en de array vrij.
  2. Geef de verwijderde naam vrij; verschuif; behoud de oude pointer; geef later alle zes capaciteitsplaatsen en de array vrij.
  3. Verschuif; bouw de pointer opnieuw op bij index 1; geef later alleen de twee actieve namen en de array vrij.
  4. Verschuif; bouw de pointer opnieuw op bij index 0; geef later namen van alle drie oude plaatsen en de array vrij.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Aan een bitveld zonder teken van 3 bits wordt tijdens de uitvoering de waarde 13 toegewezen. Welke waarde wordt opgeslagen?
  1. 13
  2. 7
  3. 1
  4. 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een lijst is 4 → 9 → 16. Code bewaart node **link = &head->next;, verwijdert en geeft de eerste knoop 4 vrij en gebruikt daarna *link om 9 te verwijderen. Waarom is die tweede bewerking ongeldig en welke inkomende verwijzing moet die nu gebruiken?
  1. link wijst naar bestaande knoop 9; gebruik het adres van het volgende veld van 9.
  2. link wijst binnen bestaande knoop 16; gebruik het adres van het volgende veld van 16.
  3. link wijst binnen vrijgegeven knoop 4; gebruik het adres van de huidige koppointer.
  4. link is het adres van de huidige koppointer; gebruik het adres van de oude knoop 4.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een dubbel gekoppelde lijst is A ↔ B ↔ C, met A als eerste en C als laatste knoop. Verwijder B, voeg D achteraan toe en verwijder daarna C, waarbij je beide richtingen bijhoudt. Wat zijn de uiteindelijke laatste knoop en zijn voorganger?
  1. Laatste knoop D; voorganger A
  2. Laatste knoop C; voorganger A
  3. Laatste knoop D; voorganger B
  4. Laatste knoop A; voorganger D
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een programma dat postfixexpressies evalueert, handelt deling correct af maar keert de twee operanden van elke aftrekking om. Wat zijn voor “20 8 - 3 / 2 -” het juiste resultaat en het resultaat van het foutieve programma? Alle delingen zijn hier exacte geheeltallige delingen.
  1. Juist resultaat 6; foutief resultaat 2
  2. Juist resultaat 2; foutief resultaat −6
  3. Juist resultaat 2; foutief resultaat 6
  4. Juist resultaat −2; foutief resultaat 6
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een lege wachtrij krijgt eerst 22, dan 7 en daarna 15 toegevoegd. Wat geeft de eerste verwijdering terug?
  1. 15
  2. 7
  3. Een fout vanwege een lege wachtrij
  4. 22
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Voeg de verschillende sleutels 9, 16, 23, 30 in die volgorde in een lege binaire zoekboom in. Wat is de hoogte als die in verbindingen wordt gemeten?
  1. 4
  2. 3
  3. 2
  4. 1
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een binaire zoekboom heeft aanvankelijk alleen wortel 40. Bij een interface die de wortel teruggeeft, roept code insert(root->left, 20) aan en negeert de teruggegeven geheugenallocatie. Daarna slaat de code root = insert(root, 60) op en ten slotte root = erase(root, 40). Een geslaagde invoeging koppelt resultaten van kinderen intern; verwijderen verbindt behouden kinderen opnieuw. Wat blijft over?
  1. Wortel 60 met linkerkind 20; geen geheugenlek.
  2. Wortel 20 met rechterkind 60; geen geheugenlek.
  3. Wortel 40 met rechterkind 60; een geheugenlek voor knoop 20.
  4. Alleen wortel 60; een geheugenlek voor de gereserveerde knoop 20.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een tabel met open adressering en grootte 7 gebruikt lineair sonderen. Drie sleutels die naar plaats 5 hashen, bezetten plaatsen 5, 6 en 0. De sleutel op plaats 6 wordt verwijderd. Waarom moet plaats 6 een verwijderingsmarkering behouden in plaats van een ongebruikte plaats te worden?
  1. Een zoekopdracht naar de sleutel op plaats 0 moet voorbij plaats 6 doorgaan.
  2. Een zoekopdracht naar de sleutel op plaats 5 moet die naar plaats 6 verplaatsen.
  3. Een zoekopdracht moet de verwijderde sleutel als nog aanwezig behandelen.
  4. Een zoekopdracht moet het sonderen steeds opnieuw op plaats 0 beginnen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

72 Kernbegrippen, elk met een definitie

A

Aaneengesloten opslag
Aaneengesloten opslag plaatst arrayelementen naast elkaar in één reeks geheugenlocaties en ondersteunt directe toegang via een index en het doorlopen van nabijgelegen locaties.
Een array van gehele getallen vermijdt de verbindingspointers per knoop en de extra kosten van afzonderlijke geheugenallocaties bij een lijst van gehele getallen.
Aantal knopen in een boom
Het aantal knopen in een boom vind je door één voor de wortel op te tellen bij de aantallen van beide kinddeelbomen; voor een lege boom is het aantal nul.
Een wortel met twee bladeren als kinderen heeft 3 knopen, hoewel de hoogte gemeten in verbindingen slechts 1 is.
Abstract datatype
Een abstract datatype beschrijft een verzameling via de toegestane bewerkingen en het gedrag, onafhankelijk van de opslag die voor de implementatie wordt gekozen.
Een stapel behoudt hetzelfde gedrag bij toevoegen en verwijderen, ongeacht of zijn waarden in een array of in gekoppelde knopen staan.
Achteraan de wachtrij toevoegen
Achteraan de wachtrij toevoegen is een item aan de achterkant van een wachtrij plaatsen terwijl de voorste positie van al wachtende waarden behouden blijft.
Een ring met een teller schrijft het nieuwe item op (head + count) % CAP en verhoogt daarna het aantal.
Array van structs
Een array van structs slaat records van één structtype aaneengesloten op en ondersteunt directe selectie van een record via de numerieke index.
De expressie readings[3].temp_c bereikt de temperatuur in het vierde record van de array.

B

Basisgeval
Een basisgeval handelt de kleinste invoer van een recursieve functie af zonder nog een recursieve aanroep te doen en stopt zo de afdaling naar kleinere deelproblemen.
Een opruimfunctie voor een boom keert direct terug bij een lege deelboom voordat die kindpointers probeert te lezen.
Benoemde initialisator
Een benoemde initialisator noemt het veld of element dat een waarde ontvangt, zodat structvelden kunnen worden geïnitialiseerd zonder de declaratievolgorde te volgen.
Voor een struct waarin id vóór temp_c is gedeclareerd, noemt de initialisator {.temp_c = 21.5, .id = 7} de velden in omgekeerde declaratievolgorde.
Bezettingsgraad
De bezettingsgraad is het aantal opgeslagen sleutels gedeeld door het aantal buckets en meet de gemiddelde bezetting zonder de lengte van elke keten vast te leggen.
Twaalf sleutels in acht buckets geven een bezettingsgraad van 1,5, ook als sommige buckets leeg blijven.
Binaire boom
Een binaire boom is een gekoppelde structuur waarin elke knoop hoogstens twee kinderen heeft, onderscheiden als linkerkind en rechterkind.
Een knoop kan geen kinderen, slechts één kind of zowel een linker- als een rechterkind hebben.
Binaire zoekboom
Een binaire zoekboom ordent verschillende sleutels zo dat elke knoop in zijn hele linkerdeelboom kleinere sleutels en in zijn hele rechterdeelboom grotere sleutels heeft.
Door de gezochte sleutel met de huidige sleutel te vergelijken, kies je de enige deelboom die een overeenkomst kan bevatten.
Bitveld
Een bitveld is een structveld dat met een breedte in bits wordt gedeclareerd, waardoor de opgeslagen gehele waarden beperkt zijn en de opslagindeling door de implementatie wordt bepaald.
Een modusveld zonder teken van 3 bits slaat waarden van 0 tot en met 7 op en je kunt het adres ervan niet opvragen.
Blad
Een blad is een boomknoop zonder kinderen, zodat beide kindpointers nul zijn en de hoogte nul is wanneer hoogte verbindingen telt.
Bij het verwijderen van een blad wordt het geheugen ervan vrijgegeven en de inkomende kindverwijzing vervangen door een nulpointer.
Botsing
Een botsing ontstaat wanneer verschillende sleutels dezelfde bucketindex krijgen, waardoor een tabel hun afzonderlijke tabelitems moet behouden en onderscheiden.
Een zoekopdracht moet de sleutels binnen een gedeelde bucket vergelijken in plaats van aan te nemen dat het eerste tabelitem overeenkomt.
Botsingsafhandeling met ketens
Botsingsafhandeling met ketens handelt hashbotsingen af door bij elke bucket een gekoppelde lijst met tabelitems bij te houden en sleutels binnen de gekozen lijst te vergelijken.
Nieuwe woorditems kunnen vooraan worden ingevoegd omdat hun volgorde binnen een bucket geen invloed heeft op exact zoeken.
Bucket
Een bucket is een locatie die door een hashtabelindex wordt gekozen; bij botsingsafhandeling met ketens bewaart die de koppointer van een lijst met tabelitems die eraan zijn toegewezen.
Een tabel met tien buckets die ASCII-tekensommen gebruikt, plaatst “dog” in bucket 4 omdat de som 314 is.

C

Capaciteit
Capaciteit is het aantal elementen dat een gereserveerde array kan bevatten en kan groter zijn dan het aantal elementen dat momenteel wordt gebruikt.
Een array met capaciteit 16 en 10 gebruikte records kan nog zes records opnemen voordat opnieuw vergroten nodig is.

D

Deelboom
Een deelboom is een knoop samen met al zijn nakomelingen. Die vormen een kleinere boom waarop dezelfde zoek- en bijwerkregels kunnen werken.
De beperking voor de linkerdeelboom van een binaire zoekboom geldt zowel voor nakomelingen verderop als voor het directe linkerkind.
Diepe kopie
Een diepe kopie maakt naast een kopie van het buitenste record onafhankelijke kopieën van gegevens in eigendom waarnaar wordt gewezen, met opruimverantwoordelijkheid voor de nieuwe geheugenallocaties.
Door een naam naar nieuw gereserveerd geheugen te kopiëren, kan elk persoonsrecord zijn eigen naam vrijgeven zonder dubbele vrijgave.
Door de implementatie bepaald gedrag
Door de implementatie bepaald gedrag is een keuze die de C-implementatie moet documenteren, zodat overdraagbare code er niet van kan uitgaan dat elke implementatie dezelfde keuze maakt.
De gedocumenteerde volgorde waarin bitvelden worden opgeslagen, kan tussen implementaties van dezelfde C-declaratie verschillen.
Doorgifte op waarde
Doorgifte op waarde betekent dat een functieparameter een kopie van de opgegeven waarde ontvangt, zodat het wijzigen van die parameter de waarde van de aanroepende functie niet vervangt.
Een temperatuurwijziging in een recordparameter die op waarde is doorgegeven, laat het temperatuurveld van de aanroepende functie ongewijzigd.
Doorloop
Een doorloop bezoekt knopen door de verwijzingen van een structuur in een vastgelegde volgorde te volgen en stopt bij het passende einde of geval van een lege deelboom.
Een afzonderlijke pointer voor de lijstdoorloop beweegt via next-verwijzingen terwijl de eigenaar de oorspronkelijke koppointer behoudt.
Doorvallen
Doorvallen is het doorgaan van de uitvoering van het ene switch-geval naar het volgende omdat geen break, return of andere overdracht de uitvoering stopt.
Een geval dat een toestand afdrukt maar break weglaat, kan ook de actie uitvoeren die voor het volgende geval is geschreven.
Dubbel gekoppelde lijst
Een dubbel gekoppelde lijst bewaart in elke knoop zowel een verwijzing naar de opvolger als naar de voorganger. Daardoor kun je achteruit doorlopen en via beide buren verwijzingen herstellen.
Bij het verwijderen van een knoop in het midden worden de volgende verwijzing van de vorige knoop en de vorige verwijzing van de volgende knoop bijgewerkt.

E

Eigenaarschap
Eigenaarschap wijst de verantwoordelijkheid toe voor het vrijgeven van een geheugenallocatie, ook wanneer meerdere pointers tijdens de levensduur hetzelfde object kunnen bereiken.
Een woordknoop is eigenaar van zijn string, dus geeft het opruimen die string vrij vóór de knoop die het adres bevat.
Enkelvoudig gekoppelde lijst
Een enkelvoudig gekoppelde lijst is een reeks knopen die door pointers naar de volgende knoop zijn verbonden, met een koppointer aan het begin en een nulverwijzing aan het einde.
Als je een knoop vooraan invoegt, krijgt die de oude eerste knoop als opvolger en wordt de nieuwe knoop de eerste.
Enumeratie
Een enumeratie definieert benoemde geheeltallige constanten en een type voor waarden die bij die keuzes horen, zonder af te dwingen dat een waarde tot de benoemde verzameling behoort.
Een enumeratie voor waarschuwingen kan de niveaus laag, gemiddeld, hoog en kritiek benoemen, terwijl invoer nog steeds validatie vereist.
Enumeratieconstante
Een enumeratieconstante is een benoemde geheeltallige constante in een enumeratiedefinitie, met een expliciete waarde of een waarde die uit de voorganger wordt bepaald.
In enum state { IDLE = 3, RUNNING }; krijgt de enumeratieconstante RUNNING waarde 4.

F

FIFO
FIFO betekent eerst erin, eerst eruit en beschrijft de volgorde waarbij het vroegst opgeslagen item wordt behandeld vóór later toegevoegde items die nog wachten.
Na het toevoegen van A, B en C geeft een FIFO-verwijdering A terug vóór een van de latere letters.

G

Gegevensinhoud
De gegevensinhoud is de opgeslagen informatie die door de soort van een getagde waarde wordt gekozen, afzonderlijk bewaard van de tag die beschrijft hoe je die informatie interpreteert.
In een token van een rekenprogramma bevat de gegevensinhoud de numerieke operand of de operator die door de tag wordt aangeduid.
Getagde union
Een getagde union combineert gedeelde opslag voor gegevensinhoud met een afzonderlijke tag die aangeeft welk unionveld lezers moeten benaderen.
Een waarde met tag K_CHAR wordt afgedrukt met het tekenveld en een conversie voor tekenopmaak.

H

Hashfunctie
Een hashfunctie zet een sleutel deterministisch om in een getal dat tot een bucketindex wordt herleid, met als doel de verwachte sleutels gelijkmatig te verdelen.
Een hash op basis van de som van tekencodes stuurt herschikkingen zoals “cat” en “act” naar dezelfde bucket.
Herallocatie
Herallocatie wijzigt de grootte van een gereserveerd blok en kan het verplaatsen. Na succes moet de eigenaar het teruggegeven basisadres gebruiken.
Een tijdelijke resultaatpointer behoudt de oude records wanneer een groottewijziging van een array naar een grootte anders dan nul mislukt.
Herhashen
Herhashen berekent de plaatsing van opgeslagen sleutels opnieuw voor een nieuw aantal buckets en verplaatst of herkoppelt tabelitems zodat latere zoekopdrachten de juiste locaties gebruiken.
Bij het verdubbelen van de bucketarray moet je de hash van elke sleutel met het nieuwe aantal herleiden voordat je de sleutel plaatst.
Hoogte
De hoogte van een niet-lege boom is het aantal verbindingen op het langste pad van wortel naar blad; de hier gebruikte hoogte van een lege boom is −1.
Een keten met vijf knopen heeft vier verbindingen op zijn langste pad, dus is de hoogte 4.

I

Inkomende verwijzing
Een inkomende verwijzing is de pointer die een knoop bereikt, de koppointer van de lijst of het volgende veld van een voorganger, en die moet veranderen als die knoop wordt overgeslagen.
Het verwijderen van de eerste knoop verandert de koppointer omdat er geen voorganger is die de inkomende verwijzing levert.
Inordedoorloop
Een inordedoorloop bezoekt elke linkerdeelboom vóór zijn wortel en daarna de rechterdeelboom, wat voor een binaire zoekboom gesorteerde sleutels oplevert.
Een binaire zoekboom met woordtellingen in inorde afdrukken levert de opgeslagen woorden op in de volgorde van de tekstvergelijkingen.
Inordeopvolger
De inordeopvolger van een knoop met een rechterdeelboom is de kleinste sleutel in die deelboom en wordt bij verwijdering gebruikt om de sleutel van een knoop met twee kinderen te vervangen.
Volg het rechterkind en daarna linkerverwijzingen om de vervangende sleutel te vinden bij het verwijderen van een knoop met twee kinderen.
Invoegen met behoud van sortering
Invoegen met behoud van sortering plaatst een nieuwe waarde waar die de bestaande volgorde behoudt en verbindt de nieuwe knoop tussen zijn voorganger en opvolger.
Een lus voor een oplopende lijst stopt vóór de eerste waarde die niet kleiner is dan de nieuwe waarde.

K

Knoop
Een knoop is een record met gegevens en de pointers die dat record verbinden met andere delen van een gekoppelde structuur.
Een lijstknoop bevat één geheel getal en een pointer naar zijn opvolger; een boomknoop heeft daarentegen twee kindpointers.
Koppointer
De koppointer wijst naar de eerste knoop van een gekoppelde reeks; een nulkoppointer stelt een reeks zonder knopen voor.
Bij het verwijderen van de eerste lijstknoop moet de koppointer van de eigenaar worden bijgewerkt voordat het geheugen van de oude knoop wordt vrijgegeven.

L

LIFO
LIFO betekent laatst erin, eerst eruit en beschrijft de volgorde waarbij het nieuwste opgeslagen item wordt behandeld vóór alle eerdere items die nog aanwezig zijn.
Nadat een stapel 4, 7 en 1 ontvangt, geven de eerste twee popbewerkingen 1 en 7 terug, zodat 4 overblijft.
Lineair sonderen
Lineair sonderen lost een botsing op door opeenvolgende arrayplaatsen met terugloop te proberen en gebruikt dezelfde volgorde voor invoegen en opzoeken.
Een sondeeractie die op de laatste plaats begint, gaat verder op plaats nul wanneer nog een bezette positie moet worden gecontroleerd.
Loskoppelen
Loskoppelen verwijdert een knoop uit een structuur door de verwijzingen eromheen opnieuw te verbinden voordat eventueel geheugen van de knoop in eigendom wordt vrijgegeven.
Door de next van een voorganger om te leiden naar de opvolger van de verwijderde knoop, blijft de overgebleven keten bereikbaar.

O

Onderloop
Onderloop ontstaat wanneer een programma een item uit een lege verzameling probeert te verwijderen en vereist een foutresultaat voordat ongeldige toegang plaatsvindt.
Een pop van een arraystapel moet op een aantal van nul controleren voordat het aantal wordt verlaagd en de array wordt gelezen.
Ondiepe kopie
Een ondiepe kopie dupliceert de veldwaarden van een record, inclusief pointeradressen, terwijl afzonderlijk gereserveerde gegevens waarnaar wordt gewezen, gedeeld blijven.
Na het kopiëren van een persoonsrecord bereiken beide naampointers nog dezelfde string, tenzij geheugen voor een nieuwe kopie wordt gereserveerd.
Ongeldige pointer naar een niet meer bestaand object
Een ongeldige pointer naar een niet meer bestaand object verwijst naar een object waarvan de levensduur is geëindigd, zodat derefereren geen geldige toegang meer tot dat object geeft.
Als je het adres van een lokaal record teruggeeft, krijgt de aanroepende functie een pointer naar een object dat niet meer bestaat.
Open adressering
Open adressering slaat tabelitems direct op arrayplaatsen op en lost botsingen op door een vastgelegde reeks alternatieve plaatsen te doorzoeken.
Een zoekopdracht kan meerdere plaatsen bekijken, ook al begint die bij één hashindex die uit een sleutel is afgeleid.
Opvulling
Opvulling bestaat uit ongebruikte bytes die een implementatie tussen structvelden of na het laatste veld kan plaatsen om aan de eisen voor de geheugenindeling te voldoen.
Een bytevergelijking kan verschillende opvulling detecteren, ook als twee records gelijke veldwaarden bevatten.

P

Pijloperator
De pijloperator selecteert een struct- of unionveld via een pointer en combineert derefereren met veldselectie.
Voor een pointer p naar een bestaand record bereikt p->temp_c hetzelfde veld als (*p).temp_c.
Pointer naar een const-gekwalificeerd object
Een pointer naar een const-gekwalificeerd object geeft toegang tot een object dat niet via die pointer kan worden gewijzigd, terwijl de pointer zelf nog kan veranderen.
Een afdrukfunctie kan via const reading *p lezen maar kan niet aan p->id toewijzen.
Pointer naar een pointer
Een pointer naar een pointer bevat het adres van een pointerobject, zodat een functie die pointer via het extra niveau kan lezen of vervangen.
Een node **link kan van het adres van de koppointer naar het adres van het veld next van een knoop bewegen.
Postfixexpressie
Een postfixexpressie schrijft elke operator na zijn operanden, zodat een stapel tussenwaarden kan bewaren totdat de operator ze combineert.
Bij “9 4 -” is de eerst verwijderde waarde de rechteroperand 4 en is het resultaat 5.
Postordedoorloop
Een postordedoorloop bezoekt de linker- en rechterdeelboom vóór hun wortel, zodat bij het opruimen kinderen vóór hun ouder kunnen worden vrijgegeven.
Een functie die een boom vrijgeeft, leest kindverwijzingen terwijl de ouder nog bestaat en geeft die ouder daarna als laatste vrij.
Preordedoorloop
Een preordedoorloop bezoekt de wortel van een deelboom vóór de linker- en rechterdeelboom en plaatst elke ouder vóór zijn nakomelingen.
Uitvoer in preorde begint met de wortelsleutel, ook als die sleutel noch de kleinste noch de grootste is.

R

Ringbuffer
Een ringbuffer hergebruikt een vaste array door posities van de laatste plaats naar de eerste te laten teruglopen in plaats van resterende items te verschuiven.
Na een verwijdering vooraan kan een latere invoeging de vrijgekomen plaats hergebruiken zodra de index terugloopt.

S

Staartpointer
De staartpointer wijst naar de laatste knoop van een gekoppelde reeks en wordt bijgehouden zodat achteraan invoegen zonder volledige doorloop kan.
Een gekoppelde wachtrij zet zowel de kop- als de staartpointer op nul wanneer de enige knoop wordt verwijderd en vrijgegeven.
Stapel
Een stapel is een verzameling waarbij invoegen en verwijderen de bovenkant gebruiken, zodat de laatst ingevoegde waarde de volgende is die wordt verwijderd.
Een haakjescontroleprogramma bewaart het meest recente ongepaarde openingsteken boven op zijn stapel.
Stringgelijkheid
Stringgelijkheid betekent dat twee strings die op een nulteken eindigen overeenkomende tekenreeksen bevatten, ongeacht of hun opslagadressen verschillen.
Een zoekopdracht in een woordtabel gebruikt strcmp(e->key, key) == 0 om gelijke tekst in afzonderlijke geheugenallocaties te herkennen.
Struct
Een struct is een C-type dat benoemde velden, die verschillende typen kunnen hebben, in één record groepeert met afzonderlijke opslag voor elk veld.
Een temperatuurrecord combineert een geheeltallige sensoridentifier en een temperatuur in één structobject.
Structtag
Een structtag benoemt een structuurtype na het sleutelwoord struct en kan het type binnen zijn eigen definitie aanduiden.
Met de tag in struct node kan de definitie een zelfverwijzing struct node *next declareren.
switch-instructie
Een switch-instructie kiest een begingev al op basis van een geheeltallige expressie en gaat verder totdat de besturing de instructie of functie verlaat.
Een switch op een getagde waarde kiest de uitvoerbewerking voor de soort gegevensinhoud die momenteel is opgeslagen.

T

Tabelitem
Een tabelitem is een opgeslagen hashtabelrecord met een sleutel en bijbehorende gegevens, plus een volgende pointer wanneer botsingsafhandeling met ketens tabelitems verbindt.
Een woordtelitem is eigenaar van een sleutelstring, bewaart het aantal van het woord en verwijst naar het volgende tabelitem in zijn bucket.
typedef
Een typedef-declaratie introduceert een alias voor een bestaand type, zodat code een kortere naam kan gebruiken zonder een afzonderlijk type te maken.
Na typedef struct reading reading; kan een declaratie reading *p gebruiken voor een pointer naar die struct.

U

Uitlijning
Uitlijning is een eis aan de adresplaatsing van een objecttype die tussenruimtes tussen velden of aan het einde van een struct kan veroorzaken.
Een geheelgetalveld kan na een tussenruimte achter een tekenveld beginnen zodat het adres geschikt is uitgelijnd.
Union
Een union is een C-type waarvan de velden opslag delen, zodat die ruimte één opgeslagen alternatief uit verschillende veldtypen kan bevatten.
In een union met een geheelgetalveld en een double-veld vervangt het schrijven van de double de eerdere geheeltallige gegevensinhoud in plaats van twee waarden te behouden.

V

Veld
Een veld is een benoemd onderdeel van een struct of union dat je uit een object met een punt of via een pointer met een pijl selecteert.
De expressie r.temp_c selecteert het temperatuurveld van de meting die in r is opgeslagen.
Vergelijkingsfunctie
Een vergelijkingsfunctie geeft de onderlinge volgorde van twee waarden aan met een negatief resultaat, nul voor gelijkheid of een positief resultaat.
Een temperatuurvergelijkingsfunctie geeft na het vergelijken van waarden een teken terug en voorkomt zo het afkappen van een klein verschil.
Verwijderingsmarkering
Een verwijderingsmarkering markeert een verwijderde plaats bij open adressering zonder die ongebruikt te laten lijken, zodat zoeken naar latere botsende tabelitems kan doorgaan.
Een zoekopdracht gaat voorbij een verwijderingsmarkering in plaats van te stoppen vóór een passende sleutel die verderop in het sondeerpad is opgeslagen.
Vooraan invoegen
Vooraan invoegen is een item vóór het huidige eerste item van een reeks plaatsen, waardoor een ander item vanaf de koppointer wordt bereikt.
Als je 1, dan 2 en dan 3 vooraan invoegt, ontstaat een lijst waarvan de doorloop eerst 3, dan 2 en daarna 1 bezoekt.
Vooraan uit de wachtrij verwijderen
Vooraan uit de wachtrij verwijderen is het voorste item uit een wachtrij halen, waarbij een lege invoer wordt gemeld voordat de toestand verandert of een waarde wordt gelezen.
Een circulaire verwijdering leest de voorkant, verplaatst de kopindex met terugloop en verlaagt het aantal.

W

Wachtrij
Een wachtrij is een verzameling die achteraan toevoegt en vooraan verwijdert, zodat de volgorde van binnenkomst van nog wachtende waarden behouden blijft.
Een gekoppelde wachtrij voegt een nieuwe knoop toe na de laatste knoop en verwijdert de knoop die momenteel door de koppointer wordt bereikt.
Wortel
De wortel is de knoop waar een boom of deelboom begint, bereikbaar via de wortelpointer van de eigenaar of een kindverwijzing van een ouder.
Bij het vervangen van een verwijderde wortel met één kind moet het overblijvende kind als nieuwe wortel van de boom worden opgeslagen.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag