C-keele struktuurid ja pointerid: täielik juhend algajatele ja edasijõudnutele

1. Sissejuhatus

C-keelt kasutatakse laialdaselt süsteemiarenduses ja manustatud programmides. Nende seas on „struktuurid” ja „pointerid” hädavajalikud elemendid, mis võimaldavad tõhusat andmehaldust ja mäluga manipuleerimist. Käesolevas artiklis selgitame neid mõisteid põhitasemest kuni rakenduseni.

Seda artiklit lugedes mõistad struktuuride ja pointerite rolli C-keeles ning õpid nende kasutamist praktiliste koodinäidete abil. Ka algajatele on see kergesti arusaadav, kuna liigume edasi konkreetsete näidete toel.

2. Struktuuride ja pointerite põhitõed

Mis on struktuur?

Struktuur on andmestruktuur, mis koondab mitu erinevat tüüpi andmevälja üheks tervikuks. Näiteks inimese info (nimi, vanus, pikkus jne) saab hallata ühe üksusena.

Alljärgnev koodinäide näitab struktuuri põhilist definitsiooni ja kasutust.

#include <stdio.h>

// Struktuuri definitsioon
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1;  // Struktuuri muutuja deklareerimine

    // Andmete määramine
    strcpy(person1.name, "Taro");
    person1.age = 20;
    person1.height = 170.5;

    // Andmete kuvamine
    printf("Nimi: %sn", person1.name);
    printf("Vanus: %dn", person1.age);
    printf("Pikkus: %.1f cmn", person1.height);

    return 0;
}

Selles näites defineeritakse struktuur Person, mis ühendab kolm erinevat tüüpi andmevälja ühte struktuuri. Nii saab seotud andmeid hallata ühtselt.

Mis on pointer?

Pointer on muutuja, mis salvestab teise muutuja mäluaadressi. Seda kasutatakse programmi sees dünaamilise mälu käsitlemiseks. Allpool on pointeri põhiline näide.

#include <stdio.h>

int main() {
    int a = 10;
    int *p;  // Pointeri deklareerimine

    p = &a;  // Pointerile omistatakse muutuja a aadress

    printf("Muutuja a väärtus: %dn", a);
    printf("Pointer p viidatav väärtus: %dn", *p);

    return 0;
}

Selles näites kasutatakse pointermuutujat p, et pääseda juurde muutuja a väärtusele. Pointerid on võimsad mäluhalduse tööriistad, kuid valesti kasutades võivad põhjustada vigu või mälulekkeid.

Struktuuride ja pointerite seos

Kombineerides struktuurid ja pointerid, on võimalik saavutada paindlikum andmete haldus. Sellest räägime täpsemalt järgmistes osades, kuid põhitõdede mõistmine aitab edasi liikuda rakenduste poole.

3. Mis on struktuur?

Struktuuri põhialused

Struktuur on andmestruktuur, mis ühendab mitu erinevat tüüpi andmevälja üheks tervikuks. C-keeles kasutatakse seda sageli seotud info rühmitamiseks ja andmehalduse lihtsustamiseks.

Alljärgnevalt on näidatud struktuuri definitsioon.

struct Person {
    char name[50];
    int age;
    float height;
};

Selles näites defineeritakse Person struktuur, mis sisaldab järgmisi kolme välja:

  • name: salvestab nime stringina (massiivina)
  • age: salvestab vanuse täisarvuna
  • height: salvestab pikkuse ujukomaarvuna

Struktuuri definitsioon on „tüübi” deklaratsioon, mida kasutatakse konkreetsete muutujate loomiseks.

Struktuurimuutuja deklareerimine ja kasutamine

Struktuuri kasutamiseks tuleb kõigepealt muutuja deklareerida. Näiteks:

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1;  // Struktuuri muutuja deklareerimine

    // Andmete määramine
    strcpy(person1.name, "Taro");
    person1.age = 20;
    person1.height = 170.5;

    // Andmete kuvamine
    printf("Nimi: %sn", person1.name);
    printf("Vanus: %dn", person1.age);
    printf("Pikkus: %.1f cmn", person1.height);

    return 0;
}

Selles koodis deklareeritakse struktuurimuutuja person1 ja määratakse selle liikmetele väärtused.

Struktuuri initsialiseerimine

Struktuurimuutujat saab ka deklareerimise hetkel initsialiseerida.

struct Person person2 = {"Hanako", 25, 160.0};

Nii saab väärtused korraga määrata ja koodi lihtsustada.

Struktuurimassiiv

Mitme andmeüksuse haldamiseks saab kasutada struktuuride massiivi.

struct Person people[2] = {
    {"Taro", 20, 170.5},
    {"Hanako", 25, 160.0}
};

for (int i = 0; i < 2; i++) {
    printf("Nimi: %s, Vanus: %d, Pikkus: %.1f cmn", people[i].name, people[i].age, people[i].height);
}

Selles näites hallatakse kahe inimese andmeid massiivina ja töödeldakse neid tsükliga.

Struktuuri edastamine funktsioonile

Struktuuri saab edastada ka funktsioonile. Näiteks:

void printPerson(struct Person p) {
    printf("Nimi: %s, Vanus: %d, Pikkus: %.1f cmn", p.name, p.age, p.height);
}

See funktsioon võtab argumendina struktuuri ja kuvab selle info.

Kokkuvõte

Struktuurid on väga kasulik andmetüüp, et hallata seotud andmeid terviklikult. Põhikasutuse omandamine muudab andmete korrastamise ja neile ligipääsu tõhusamaks.

4. Pointerite põhitõed

Mis on pointer?

Pointer on C-keele võimas vahend, mis võimaldab muutuja mäluaadressi otsest käsitlemist. Selles osas selgitame pointerite põhimõtteid, deklareerimist, kasutamist ja konkreetseid näiteid.

Pointeri deklareerimine ja initsialiseerimine

Pointer deklareeritakse, lisades tüübi ette *.

int a = 10;     // Tavaline muutuja
int *p;         // Pointeri deklareerimine
p = &a;         // p viitab muutuja a aadressile
  • *p: väljendab pointeri viidatava aadressi väärtust (indirektne viide)
  • &a: võtab muutuja a aadressi (aadressioperaator)

Väärtuste muutmine pointeri abil

Järgmine näide näitab väärtuste muutmist pointeri kaudu.

#include <stdio.h>

int main() {
    int a = 10;      
    int *p = &a;     

    printf("a väärtus: %dn", a);           
    printf("a aadress: %pn", &a);   
    printf("p väärtus (aadress): %pn", p); 
    printf("p viidatav väärtus: %dn", *p);     

    *p = 20;  
    printf("a uus väärtus: %dn", a);  

    return 0;
}

Selles koodis muudetakse muutuja a väärtust pointeri p kaudu.

Massiiv ja pointerid

Ka massiivi elementidele saab ligi pointerite abil.

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *p = arr; // Viitab massiivi esimesele elemendile

    printf("1. element: %dn", *p);     
    printf("2. element: %dn", *(p+1)); 
    printf("3. element: %dn", *(p+2)); 

    return 0;
}

Selles näites kasutatakse pointerit p massiivi elementidele ligipääsuks.

Kokkuvõte

Pointerid on C-keeles väga olulised. Need võimaldavad tõhusat mäluhaldust ja paindlikku programmide ülesehitust. Järgmisena vaatleme „5. Struktuuride ja pointerite kombinatsiooni”.

5. Struktuuride ja pointerite kombinatsioon

Struktuuripointeri põhialused

Kombineerides struktuurid ja pointerid, saab andmeid hallata veelgi paindlikumalt ja tõhusamalt. Selles osas selgitame struktuuripointerite põhikasutust ja näitame rakendusi.

Allpool on lihtne näide struktuuripointerist.

#include <stdio.h>
#include <string.h>

// Struktuuri definitsioon
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person person1 = {"Taro", 20, 170.5}; 
    struct Person *p = &person1;                

    // Ligipääs andmetele pointeri abil
    printf("Nimi: %sn", p->name);
    printf("Vanus: %dn", p->age);
    printf("Pikkus: %.1f cmn", p->height);

    // Andmete muutmine pointeri kaudu
    p->age = 25;
    printf("Muudetud vanus: %dn", p->age);

    return 0;
}

Koostöö dünaamilise mäluga

Struktuuripointerid sobivad hästi dünaamilise mäluhaldusega, mis on mugav suure hulga andmete töötlemisel.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person *p = (struct Person *)malloc(sizeof(struct Person));

    strcpy(p->name, "Hanako");
    p->age = 22;
    p->height = 160.0;

    printf("Nimi: %sn", p->name);
    printf("Vanus: %dn", p->age);
    printf("Pikkus: %.1f cmn", p->height);

    free(p);

    return 0;
}

Massiivid ja struktuuripointerid

Kombineerides massiivid ja struktuuripointerid, saab hallata mitut üksust korraga.

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    struct Person people[2] = {{"Taro", 20, 170.5}, {"Hanako", 25, 160.0}};
    struct Person *p = people; 

    for (int i = 0; i < 2; i++) {
        printf("Nimi: %sn", (p + i)->name);
        printf("Vanus: %dn", (p + i)->age);
        printf("Pikkus: %.1f cmn", (p + i)->height);
    }

    return 0;
}

Kokkuvõte

Struktuuride ja pointerite ühendamine muudab andmehalduse efektiivsemaks ja annab paindlikkust mälutöötluses. Selles osas käsitlesime põhilisi kasutusviise ja dünaamilist mäluhaldust.

6. Funktsioonide ja struktuuripointerite koostöö

Struktuuri edastamise viisid

Struktuuri saab funktsioonile edastada kahel viisil:

  1. Väärtusena – tehakse koopia, mis võib olla ebaefektiivne suurte andmete puhul.
  2. Viitena (pointeriga) – edastatakse aadress, mis säästab mälu ja võimaldab originaalandmeid otse muuta.

Väärtuse edastamise näide

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

void printPerson(struct Person p) {
    printf("Nimi: %sn", p.name);
    printf("Vanus: %dn", p.age);
}

int main() {
    struct Person person1 = {"Taro", 20};
    printPerson(person1);

    return 0;
}

Viitena (pointeriga) edastamise näide

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

void updateAge(struct Person *p) {
    p->age += 1;
}

void printPerson(const struct Person *p) {
    printf("Nimi: %sn", p->name);
    printf("Vanus: %dn", p->age);
}

int main() {
    struct Person person1 = {"Hanako", 25};

    printf("Enne muutmist:n");
    printPerson(&person1);

    updateAge(&person1);

    printf("Pärast muutmist:n");
    printPerson(&person1);

    return 0;
}

7. Pointerite kasutamine struktuuri sees

Eelis

Struktuuri sees olevad pointerid võimaldavad paindlikku mäluhaldust ja keerukamate andmestruktuuride loomist.

Näide: dünaamiline stringihaldus

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person {
    char *name;
    int age;
};

void setPerson(struct Person *p, const char *name, int age) {
    p->name = (char *)malloc(strlen(name) + 1);
    strcpy(p->name, name);
    p->age = age;
}

void printPerson(const struct Person *p) {
    printf("Nimi: %sn", p->name);
    printf("Vanus: %dn", p->age);
}

void freePerson(struct Person *p) {
    free(p->name);
}

8. Praktiline näide: ühendatud loend

Ühendatud loend on dünaamiline andmestruktuur, kus iga element viitab järgmisele. C-keeles rakendatakse seda struktuuride ja pointerite abil.

struct Node {
    int data;
    struct Node *next;
};

Nii saab dünaamiliselt elemente lisada ja eemaldada.

9. Levinud vead ja silumine

  • Initsialiseerimata pointerid
  • Mälulekked
  • Dangling pointerid
  • NULL-pointeri kasutamine

Need vead võivad põhjustada tõsiseid probleeme, seetõttu tuleb pointeritega ettevaatlik olla ning kasutada tööriistu nagu valgrind ja gdb.

10. Kokkuvõte

Õpitu kordamine

  • Struktuurid – seotud andmete rühmitamiseks
  • Pointerid – mäluaadresside otsekäsitlemiseks
  • Nende kombinatsioon – efektiivseks andmehalduseks
  • Funktsioonidega kasutamine – andmete jagamiseks ja muutmiseks
  • Praktilised rakendused – nagu ühendatud loendid

Edasised sammud

  • Kohanda näidiskoodi oma projektide jaoks
  • Õpi keerukamaid andmestruktuure (puud, graafid)
  • Kombineeri algoritmidega (otsing, sorteerimine)
  • Harjuta silumist ja optimeerimist

Lõppsõna

C-keele struktuurid ja pointerid on olulised mõisted, mis võimaldavad luua paindlikke ja tõhusaid programme. Käesolev artikkel aitas sul õppida nende põhimõtteid ja rakendusi praktiliste näidete abil. Kasuta neid teadmisi järgmiste sammude astumiseks ja oma programmeerimisoskuste arendamiseks!