OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 1 Számábrázolás Számok bináris alakja A számítógépek memóriájában a számokat bináris alakban (kettes számrendszerben) ábrázoljuk. A bináris alakban felírt szám egy számjegye 0 vagy 1 értékű lehet. Ezt hívjuk bitnek. A számjegy értéke a pozíciójától, a helyiértékétől függ. A kettes számrendszer helyiértékei a kettő hatványai. Egy természetes szám bináris alakjának számjegyeit helyiérték szerint növekvő sorrendben egy kettővel való osztogatásos módszerrel lehet előállítani. Az osztásoknál keletkezett maradék a soron következő számjegy, az osztás eredménye pedig a következő osztás osztandója. Az osztogatás addig tart, amíg az osztandó nem nulla. Példa: Adjuk meg a 183 (10) bináris alakját! 183 : 2 = 91 maradt: 1 91 : 2 = 45 maradt: 1 45 : 2 = 22 maradt: 1 22 : 2 = 11 maradt: 0 11 : 2 = 5 maradt: 1 5 : 2 = 2 maradt: 1 2 : 2 = 1 maradt: 0 1 : 2 = 0 maradt: 1 Tehát 183 (10) = 10110111 (2) = 1*2 7 + 0*2 6 + 1*2 5 + 1*2 4 + 0*2 3 + 1*2 2 + 1*2 1 + 1*2 0 Egy törtszám bináris alakjának számjegyeit helyiérték szerint csökkenő sorrendben egy kettővel való szorzásos módszerrel lehet előállítani. A szorzat egész része a soron következő számjegy, törtrésze a következő szorzás szorzandója. A módszer addig tart, amíg a szorzandó nem nulla, de előfordulhat, hogy ez nem következik be, mert a kettes számrendszerben felírt szám egy végtelen kettedes tört lesz. Példa: Adjuk meg a 0.8125 (10) bináris alakját! 0.8125 * 2 = 1.6250 egészrész: 1 törtrész: 0.625 0.625 * 2 = 1.250 egészrész: 1 törtrész: 0.25 0.25 * 2 = 0.50 egészrész: 0 törtrész: 0.5 0.5 * 2 = 1.0 egészrész: 1 törtrész: 0 Tehát 0.75 (10) = 0.1101 (2) = 1*1/2 + 1*1/4 + 0*1/8 + 1*1/16 Példa: Adjuk meg a 0.1 (10) bináris alakját! 0.1 * 2 = 0.2 egészrész: 0 törtrész: 0.2 0.2 * 2 = 0.4 egészrész: 0 törtrész: 0.4 0.4 * 2 = 0.8 egészrész: 0 törtrész: 0.8 0.8 * 2 = 1.6 egészrész: 1 törtrész: 0.6 0.6 * 2 = 1.2 egészrész: 1 törtrész: 0.2 0.2 * 2 = 0.4 egészrész: 0 törtrész: 0.4 0.4 * 2 = 0.8 egészrész: 0 törtrész: 0.8 Tehát 0.1 (10) = 0.00011 (2) (végtelen szakaszos tört)
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 2 Természetes számok ábrázolása A számítógépeken a 8 bit (1 bájt) többszörösein történik az ábrázolás, hiszen csak a bájt a memória legkisebb címezhető egysége. A természetes számok ábrázolásánál egyszerűen kettes számrendszerbe váltjuk át a számot. Egyetlen kérdés az, hogy rendelkezésre áll-e elegendő bit, a számjegyek ábrázolására. Ha nem, túlcsordulásról beszélünk. Ilyenkor az eredeti szám értéke hibásan rögzül. Egész számok ábrázolása Az egész számok ábrázolása abban tér el a természetes számokétól, hogy a szám abszolút értéke mellett az előjelét is ábrázolni kell. Az abszolút értéket bináris alakban ábrázoljuk, az előjelet pedig egyetlen biten. Történetileg többféle ábrázolás is kialakult. Egyenes kód: A számokat s biten ábrázoljuk úgy, hogy az első biten a szám előjelét (0 - pozitív, 1 - negatív), utána pedig a szám abszolút értékének bináris alakját. Az ábrázolható számok -2 s-1 +1 és 2 s-1 1 közé kell, hogy essenek, ellenkező esetben túlcsordulás lép fel. Az ábrázolás hátránya, hogy a nullát két különböző kód is jelzi (00000000 illetve 10000000), továbbá az, hogy két szám összegét a számok előjelétől függő módszerekkel lehet kiszámolni. Egyes komplemens kód vagy inverz kód: A számokat s biten ábrázoljuk úgy, hogy az abszolút értékének bináris alakja nullával kezdődjön. Az ábrázolható számok -2 s-1 +1 és 2 s-1 1 közé kell, hogy essenek, ellenkező esetben túlcsordulás lép fel. A pozitív egész számot ezzel a bináris alakkal kódoljuk, a negatív egész szám esetén az abszolút érték 0-val kezdődő bináris alakját komplementáljuk. (Ilyenkor az első bit 1-es lesz, amely jelzi, hogy a szám negatív.) Az ábrázolás hátránya, hogy a nullát két különböző kód is jelzi (00000000 illetve 11111111), továbbá az, hogy két szám összeadásakor keletkező túlcsordulás bitet (legmagasabb helyiértéken keletkező átvitel) hozzá kell adni az eredményhez, ha a helyes eredményt akarjuk megkapni (feltéve, hogy az eredmény is ábrázolható s biten). Kettes komplemens kód: Az egész számok általánosan elterjedt kódolása. A számokat s biten ábrázoljuk úgy, hogy az abszolút értékének bináris alakja nullával kezdődjön. Az ábrázolható számok -2 s-1 és 2 s-1 1 közé kell, hogy essenek, ellenkező esetben túlcsordulás lép fel. A pozitív egész számot ezzel a bináris alakkal kódoljuk, a negatív egész szám esetén a szám abszolút értékének bináris alakját komplementáljuk és hozzáadunk egyet. (Az első bit jelzi, hogy a szám pozitív-e vagy negatív: 0 - pozitív, 1 - negatív.) 1. Példa. Adjuk meg a +12 egész szám kettes komplemens kódját 4 bájton! i) 12 (10) = 00000000 00000000 00000000 00001100 (2)
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 3 2. Példa. Adjuk meg a -12 egész szám kettes komplemens kódját! i) 12 (10) = 00000000 00000000 00000000 00001100 (2) ii) Vegyük a bináris alak komplementerét! 11111111 11111111 11111111 11110011 iii) Adjunk hozzá binárisan 1-et! 11111111 11111111 11111111 11110100 Többletes kód: A számokhoz, mielőtt binárisan ábrázoljuk őket, hozzáadunk egy rögzített értéket (eltolás), és az így kapott számnak vesszük a bináris alakját. Az eltolás mértékét úgy állapítjuk meg, hogy az ábrázolni kívánt számok az eltolás után ne legyenek negatívak. Például ha s biten kívánjuk a számainkat ábrázolni, akkor az eltolás lehet a 2 s-1. Ekkor ennek bináris alakja (100 00) jelenti a nullát és az ábrázolható számok -2 s-1 és 2 s-1 1 közé esnek. (Az első bit most is a szám előjelét jelzi, de 1 pozitív és 0 - negatív.) Ha az eltolásnak a 2 s-1 1-et választjuk, akkor a nulla kódja 01111111 lesz, az ábrázolható számok -2 s-1 +1 és 2 s-1 közé esnek. Elsősorban számok nagyság szerinti összehasonlításához, számok összegének és különbségének kiszámolásához alkalmas ábrázolás. Fixpontos számábrázolás Valós számok ábrázolása úgy, hogy rögzített számú biten lehet külön a szám egészrészét és külön a törtrészét ábrázolni. Az előjel ábrázolására alkalmazhatók az egész számok ábrázolásánál bemutatott módszerek BCD számábrázolás A binárisan kódolt decimális számokat tízes számrendszerben ábrázoljuk, de a számjegyeket binárisan kódoljuk, többnyire egy számjegyet félbájton. Külön félbájt szolgál az előjel jelzésére. Ez a forma kevésbé alkalmas a számok közötti műveletek elvégzésére. Lebegőpontos számábrázolás Valós számok IEEE 754 szabvány szerinti (4 illetve 8 bájtos) lebegőpontos ábrázolásánál a számokat ((-2)*b+1)*m*2 k alakban írjuk fel, ahol b az előjel (0 - pozitív, 1 - negatív), az m a mantissza (1 m<2), a k pedig a karakterisztika. Maga a kód a valós szám előjelbitjéből (1 bit), utána s biten (az s 8 illetve 11 bit) a karakterisztika 2 s-1 1 eltolású többletes kódjából (ekkor - 2 s-1 +1 k 2 s-1 ), végül a mantissza bináris alakjának törtrészéből (23 illetve 52 biten) áll. Példa: Adjuk meg a -12.25 valós szám lebegőpontos kódját 1+11+52 biten! i. Negatív szám, ezért az előjel bit 0 lesz: b = 0 ii. A szám abszolút értékének bináris alakja: 12.25 (10) = 1100.01 (2) iii. Normalizáljuk az így kapott számot 1 és 2 közé: 1100.01 (2) = 1.10001 (2) *2 3 iv. Ábrázoljuk a karakterisztikát többletes kódban 11 biten: 3+2 10 1 (10) = 10000000010 (2) v. Állítsuk össze a kódot: 1 10000000010 10001000000000000000 00000000
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 4 Adatábrázolás Karakterek ábrázolása Egy adott karakterkészlet elemeit, karaktereit megsorszámozunk 0-val kezdődően, és ezeket a sorszámokat tároljuk binárisan kódolva a megfelelő karakter helyett. Az egyik legegyszerűbb az ASCII karakterkészket, amelyik 256 különböző karaktert tartalmaz. A 0 és 255 közötti sorszámok bináris kódja egy bájton ábrázolható. Az UTF-8 változó hosszú kódolással ábrázolja a karaktereket (magába foglalja és 1 bájton ábrázolja az ASCII karaktereket, de például az ékezetes betűk sorszámai 2 bájton kódolódnak) Logikai érték ábrázolása Habár a logikai érték tárolására egyetlen bit is elég lenne, de mivel a legkisebb címezhető egység a bájt, ezért egy bájtot foglal el. A nulla értékű (csupa nulla bit) bájt a hamis értéket, minden más az igaz értéket reprezentálja. Struktúra ábrázolása Egy struktúra mezői közvetlenül egymás után foglalnak helyet a memóriában. Az első elem címe egyben a struktúra címe is. Például egy -5 egész számot és egy hamis logikai értéket magába foglaló struktúra esetén a memória egymást követő 5 bájtján, először 4 bájton a -5 kettes komplemens kódja, majd egy csupa nulla bitből álló bájt jelenik meg. (struct valami int n; bool l) Tömb ábrázolása Egy tömb elemei közvetlenül egymás után, sorban helyezkednek el a memóriában. Az első elem címe egyben a tömb címe is. Például négy egész számot tartalmazó [0, 1, -5, 3] tömb esetében a memória egymás utáni 16 bájtján fog elhelyezkedni a négy egész szám kettes komplemens kódja. (int v[] = 0, 1, -5, 3) A több dimenziós tömb felfogható eggyel kevesebb dimenziós tömbök tömbjeként. Például a mátrix felfogható, mint sorainak tömbje (ez a sor-folytonos szemlélet), vagy oszlopainak tömbje (ez az oszlop-folytonos szemlélet). Sor-folytonos szemlélet esetén a mátrixok sorait közvetlenül egymás után tároljuk, a sorok tárolására pedig a korábban elmondottak érvényesek. Karakterlánc ábrázolása A karakterlánc egy változtatható hosszúságú karaktersorozat. A karakterek kódját sorban egymás után tároljuk el. A sorozat hosszát vagy egy külön számláló (ez többnyire a lánc előtt tárolt egész szám, ami a karakterlánc kezdetét jelző címen található) vagy a lánc végén utolsó utáni karakterként elhelyezett speciális karakter ( \0 ) jelzi.
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 5 Pointerváltozó és közvetett hivatkozás Változó int i = -2 Pointer int *p Közvetett hivatkozás int i; int *p = &i; *p = 3; Referencia változó int i = -2; int &r = i;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 6 Tömbök és pointerek Automatikus helyfoglalású tömb int v[] = 0, 1, -5, 3 v == &v == &v[0] v[0] == *v Pointer és tömb int *p; p = v; *(p+2) = -3; p[2] = 12; *(v+2) = -5;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 7 Dinamikus helyfoglalás int *p; p = new int; *p = -2; delete p; Dinamikus helyfoglalású tömb int* v; v = new int[4]; for(int i=0; i<4; ++i) v[i] = i; delete[] v;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 8 Feladat Rendezzük növekvő sorrendbe egy mátrix sorait a sorösszegek alapján! Használjunk buborékrendezést! Megoldás A feladat megoldásához szükségünk lesz egy mátrixra, amelyet a szabványos bementről töltünk fel. A rendezéshez először kiszámoljuk a mátrix sorösszegeit, és azokat a sorok indexeivel együtt egy egydimenziós tömbben tároljuk el. A rendezést ugyanis nem közvetlenül a mátrixon hajtjuk végre (a sorok cseréje túl költséges lenne), hanem ezen a sorösszeg-sorindex párokat tartalmazó segédvektoron. A segédvektor elemeit az összegérétkek alapján sorba rendezzük. A rendezés után a segédvektor i-edik eleme azt mutatja majd meg, hogy a rendezett mátrix (amit fizikailag nem kell létrehoznunk) i-edik sora az eredeti mátrix hányadik sorának felel meg. Ez alapján tudjuk a szabványos kimeneten az átrendezett mátrixot megjeleníteni úgy, hogy közben a mátrix a kezdeti tartalmát őrzi. Specifikáció A = (t : Z n m, v : Pár n ) Pár = rec(ért: Z, ind: Z) Ef = ( t = t ) Uf = ( t = t v : Pár n i [1..n]: v [i] = ( j=1..m t[i,j], i) v = rendezett(v ) ) Absztrakt program Előkészítés: A v tömböt az utófeltételben szereplő v értékűre állítja be. i = 1.. n s := össz(i) v[i].ért := össz(i) s := 0 v[i].ért :=i j = 1.. m s := s + t[i,j] Buborékrendezés: A v tömb rendezése ért szerint. i = 2.. n menet(v, i, k) menet(v, i, k) i := k k := n j:=n.. i v[j].ért < v[j-1].ért csere(j,j-1) SKIP k := j
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 9 Megjelenítés: A t mátrix sorainak megjelenítése összegük szerinti növekedő sorrendben a v tömb segítségével. i = 1.. n i = 1.. n Ír(t[v[i].ind][j] Megoldás C++-ban Az implementációt három változatban készítjük el. A mátrixot és a segédvektort először statikusan lefoglalt tömbökkel másodszor dinamikusan lefoglalt tömbökkel, végül a vector< > sablon segítségével ábrázoljuk. Az első változathoz szükség van a max maximális méretre. A tömbjeinket (mátrixot és a rendezésnél használt segédvektort) ekkora méretűre deklaráljuk. A max max méretű mátrixnak természetesen csak egy n m-es részét, a max méretű segédvektornak pedig az első n elemét használjuk. Hátránya ennek a megoldásnak, hogy egyfelől memória pazarlás, másfelől ellenőrizni kell, hogy a tényleges méretek nem nagyobbak-e a max értéknél. A második változatban mind a mátrixot, mind a rendezéshez használt segédvektort dinamikusan foglaljuk le. Ez a megoldás a memória felhasználásában takarékos, de magunknak kell gondoskodni a memória terület lefoglalásáról és felszabadításáról, illetve annak a kivételnek a kezeléséről, amely akkor keletkezik, ha az általunk kért memória foglalás nem sikerül. A harmadik változat hátterében is dinamikus memória foglalás történik, de ezt elrejti előlünk a vector< > típus. További előny, hogy itt nem kell a tömbök paraméterátadásánál külön átadni a tömb aktuális méretét, az bármikor lekérdezhető tulajdonsága a tömbnek. Mindhárom változat main() függvénye tartalmazza az alábbi négy szakaszt, amely a dinamikusan lefoglalt tömbök használatánál kiegészül a tömb felszabadítását végző ötödik szakasszal (ez meghívja a free() függvényt) is: 1. Mátrix létrehozása és feltöltése (meghívja a read() függvényt) A mátrix deklarációja után a read()függvény végzi el a mátrix feltöltését. A második változatban a mátrix deklarációja még csak egy pointert jelez. 2. Segédvektor létrehozása és feltöltése (meghívja a sum() függvényt) A segédvektort a változatnak megfelelően deklaráljuk, majd az absztrakt programban megadott módon feltöltjük. A segédvektor egy elemének kitöltéséhez az absztrakt programban megadott s:=össz(i) függvényt a sum() függvény implementálja. 3. Segédvektor rendezése (meghívja a session() függvényt) Az absztrakt program alapján működik, az abban megadott k:=menet(i) függvényt implementálja a session() függvény. 4. Rendezett mátrix kiírása (meghívja a write() függvényt) A mátrix sorainak a rendezés utáni sorrendjében történő kiírása. Ehhez a segédvektort használjuk hiszen annak i-edik eleme mutatja meg, hogy a rendezett mátrix (amit fizikailag nem hoztunk létre) i-edik sora az eredeti mátrix hányadik sorának felel meg.
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 10 1. Változat #include <iostream> #include <iomanip> #define max 100 using namespace std; struct Pair int value; int ind; ; void read(int t[][max], int &n, int &m); int sum(const int x[], const int m); int session(pair v[], const int n, const int i); void write(const Pair v[], const int t[][max], const int n, const int m); int main() int t[max][max]; int n, m; // Mátrix beolvasása read(t, n, m); // Előkészítés Pair v[max]; for(int i=0; i<n; ++i) v[i].value = sum(t[i], m); v[i].ind = i; // Rendezés for(int i=1; i<n; ++i) i = session(v, n, i); // Rendezett mátrix megjelenítése write(t, n, m); char ch; cin >> ch; return 0;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 11 2. Változat #include <iostream> #include <iomanip> using namespace std; struct Pair int value; int ind; ; void read(int** &t, int &n, int &m); int sum(const int x[], const int m); int session(pair v[], const int n, const int i); void write(const Pair v[], int** t, const int n, const int m); void free(int** &t, const int n); int main() int** t; int n, m; // Mátrix beolvasása read(t, n, m); // Előkészítés try Pair* v = new Pair[n]; catch(std::bad_alloc o) cout << "Memória foglalási hiba\n"; exit(1); for(int i=0; i<n; ++i) v[i].value = sum(t[i], m); v[i].ind = i; // Rendezés for(int i=1; i<n; ++i) i = session(v, n, i); // Rendezett mátrix megjelenítése write(t, n, m); free(t, n); delete[] v; char ch; cin >> ch; return 0;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 12 3. Változat #include <iostream> #include <iomanip> #include <vector> using namespace std; struct Pair int value; int ind; ; void read(vector<vector<int> > &t); int sum(const vector<int> &x); int session(vector<pair> &v, const int i); void write(const vector<vector<int> > &t); int main() vector<vector<int> > t; // Mátrix beolvasása read(t); int n = (int)t.size(); // Előkészítés vector<pair> v; for(int i=0; i<n; ++i) v[i].value = sum(t[i]); v[i].ind = i; // Rendezés for(int i=1; i<n; ++i) i = session(v, i); // Rendezett mátrix megjelenítése write(t); char ch; cin >> ch; return 0;
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 13 Függvények A read() függvény megvalósításai természetesen eltérnek: az első változat csak feltölti a max max méretű mátrix n m-es részét, a második változat lefoglalja az n darab sor számára az m méretű helyeket, a harmadik változat elrejti ezt a memóriafoglalást, erre csak a resize() metódus hívása utal. void read(int t[][max], int &n, int &m) // 1.VÁLTOZAT cout << "n: "; cin >> n; cout << "m: "; cin >> m; if(n>max m>max) cout << Hibás méretek! ; char ch; cin >> ch; exit(2); for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) cout << "t[" << i << "," << j << "]: "; cin >> t[i][j]; void read(int** &t, int &n, int &m) // 2. VÁLTOZAT try cout << "n: "; cin >> n; cout << "m: "; cin >> m; t = new int*[n]; for(int i=0; i<n; ++i) t[i] = new int[m]; for(int j=0; j<m; ++j) cout << "t[" << i << "," << j << "]: "; cin >> t[i][j]; catch(std::bad_alloc o) cout << "Memória foglalási hiba\n"; char ch; cin >> ch; exit(1); void read(vector<vector<int> > &t) // 3. VÁLTOZAT int n; cout << "n: "; cin >> n; t.resize(n); int m; cout << "m: "; cin >> m; for(int i=0; i<n; ++i) t[i].resize(m); for(int j=0; j<m; ++j) cout << "t[" << i << "," << j << "]: "; cin >> t[i][j];
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 14 A write() függvény a v segédvektor által megadott sorrendben írja ki a mátrix sorait a szabványos kimenetre. Az i-edikként kiírt sor sorszáma a v[i].ind-ben található, ezért az i-edik sor j-edik elemének kiírása a cout << t[v[i].ind][j] utasítással történik. A write() függvény ezért csak a szignatúrájában különbözi a három változatban, de a harmadik változatban a tömbök méretének megadása most is a size() metódussal történik. void write(const Pair v[], const int t[max][], const int n, const int m) for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) cout << t[v[i].ind][j] << "\t"; cout << endl; // 1.VÁLTOZAT void write(const Pair v[], int** t, const int n, const int m) for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) cout << t[v[i].ind][j] << "\t"; cout << endl; // 2.VÁLTOZAT void write(const vector<pair> &v, vector<vector<int> > &t) for(int i=0; i<(int)t.size(); ++i) for(int j=0; j<(int)t[v[i].ind].size(); ++j) cout << t[v[i].ind][j] << "\t"; cout << endl; // 3.VÁLTOZAT A második változat második paramétere nem const, ugyanis nem megengedett const int** változónak egy int** értékül adása, erre pedig a main() függvényből való meghívásnál sor kerül. A második változatban nekünk kell gondoskodni a lefoglalt terület felszabadításáról. void free(int** &t, const int n) for(int i=0; i<n; ++i) delete[] t[i]; delete[] t; // 2. VÁLTOZAT
OAF Gregorics Tibor : Memória használat C++ szemmel (munkafüzet) 15 A sum() és a session() az absztrakt program alapján készült, az első két változatban teljesen azonos. int sum(const int x[], const int m) int s = 0; for(int j=0; j<m; ++j) s += x[j]; return s; int session(pair v[], const int n, const int i) int k = n-1; for(int j=n-1; j>i-1; --j) if( v[j].value < v[j-1].value ) Pair c = v[j]; v[j] = v[j-1]; v[j-1] = c; k = j; return k; A harmadik változatban ezeknek a függvényeknek más lesz a szignatúrája. int sum(const vector<int> &x); int session(vector<pair> &v, const int i); A függvénytörzsben egyetlen eltérés van: a paraméterként megkapott tömbök méretét nem tartalmazza külön paraméterváltozó (m illetve n), mert azt lekérdezhetjük a size() metódussal ( (int)x.size() illetve (int)v.size()).