Kihagyás

7. gyakorlat

Memória és pointerek (part 2)

(Az előző gyakorlat folytatása)

Figyeljünk a zárójelekre

#include <stdio.h>

int main()
{
    int szamok[] = {1, 2, 3, 4, 5};

    int* p = szamok;

    for (int i = 0; i < 5; i++)
    {
        // 1)
        printf("%d\n", *p + i);

        // 2)
        printf("%d\n", *(p + i));
    }

    return 0;
}

Különbség:

Az 1. esetben előbb kiolvassuk a pointer értékét (vagyis a tömb 0. elemét) és utána hozzáadjuk a ciklusváltozó értékét

A 2. esetben előbb léptetjük a pointert és utána olvassuk ki az értéket (vagyis végighaladunk a tömbön)

Típuskonverzió pointerekre

Nem összekeverendő a kettő!

1) Pointerváltozó típusának konverziója

A csillagnak a zárójelen belül kell lennie!

típus1* p1;
típus2* p2 = (típus2*) p1;

2) A mutatott memóriaterület (az eredeti változó) típusának konverziója

Fel kell oldani hozzá a pointert => a csillag a zárójelen kívülre kerül

típus1* p1;
típus2 változó = (típus2) *p1;

Felhasználói típusok és pointerek

  • Felhasználói (user-defined) típusokra is lehet pointert állítani
  • Használatuk ugyanaz
  • A címfeloldás is hasonló, de van rá külön operátor is
    • Nyíl (->) operátor: Adattag elérése egy pointeren keresztül

Általánosan:

struct T
{
    típus adat;
};

struct T s = ...;
struct T* p = &s;

típus a = (*p).adat;
típus b = p->adat;

Példa:

#include <stdio.h>

struct Ido
{
    int ora;
    int perc;
};

int main()
{
    struct Ido i = {2, 10};
    struct Ido* p = &i;

    // Ugyanazt csinálja mindkettő
    int a = (*p).ora;
    printf("a = %d\n", a);

    int b = p->ora;
    printf("b = %d\n", b);

    return 0;
}

Értékadás pointeren keresztül példa:

#include <stdio.h>

struct T
{
    int a;
};

int main()
{
    struct T szamok[2];
    struct T* p = szamok;    

    for (int i = 0; i < 2; i++)
    {
        p->a = i;
        p++;
    }

    for (int i = 0; i < 2; i++)
    {
        printf("%d\n", szamok[i].a);
    }    

    return 0;
}

Típusmódosító pointerek

A const a legfontosabb, így ez van részletezve

Deklaráció Mit jelent?
const type* p vagy type const* p A hivatkozott adat a pointeren keresztül nem módosítható
type* const p A pointer értéke nem módosítható
const type* const p Sem a pointer értéke, sem a hivatkozott adat nem módosítható

Pointer, mint függvényargumentum

  • Egy függvénynek lehet (akár többszörös) pointer argumentuma
  • Ha el akarjuk kerülni a változó megváltozását a függvényben, akkor érdemes használni a const kulcsszót
  • Ha egy olyan adatot akarunk megváltoztatni egy függvényben, ami a hívó fél oldalán van (lokális), akkor a memóriacímét adjuk meg a hívott függvénynek (lásd. példa)
  • A függvényekben az argumentumként kapott pointereket ugyanúgy kell kezelni, mint a lokális pointereket
  • Általánosságban a beépített típusoknál nem éri meg pointereket használni, de a nagyobb felhasználói objektumok (struct, union, tömb) esetén igen
    • Oka: A függvény argumentumok lemásolódnak a függvényen belül lokális változóként
    • Nagyobb objektumok esetén a pointerek "költséghatékonyabbak". Pl. nem 100 bájt másolódik le, hanem csak 8 bájt
  • Vigyázat: Függvényben lokálisan definiált változóra mutató pointer visszaadása segfaultot okozhat!

Példa:

#include <stdio.h>

void swap(int* pA, int* pB)
{
    int h = *pA;
    *pA = *pB;
    *pB = h;
}

void swap2(int* pA, int* pB)
{
    *pA = *pA + *pB;
    *pB = *pA - *pB;
    *pA = *pA - *pB;
}

int main()
{
    int a = 5;
    int b = 9;

    printf("Original:\na = %d\nb = %d\n", a, b);

    swap2(&a, &b);

    printf("After swap:\na = %d\nb = %d\n", a, b);

    return 0;
}

Önhivatkozó struktúrák

  • Önhivatkozás: Az összetett típus (pl. struct) tartalmazza önmagának egy példányát, vagyis egy arra mutató változót
  • Avagy: Olyan struktúra, amely tartalmaz 1 vagy több pointert, amely egy ugyanolyan típusú struktúrára mutat
  • Unionnál is lehet ilyet, de nem sok haszna van
  • Structoknál használatos pl.:
    • bináris fa (left és right)
    • láncolt lista (prev és next)
  • Tetszőleges mennyiségű másik ugyanolyan típusú struktúrára mutató adattagot tartalmazhat (általában 1-2 szokott lenni)

Áltlánosan:

struct Node
{
    típus adat;
    struct Node* node1;
    ...
    struct Node* node1;
};

Példa:

struct Self
{
    int data;
    struct Self* ptr;
};

Linked List in C video

Tree in C video

Az általános mutató

  • Általános pointer: void*
  • Akkor használjuk, amikor a hitkozott adat típusa érdektelen
  • Pl.: Memória bitenkénti másolása

Mutatótömbök

Egy tömb lehet pointer típusú is

Általánosan:

type* p[size];

Példa feladat pointer tömbbel:

#include <stdio.h>

void printToConsole(int** p)
{
    for (int i = 0; i < 3; i++)
    {
        printf("%d\n", **(p + i));
    }
}

void modify(int** p)
{
    for (int i = 0; i < 3; i++)
    {
        printf("Új érték: ");
        scanf("%d", *(p + i));
    }
}

int main()
{
    int a = 1;
    int b = 2;
    int c = 3;

    int* szamok[] = {&a, &b, &c};

    printToConsole(szamok);
    modify(szamok);
    printToConsole(szamok);

    return 0;
}