Hirdetés

Keresés

Új hozzászólás Aktív témák

  • kingabo

    őstag

    válasz Cicero #2297 üzenetére

    Hát ez "picit hosszú lett. Remélem érthető. Ha kérdésed van kérdezz nyugodtan, ha a többiek valami hibát találnak szóljanak és modikkal javíttatom.
    A pointer az egy mutató a memória egy pontjára, vagyis egy memória címet tárol (egy int-et). Minden pointernek meg kell mondani, hogy milyen típusra mutat, hogy a fordító tudja, mi van ott: pl char vs float, mekkora (1byte vs 4byte). A pointert minden esetben használat elött inicializálni kell! (egyszerűbben: értéket kell neki adni), mivel a pointer "kezdeti értéke" nem NULL (==megegyezés szerint nem mutat sehova sem /semmire sem), hanem valamilyen memória szemét, ha ezt a szemetet akarod felhasználni arra, hogy elérd a pointer által mutatot memória helyet elég csúnya dolgok történhetnek ("jobb esetben" az oprendszer nem hagyja, "rosszabb esetben" felhasználhatják a program eltérítésére). Pointer deklarálása: a "mutatandó" érték típusa és a változó neve elé egy *:
    int * p;

    Érték adás:
    - NULL:
    int *p = NULL;
    - Egy másik pointer(1), vagy pl tömb esetén annak egy tetszőleges eleme(2) (részletesen lentebb)
    int *pt,*pt1,t2[]={1,2,3,5};
    pt = t2; //(2)
    pt1 = pt; //(1)
    pt = pt1 + 3; //(2) a t2 4. elemére mutat (1+3)

    - Egy változó címe: a váltózó elé a '&' jelet kell írni
    int *p, i = 5;
    p = &i;

    - Dinamikusan foglalunk le memóriát (ld. késöbb)

    Pointer értéke vs pointer ÁLTAL MUTATOTT memória hely értéke: ezek keverése miatt lehet igen csúnya dolgokat (==hibákat) művelni. A pointer értéke egy memória cím, ami (jó esetben) egy megfelelő értékre mutat. A pointer által mutatott hely értéke az elején részletezett, a pointer deklarácójakor megadott típusú érték. Ezt elérni a pointer neve elé tett *-al lehet. Pl az előző részből az utolsó példában a 'p' pointer értéke az 'i' változó CÍME a memóriában. Az általa mutatott ÉRTÉK, vagyis a *p értéke 5 (*p = i = 5).
    A gondot az okozhatja, hogy "véletlenül" lemarad a 'p' elől a *, így a pointer kap értéket, nem az általa mutatott memória cím. Vagyis ha az 'i'-nek értéket akarunk adni a 'p' pointeren keresztül akkor így kell *p = 6; // ==i;. A következő kód hibás, csak szemléltető ellenpélda: p = 6;, ekkor az 'i' értéke nem változik, a 'p' értéke egy, a programunktól független (nem a program egy változójára, vagy általa dinamikusan lefoglalt memória területre mutat), a 6-os memória címre fog mutatni, ahol biztosan semmi keresni valónk nincs és valószínűleg az oprendszer adatai vannak, vagyis semmi jóra ne számítsunk. (akit érdekel picit részletesebben itt az utolsó 2 sor)

    Egy dimenziós tömb: avagy a vektor. Egy 'n' elemű vektor, n db a memóriában egymás után lévő érték. (mint linalgból) Szemléletesen pl 3 elemű tömb (az '_' helyére kerülnek az értékek: |_|_|_|
    Pl: int t[]={1,2,3,5};
    A dolog szépsége az, hogy a 't' is egy pointer lesz, mely a tömb első elemének memóriabeli címére fog mutani.
    A tömb elemeinek elérése
    - a pointer eltolása, vagyis a pointer értékéhez hozzáadva, vagy levonva belőle. Fontos: nincs semmilyen védelem, hogy a pointer ne tudjon a tömbről "lemenni". Célszerűbb (főleg az elején), inkább a másik megoldást alkalmazni, kisebb a hiba lehetősége.
    pl (a fenti t-t felhasználva): int *masodikElem = t+1; // a t 2. elemére mutat (1+1)
    int harmadikElemErteke = *(t+2); //t eltolva 2-vel, vagyis a 3
    . elem címére mutat és vesszük a címen lévő értéket

    - a [] operátort használva. a változó után írva, a zárójelek közé egész számot, vagy egész típusú változót írva.Fontos: a megszokottól eltérően, c-ben az indexelés 0-tól n-1-ig történik! Vagyis pl, a zárójelek közé 0-át írva az általunk megszokott első elemet, n-1-et írva az általunk megszokott utolsó elemet kapjuk. További megjegyzendő tény, hogy nincs semmi védelem az ellen, hogy ne "menjünk le" a tombről. Más szóval ha a c-s indexeléssel nézve a -1-edik vagy az n-edik elemét akarjuk elérni a tömbnek (ami nem létezik), akkor a memóriában a tömb elött és utána lévő értékeket fogjuk elérni, amiről már a legelején megemlítettem, hogy mindent csak jót nem jelent! (Érdekesség: a fordító ezesetben átfordítja az előző változatra. pl t[2]-ből *(t+2) lesz)

    Dinamikus helyfoglalás: adott a probléma: fordítási időben meg kell adnunk, hogy mekkora lesz a tömbünk mérete. Viszont, ha ez az érték csak futási időben derül ki, mert pl a felhasználó fogja megadni, akkor dinamikusan, azaz futási időben kell a tömböt lefoglalni. Módja 1 dimes tömb esetén:
    - 1 deklarálni egy megfelelő típusú pointert, ami majd a tömb első (azaz 0.) elemére fog mutatni
    - malloc-kal lefoglani n db, a fenti pointer által mutatott típusnak megfelelő méretű, folytonos memória területet
    - a pointer típusára konvertálni a malloc által visszaadott pointert (ha valakit érdekel miért kédezzen rá, de nem igazán ez a szint, szerintem)
    pl:int *p, n = 0;
    //... n értéket kap
    p = (int*) malloc (sor * sizeof (int));

    Ha pl int helyett float kell, akkor 3 (azaz három) helyen kell cserélni a típust: a pointer deklarációjánál, a sizeof paraméterében (ez adja meg, hogy mekkora helyet foglal 1 adott típusú változó), illetve a malloc elött.
    Esetleg célszerű egy if ágban lekezelni, hogy sikeres volt-e a hely lefoglalása:
    if ( (ppt = (int*) malloc (oszlop * sizeof (int)) ) == NULL)
    { printf ("nincshely");
    exit (1);
    }//if

    A fenti kód megpróbál oszlopnyi int-et lefoglalni, ennek első elemre mutató pointerét értékül kapja a ppt. Ezt utána megvizsgálunk, hogy NULL-e (ez az ami nem mutat sehova, vagyis nem sikerült lefoglalni), ha NULL, akkor a hibáról értesítjük a felhasználót és 1-es hibakóddal termináljuk (befejezzük) a program futását.
    Memória felszabadítása: az általunk malloc-kal lefoglalt memóriát senki sem fogja felszabadítani, így ha már nincs rá szükségünk, vagy ugyanazt a pointert akarjuk használni egy újabb terület lefoglalásához, akkor elötte kötelező felszabadítani a memória területet, illetve célszerű a pointernek a NULL értéket adni. Ha az elöbbit elfelejtjük a programunk elszívárogtatja a memóriát (memoryleak). A felhasználó ebből azt veszi észre, hogy minnél többször futtatja a programunkat, annál több szabad hely fogy el, amit a program lefutása után nem kap vissza. A második inkább csak biztosági szempontok miatt szükséges, nehogy valaki véletlenül (esetleg rossz akaratúak direkt) a már felszabadított memória területről próbáljanak olvasni, esetleg írni (amivel más adatai írodnak felül) Fontos: NE felejtsük el felszabadítani a lefoglalt területet!
    Felszabadítás: free(pointer); //ahol a pointer egy dinamukusan lefoglalt
    memória terület kezdőcíme

    Tényleg ne maradjon le az a free! Nem lehet elég sokszor hangsúlyozni.

    Több dimenziós tömbök: az egyszerűség kedvéért csak 2 dimes tömb, de ez alapján könnyen megoldható magasabb dimszámra is. (ugyanazt kell elvégezni még annyiszor, amíg el nem érjük a kívánt dim-et, ami alább következik)
    Megvalósítás(elmélet): 2 dimes tömböt c-ben csak úgy tudunk megvalósítani, hogy létrehozunk egy tömböt, amely minden eleme a 2 dimes tömb egy sorára mutat.
    Pl: 3x4-es tömb: van egy 3 elemű tömbünk, amelynek minden eleme egy-egy 4 elemű tömb első elemére mutat. Szemléletesen (a mutatást a -> jelőli)
    _
    |_| --------------> |_|_|_|_| első sor
    |_| --------------> |_|_|_|_| második sor
    |_| --------------> |_|_|_|_| harmadik sor

    Tömbben lévő értékek elérése: mesélve: először (a fenti 3x4-es pl-nél maradva) a 3 elemű tömbben kell megkeresni a kiválaszotott sort tartalmazó tömb címét. Ezután elugrani erre a címre és a megadott oszlop elemének címét meghatározni.
    Konkrét pl-en: próbáljuk meghatározni a fenti tömb (szokásos indexeléssel) a 3. sor 2. oszlopában lévő elemet. Ekkor először meg kell tudnunk a 3. sort tartalmazó vektor címét, vagyis t[2]-t (==t[3-1]). Ezután már csak a 2. oszlop elemét kell venni, vagyis t[2][1] (ld. előbb). Megvalósítás a pointer eltolásos technikával, inkább csak érdekesség képpen: a 3. sor címe: *(t + 2), az így megkapott vektor a 2. elemének értéke: *( *(t + 2) + 1)
    2 dimes tömb lefoglalása: a fenti szemléltető ábrán is (remélhetőleg) jól látszik, hogy 4 (azaz négy) darab tömböt kell lefoglalnunk, melyek a memóriában "bárhol" lehetnek (az össz megkötés annyi, hogy a tömb elemei egymás után jönnek):
    - 3db 4 elemű tömböt (vagyis lesz 3db pointerünk)
    - 1db 3 elemű pointereket tartalmazó tömböt (vagyis ennek típusa pointereket tartalmató tömb, ami viszont szintén egy pointer, vagyis 2db csillag kell deklarációnál)
    Pl:
    //2 dimes tömb lefoglalása
    int **ppt, sor, oszlop, i,j;
    //.. értékadás a sor-nak és az oszlop-nak
    //első rész lefoglaljuk az oszlop vektort, ami majd tárolja a
    sorok címeit
    if ( (ppt = (int**) malloc (sor * sizeof (int*)) ) == NULL)
    { printf("nincshely");
    exit(1);
    }//if

    //második rész lefoglaljuk egyesével a sorokat
    for (i = 0; i < sor; i++)
    if ((ppt[i] = (int*) malloc (oszlop * sizeof (int)) ) == NULL)
    { printf ("nincshely");
    exit (1);
    }//if
    //for

    //2 dimes tömb bejárása:
    for(i=0;i<sor;i++)
    {
    for(j=0;j<oszlop;j++)
    {
    ppt[i][j]=j; //itt tetszüleges int állhat; értékadás
    printf("%d ",ppt[i][j]); //érték "lekérése"
    }//forj
    printf("\n");
    } //fori

    //2dimes tömb felszabadítása:
    for(i = 0; i < sor; i++)
    free(ppt[i]);
    free(ppt);

    Pont úgy ahogy a fenti pl-nél maradva 4 tömböt kell lefoglalni 4-et is kell felszabadítani!
    2 dimes tömb megvalósítása 1 dimes tömbbel: a 2 dimes tömb felfogható úgy is, hogy egymás után írjuk a sorait, így egy 1 dimes tömböt kapunk. A korábbi 3x4-es pl, így egy 1x12-es vektorra módosul. Egyetlen "probléma", hogy hogyan lehet kiszámolni a harmadik sor második elemének indexét. A megoldás nagyon egyszerű, csak végig kell gondolni mi történt az eredeti tömbbel (a szokásos indexelést használva): leírtunk egymás után 2 sort (2 * 4 elemet), majd a 3. sort megkezdve leírtunk még 2 elemet, vagyis meg is lett a 3.sor 2. eleme. (persze a többit is leírtuk, de az index kiszámításához nincs rájuk szükség) Vagyis mostmár általánosan: az x-edik sorhoz elötte el kell mennünk x-1 sor elemein, majd az x. sor elemei közül még az y-adikig. Legyen mondjuk minden sorban n db elem, ekkor az x-edik sor y-adik eleme: (x - 1) * n + y. Már csak azt kell végig gondolni, hogy a c féle 0-tól való indexelés változtat-e valamit vagy sem. Vagyis, ha a (innentől már a c-s indexelés) 0. sor 1. (ami nálunk az 1 sor 2. eleme) elemének az indexét kell meghatároznunk, akkor az 0 * n + 1 indexű lesz, ha az 1. sor 3. eleme 1 * n + 3, vagyis általánosan x-edik sor y-adik eleme: x * n + y.
    Magasabb szinten, esetleg optimalizáláskor jöhet jól, ugyanis ezzel a módszerrel a tömb tetszőleges eleme 1 lépésben megkapható, míg a korábban leírt változattal csak 2 lépésben
    Több dimenziós tömbök: csak vázlatosan: vizuális típusú embereknek: téglapból (XxY) úgy lehet téglát(XxYxZ) készíteni, hogy az (x,y) koordináta "fölé is teszünk 1 vektort(magasság)". Ezesetben ugyanúgy lesz 1 tömbünk, ami a sorokra mutató pointereket fog tartalmazni, viszont a sorok most szintén pointereket fognak tartalmazni, ezek lesznek az (x,y) helyen a fölé tett vektorra mutató pointerek. Vagyis lesz 1db X elemű tömb(int***), amiben lesznek a sorokra mutató pointerek (int**), lesz Xdb Y elemű vektor(int**), amik a magasság vektorokra mutató pointereket (int*) tartalmaznak és lesz X*Ydb Z elemű vektor (int*), ami tartalmazza a magasság adatokat (int).
    Másként lehet úgy is nézni, hogy van egy vektorunk, aminek minden elem 1-1 két dimes tömbre mutat. Magasabb dim-ek ezzel a rekurzióval előállíthatók, vagy alkalmazható a fenti dim csökkentő módszer, bonyolultabb képlettel.

    Láncolt listák: mese: aki érti a 2 dimes tömböknél történteket, annak ez sem jelenthet gondot. A láncolt lista azt az ötletet használja fel, amit a sorok címének meghatározására használtunk fel. Viszont itt azt szeretnénk, hogy tárolhassunk a pointer mellett még adato(ka)t, illetve ne egy sorra mutasson a pointer, hanem egy másik ugyanilyen elemre. Ilyen adatszerkezetet használnak, akkor ha pl nem tudjuk előre, hogy mennyi adat lesz és a felhasználó sem tudja előre. Béna példa: mennyi madarfajt tudsz felsorolni? Ezt a példát tovább víve, a listában tárolandó adat a madárfaj neve, a pointer értéke kezdetben NULL, aztán ha a felhasználó megad egy újabb madárfajt, akkor dinamikusan létrehozunk még egy ilyen lista elemet, és ennek címét állítjuk be az előbb említett pointernek. Célszerű egy külön pointerben eltárolni a lista első elemét (fej/fej elem), és bejáráskor egy másik pointerrel (akt) végig menni rajtuk. (így nem veszik el az eleje)
    Megvalósítás: szükséges hozzá tudni mi az a struct! A lista típusa egy struct lesz, ami lehetővé teszi, hogy adatokat is tárolhassunk és mutathassunk egy másik azonos típusú struct-ra.
    Saját structunk létrehozása, illetve a fej deklarálása:
    typedef struct madarFaj
    {
    char nev[30];
    struct madarFaj *kov; // kov pointer madarFaj típusú
    struktúrára
    }MADARFAJ; //létrehoztuk a MADARFAJ nevű típust
    MADARFAJ *fej, *akt;
    char madarFajNeve[30];

    Új létrehozása és értékadások:
    //ciklusban madárfaj bekérése
    MADARFAJ p = (MADARFAJ*) malloc (sizeof (MADARFAJ) );
    //új listaelem lefoglalása
    p->nev = madarFajNeve; //elmentjük a faj nevét
    p->kov = NULL; //még nincs következő lista elem, ezért nem mutat sehova sem
    akt->kov = p; //hozzá fűzzük a listához az új elemet.

    Fontos: mivel a lista elemei is dinamikusan lefoglalt memória terültetek, ezért egyesével végig kell menni minden lista elemen és fölszabadítani!
    akt = fej;
    while (akt != NULL) //amíg van elem a listában
    {
    MADARFAJ *p = akt; //elmentjük az aktuális listaelem címét
    akt = p->kov; //az aktot a következő elemre állítjuk
    (felszabadítás után nehéz lenne)
    free(p);
    }

  • kingabo

    őstag

    válasz Cicero #2297 üzenetére

    A pointerezést holnap megpróbálom összeírni, meg a "mágiás" részt. már ha elfogadod tőlem, nem írja meg elöbb senki sem, nem google-zol rá

    "Ja a lényeg kimaradt: a zh példában ha jól emlékszem 8x8-as tömb volt megadva."
    Ez esetben a progi elejére:#define MAX_MERET 8 és tömb deklarációnál ciklusoknál MAX_MERET-et írod be. C-ben nincs const, csak így lehet megoldani, a precompiler fogja minden egyes helyen behelyettesíteni a kódban a MAX_MERET helyére a 8-at. Azért célszerű így csinálni, mert ha valaki azt mondja, hogy mégse 8 hanem 10 elemű a tömb, akkor 1 helyen átírod, újrafordítod és kész is vagy, nem kell mindenütt cserélgetni.aztán meg debuggolni ha valahol mégis kimaradt. A keres + csere működhet, ha a kódban nincs pl 128, de ha van, máris nem lehet keres+cserét használni

    "Az én hibám hogy AxB-t írtam, de ha kukacoskodni akarnék nem kötöttem ki hogy A!=B :P"
    Viszont azt se írtad, hogy A==B :P , ezért vettem úgy hogy nem egyenlők.
    Nem bme-s vagyok, nem tudom ott hogy oktatják/nem oktatják a programozás. Számomra teljesen logikus volt, hogy AxB-sre ha nem megy a dolog, akkor megszorítom a kisebb dim szerinti négyzetes mátrixra...

Új hozzászólás Aktív témák